com.alicizax.unity.ui.exten.../Runtime/UXComponent/Group/UXGroup.cs
陈思海 551423f09d 大优化
大优化 懒得写了 重构了分离了 UXButton UXToggle UXSelectable UXGroup
2025-12-19 20:26:22 +08:00

287 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.EventSystems;
using UnityEngine;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/UXGroup", 31)]
[DisallowMultipleComponent]
public class UXGroup : UIBehaviour
{
[SerializeField] private bool m_AllowSwitchOff = false;
public bool allowSwitchOff
{
get { return m_AllowSwitchOff; }
set { m_AllowSwitchOff = value; }
}
[SerializeField]
private List<UXToggle> m_Toggles = new List<UXToggle>();
// 新增:默认选中的 Toggle可在 Inspector 设置)
[SerializeField]
private UXToggle m_DefaultToggle;
public UXToggle defaultToggle
{
get { return m_DefaultToggle; }
set { m_DefaultToggle = value; EnsureValidState(); }
}
protected UXGroup()
{
}
protected override void Start()
{
EnsureValidState();
base.Start();
}
protected override void OnEnable()
{
EnsureValidState();
base.OnEnable();
}
private void ValidateToggleIsInGroup(UXToggle toggle)
{
if (toggle == null || !m_Toggles.Contains(toggle))
throw new ArgumentException(string.Format("UXToggle {0} is not part of ToggleGroup {1}", new object[] { toggle, this }));
}
public void NotifyToggleOn(UXToggle toggle, bool sendCallback = true)
{
ValidateToggleIsInGroup(toggle);
for (var i = 0; i < m_Toggles.Count; i++)
{
if (m_Toggles[i] == toggle)
continue;
if (sendCallback)
m_Toggles[i].isOn = false;
else
m_Toggles[i].SetIsOnWithoutNotify(false);
}
}
public void UnregisterToggle(UXToggle toggle)
{
if (toggle == null)
return;
if (m_Toggles.Contains(toggle))
m_Toggles.Remove(toggle);
// 如果被移除的正好是默认选项,则清空默认项
if (m_DefaultToggle == toggle)
{
m_DefaultToggle = null;
}
}
public void RegisterToggle(UXToggle toggle)
{
if (toggle == null)
return;
if (!m_Toggles.Contains(toggle))
m_Toggles.Add(toggle);
// 当组内已有其他开启项,并且不允许 all-off 时,
// 如果新加入的 toggle 本身为 on则需要把它关闭以维持单选。
if (!allowSwitchOff)
{
// 如果已经有一个 active 的 toggle且不是刚加入的这个并且刚加入的 toggle isOn 为 true则关闭刚加入的 toggle
var firstActive = GetFirstActiveToggle();
if (firstActive != null && firstActive != toggle && toggle.isOn)
{
// 我们使用不触发回调的方式避免编辑时产生不必要的事件调用
toggle.SetIsOnWithoutNotify(false);
}
else if (firstActive == null)
{
// 没有任何 active 且不允许 all-off如果 group 指定了 defaultToggle优先选中它但仅当 default 在本组内且可交互)
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle))
{
var dt = m_DefaultToggle;
if (dt != null && dt != toggle)
{
// 确保默认项被选中editor/runtime 都适用)
dt.isOn = true;
NotifyToggleOn(dt);
}
else if (dt == toggle)
{
// 新加入项就是默认项,确保它为 on
toggle.isOn = true;
NotifyToggleOn(toggle);
}
}
}
}
}
// 新增:判断组里是否包含某 toggle用于运行时/编辑器避免重复注册)
public bool ContainsToggle(UXToggle toggle)
{
return m_Toggles != null && m_Toggles.Contains(toggle);
}
// Ensure list consistency: clean nulls, and make sure every toggle in this list actually references this group.
public void EnsureValidState()
{
if (m_Toggles == null)
m_Toggles = new List<UXToggle>();
// Remove null references first
m_Toggles.RemoveAll(x => x == null);
// 如果不允许 all-off优先尝试选中 defaultToggle否则选中第一个。
if (!allowSwitchOff && !AnyTogglesOn() && m_Toggles.Count != 0)
{
UXToggle toSelect = null;
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle))
{
toSelect = m_DefaultToggle;
}
else
{
toSelect = m_Toggles[0];
}
if (toSelect != null)
{
toSelect.isOn = true;
NotifyToggleOn(toSelect);
}
}
IEnumerable<UXToggle> activeToggles = ActiveToggles();
if (activeToggles.Count() > 1)
{
// 如果 defaultToggle 是开启的,优先保留它
UXToggle firstActive = GetFirstActiveToggle();
foreach (UXToggle toggle in activeToggles)
{
if (toggle == firstActive)
{
continue;
}
toggle.isOn = false;
}
}
// Synchronize each toggle's group reference to this group if necessary,
// but avoid causing re-ordering in cases where it's already consistent.
for (int i = 0; i < m_Toggles.Count; i++)
{
var t = m_Toggles[i];
if (t == null)
continue;
if (t.group != this)
{
// 使用 setter 会触发必要的注册/注销逻辑
t.group = this;
}
}
}
public bool AnyTogglesOn()
{
return m_Toggles.Find(x => x != null && x.isOn) != null;
}
public IEnumerable<UXToggle> ActiveToggles()
{
return m_Toggles.Where(x => x != null && x.isOn);
}
public UXToggle GetFirstActiveToggle()
{
// 优先返回 defaultToggle如果它处于 on 且在组内)
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle) && m_DefaultToggle.isOn)
return m_DefaultToggle;
IEnumerable<UXToggle> activeToggles = ActiveToggles();
return activeToggles.Count() > 0 ? activeToggles.First() : null;
}
public void SetAllTogglesOff(bool sendCallback = true)
{
bool oldAllowSwitchOff = m_AllowSwitchOff;
m_AllowSwitchOff = true;
if (sendCallback)
{
for (var i = 0; i < m_Toggles.Count; i++)
if (m_Toggles[i] != null)
m_Toggles[i].isOn = false;
}
else
{
for (var i = 0; i < m_Toggles.Count; i++)
if (m_Toggles[i] != null)
m_Toggles[i].SetIsOnWithoutNotify(false);
}
m_AllowSwitchOff = oldAllowSwitchOff;
}
public void Next()
{
SelectAdjacent(true);
}
public void Preview()
{
SelectAdjacent(false);
}
private void SelectAdjacent(bool forward)
{
if (m_Toggles == null || m_Toggles.Count == 0)
return;
UXToggle current = GetFirstActiveToggle();
int currentIndex = current != null ? m_Toggles.IndexOf(current) : -1;
int idx = currentIndex;
if (idx == -1 && !forward)
idx = 0;
for (int step = 0; step < m_Toggles.Count; step++)
{
if (forward)
{
idx = (idx + 1) % m_Toggles.Count;
}
else
{
idx = (idx - 1 + m_Toggles.Count) % m_Toggles.Count;
}
UXToggle t = m_Toggles[idx];
if (t == null)
continue;
if (!t.IsActive())
continue;
if (!t.IsInteractable())
continue;
t.isOn = true;
return;
}
}
}
}