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 m_Toggles = new List(); // 新增:默认选中的 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(); // 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 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 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 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; } } } }