2025-12-19 20:26:22 +08:00
|
|
|
using UnityEngine;
|
2026-04-28 20:52:06 +08:00
|
|
|
using UnityEngine.EventSystems;
|
2025-10-13 20:20:01 +08:00
|
|
|
|
2025-12-09 20:30:11 +08:00
|
|
|
namespace UnityEngine.UI
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-12-19 20:26:22 +08:00
|
|
|
[AddComponentMenu("UI/UXGroup", 31)]
|
2025-12-09 20:30:11 +08:00
|
|
|
[DisallowMultipleComponent]
|
|
|
|
|
public class UXGroup : UIBehaviour
|
|
|
|
|
{
|
2025-12-19 20:26:22 +08:00
|
|
|
[SerializeField] private bool m_AllowSwitchOff = false;
|
2025-04-11 17:26:28 +08:00
|
|
|
|
2025-12-19 20:26:22 +08:00
|
|
|
[SerializeField]
|
2026-04-28 20:52:06 +08:00
|
|
|
private UXToggle[] m_Toggles = new UXToggle[0];
|
2025-12-19 20:26:22 +08:00
|
|
|
|
|
|
|
|
[SerializeField]
|
|
|
|
|
private UXToggle m_DefaultToggle;
|
2025-04-11 17:26:28 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
private int m_ToggleCount;
|
|
|
|
|
private UXToggle m_CurrentToggle;
|
|
|
|
|
private int m_CurrentIndex = -1;
|
|
|
|
|
|
|
|
|
|
public bool allowSwitchOff
|
|
|
|
|
{
|
|
|
|
|
get { return m_AllowSwitchOff; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
m_AllowSwitchOff = value;
|
|
|
|
|
EnsureValidState();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 20:26:22 +08:00
|
|
|
public UXToggle defaultToggle
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-12-19 20:26:22 +08:00
|
|
|
get { return m_DefaultToggle; }
|
2026-04-28 20:52:06 +08:00
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
m_DefaultToggle = ContainsToggle(value) ? value : null;
|
|
|
|
|
EnsureValidState();
|
|
|
|
|
}
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
2025-07-28 13:04:49 +08:00
|
|
|
|
2025-12-19 20:26:22 +08:00
|
|
|
protected UXGroup()
|
2025-12-09 20:30:11 +08:00
|
|
|
{
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
2025-10-13 20:20:01 +08:00
|
|
|
|
2025-12-19 20:26:22 +08:00
|
|
|
protected override void Start()
|
|
|
|
|
{
|
|
|
|
|
EnsureValidState();
|
|
|
|
|
base.Start();
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
2025-12-09 20:30:11 +08:00
|
|
|
|
2025-12-19 20:26:22 +08:00
|
|
|
protected override void OnEnable()
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-12-19 20:26:22 +08:00
|
|
|
EnsureValidState();
|
|
|
|
|
base.OnEnable();
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-19 20:26:22 +08:00
|
|
|
public void NotifyToggleOn(UXToggle toggle, bool sendCallback = true)
|
|
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
EnsureStorage();
|
|
|
|
|
int index = IndexOfToggle(toggle);
|
|
|
|
|
if (index < 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_CurrentToggle = toggle;
|
|
|
|
|
m_CurrentIndex = index;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < m_ToggleCount; i++)
|
2025-12-09 20:30:11 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
UXToggle item = m_Toggles[i];
|
|
|
|
|
if (item == null || item == toggle)
|
2025-12-19 20:26:22 +08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (sendCallback)
|
2026-04-28 20:52:06 +08:00
|
|
|
item.isOn = false;
|
2025-12-19 20:26:22 +08:00
|
|
|
else
|
2026-04-28 20:52:06 +08:00
|
|
|
item.SetIsOnWithoutNotify(false);
|
2025-12-09 20:30:11 +08:00
|
|
|
}
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void UnregisterToggle(UXToggle toggle)
|
|
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
EnsureStorage();
|
|
|
|
|
int index = IndexOfToggle(toggle);
|
|
|
|
|
if (index < 0)
|
2025-12-19 20:26:22 +08:00
|
|
|
return;
|
|
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
RemoveAt(index);
|
2025-12-19 20:26:22 +08:00
|
|
|
|
|
|
|
|
if (m_DefaultToggle == toggle)
|
|
|
|
|
m_DefaultToggle = null;
|
2026-04-28 20:52:06 +08:00
|
|
|
|
|
|
|
|
if (m_CurrentToggle == toggle)
|
|
|
|
|
{
|
|
|
|
|
m_CurrentToggle = null;
|
|
|
|
|
m_CurrentIndex = -1;
|
2025-12-09 20:30:11 +08:00
|
|
|
}
|
2026-04-28 20:52:06 +08:00
|
|
|
|
|
|
|
|
EnsureSingleSelection();
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
2025-07-28 13:04:49 +08:00
|
|
|
|
2025-12-19 20:26:22 +08:00
|
|
|
public void RegisterToggle(UXToggle toggle)
|
2025-12-09 20:30:11 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
EnsureStorage();
|
|
|
|
|
if (toggle == null || ContainsToggle(toggle))
|
2025-12-19 20:26:22 +08:00
|
|
|
return;
|
2025-10-13 20:20:01 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
EnsureCapacity(m_ToggleCount + 1);
|
|
|
|
|
m_Toggles[m_ToggleCount] = toggle;
|
|
|
|
|
m_ToggleCount++;
|
|
|
|
|
|
|
|
|
|
if (toggle.isOn)
|
|
|
|
|
NotifyToggleOn(toggle);
|
|
|
|
|
else
|
|
|
|
|
EnsureSingleSelection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool ContainsToggle(UXToggle toggle)
|
|
|
|
|
{
|
|
|
|
|
EnsureStorage();
|
|
|
|
|
return IndexOfToggle(toggle) >= 0;
|
|
|
|
|
}
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
public void EnsureValidState()
|
|
|
|
|
{
|
|
|
|
|
EnsureStorage();
|
|
|
|
|
CompactNulls();
|
|
|
|
|
SyncToggleGroups();
|
|
|
|
|
EnsureDefaultToggle();
|
|
|
|
|
EnsureSingleSelection();
|
|
|
|
|
}
|
2026-03-18 16:01:18 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
public bool AnyTogglesOn()
|
|
|
|
|
{
|
|
|
|
|
return FindFirstActiveIndex() >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public UXToggle GetFirstActiveToggle()
|
|
|
|
|
{
|
|
|
|
|
int index = FindFirstActiveIndex();
|
|
|
|
|
return index >= 0 ? m_Toggles[index] : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetAllTogglesOff(bool sendCallback = true)
|
|
|
|
|
{
|
|
|
|
|
bool oldAllowSwitchOff = m_AllowSwitchOff;
|
|
|
|
|
m_AllowSwitchOff = true;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < m_ToggleCount; i++)
|
2025-12-09 20:30:11 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
UXToggle toggle = m_Toggles[i];
|
|
|
|
|
if (toggle == null)
|
|
|
|
|
continue;
|
2026-03-18 16:01:18 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
if (sendCallback)
|
|
|
|
|
toggle.isOn = false;
|
|
|
|
|
else
|
2025-12-19 20:26:22 +08:00
|
|
|
toggle.SetIsOnWithoutNotify(false);
|
2025-12-09 20:30:11 +08:00
|
|
|
}
|
2026-04-28 20:52:06 +08:00
|
|
|
|
|
|
|
|
m_CurrentToggle = null;
|
|
|
|
|
m_CurrentIndex = -1;
|
|
|
|
|
m_AllowSwitchOff = oldAllowSwitchOff;
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
2025-12-09 20:30:11 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
public void Next()
|
|
|
|
|
{
|
|
|
|
|
SelectAdjacent(true);
|
|
|
|
|
}
|
2026-03-18 16:01:18 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
public void Previous()
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
SelectAdjacent(false);
|
2025-12-09 20:30:11 +08:00
|
|
|
}
|
2025-04-11 17:26:28 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
internal UXToggle GetToggleAt(int index)
|
2025-12-09 20:30:11 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
return index >= 0 && index < m_ToggleCount ? m_Toggles[index] : null;
|
|
|
|
|
}
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
internal int ToggleCount
|
|
|
|
|
{
|
|
|
|
|
get { return m_ToggleCount; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EnsureStorage()
|
|
|
|
|
{
|
|
|
|
|
if (m_Toggles == null)
|
|
|
|
|
m_Toggles = new UXToggle[0];
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
if (m_ToggleCount == 0)
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
int count = 0;
|
|
|
|
|
for (int i = 0; i < m_Toggles.Length; i++)
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
if (m_Toggles[i] != null)
|
|
|
|
|
count = i + 1;
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
2025-08-08 20:56:31 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
m_ToggleCount = count;
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
if (m_ToggleCount > m_Toggles.Length)
|
|
|
|
|
m_ToggleCount = m_Toggles.Length;
|
|
|
|
|
}
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
private void CompactNulls()
|
|
|
|
|
{
|
|
|
|
|
int write = 0;
|
|
|
|
|
for (int read = 0; read < m_Toggles.Length; read++)
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
UXToggle toggle = m_Toggles[read];
|
|
|
|
|
if (toggle == null)
|
|
|
|
|
continue;
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
m_Toggles[write] = toggle;
|
|
|
|
|
write++;
|
|
|
|
|
}
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
for (int i = write; i < m_Toggles.Length; i++)
|
|
|
|
|
m_Toggles[i] = null;
|
|
|
|
|
|
|
|
|
|
m_ToggleCount = write;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SyncToggleGroups()
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < m_ToggleCount; i++)
|
|
|
|
|
{
|
|
|
|
|
UXToggle toggle = m_Toggles[i];
|
|
|
|
|
if (toggle != null && toggle.group != this)
|
|
|
|
|
toggle.SetToggleGroupInternal(this, true);
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
2026-04-28 20:52:06 +08:00
|
|
|
}
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
private void EnsureDefaultToggle()
|
|
|
|
|
{
|
|
|
|
|
if (m_ToggleCount == 0)
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
m_DefaultToggle = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
if (m_DefaultToggle != null && IndexOfToggle(m_DefaultToggle) >= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!m_AllowSwitchOff)
|
|
|
|
|
m_DefaultToggle = m_Toggles[0];
|
|
|
|
|
else
|
|
|
|
|
m_DefaultToggle = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EnsureSingleSelection()
|
|
|
|
|
{
|
|
|
|
|
int selectedIndex = FindSelectedIndex();
|
|
|
|
|
if (selectedIndex < 0 && !m_AllowSwitchOff && m_ToggleCount > 0)
|
|
|
|
|
{
|
|
|
|
|
selectedIndex = GetDefaultIndex();
|
|
|
|
|
if (selectedIndex < 0)
|
|
|
|
|
selectedIndex = 0;
|
|
|
|
|
|
|
|
|
|
UXToggle toggle = m_Toggles[selectedIndex];
|
|
|
|
|
if (toggle != null)
|
|
|
|
|
toggle.isOn = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selectedIndex >= 0)
|
|
|
|
|
{
|
|
|
|
|
m_CurrentToggle = m_Toggles[selectedIndex];
|
|
|
|
|
m_CurrentIndex = selectedIndex;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < m_ToggleCount; i++)
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
UXToggle toggle = m_Toggles[i];
|
|
|
|
|
if (toggle != null && i != selectedIndex && toggle.isOn)
|
|
|
|
|
toggle.SetIsOnWithoutNotify(false);
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-28 20:52:06 +08:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
m_CurrentToggle = null;
|
|
|
|
|
m_CurrentIndex = -1;
|
|
|
|
|
}
|
2025-12-09 20:30:11 +08:00
|
|
|
}
|
2025-08-08 20:56:31 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
private int FindSelectedIndex()
|
2025-12-09 20:30:11 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
int defaultIndex = GetDefaultIndex();
|
|
|
|
|
if (defaultIndex >= 0 && m_Toggles[defaultIndex].isOn)
|
|
|
|
|
return defaultIndex;
|
|
|
|
|
|
|
|
|
|
return FindFirstActiveIndex();
|
2025-12-09 20:30:11 +08:00
|
|
|
}
|
2025-08-08 20:56:31 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
private int FindFirstActiveIndex()
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
for (int i = 0; i < m_ToggleCount; i++)
|
|
|
|
|
{
|
|
|
|
|
UXToggle toggle = m_Toggles[i];
|
|
|
|
|
if (toggle != null && toggle.isOn)
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
2025-08-08 20:56:31 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
private int GetDefaultIndex()
|
2025-08-11 14:45:27 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
if (m_DefaultToggle == null)
|
|
|
|
|
return -1;
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
return IndexOfToggle(m_DefaultToggle);
|
2025-08-08 20:56:31 +08:00
|
|
|
}
|
2025-08-11 14:45:27 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
private int IndexOfToggle(UXToggle toggle)
|
2025-12-09 20:30:11 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
if (toggle == null || m_Toggles == null)
|
|
|
|
|
return -1;
|
2025-12-09 20:30:11 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
for (int i = 0; i < m_ToggleCount; i++)
|
2025-12-09 20:30:11 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
if (m_Toggles[i] == toggle)
|
|
|
|
|
return i;
|
2025-12-09 20:30:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
return -1;
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
private void EnsureCapacity(int capacity)
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
if (m_Toggles == null)
|
|
|
|
|
m_Toggles = new UXToggle[capacity < 4 ? 4 : capacity];
|
|
|
|
|
|
|
|
|
|
if (m_Toggles.Length >= capacity)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
int newCapacity = m_Toggles.Length == 0 ? 4 : m_Toggles.Length * 2;
|
|
|
|
|
while (newCapacity < capacity)
|
|
|
|
|
newCapacity *= 2;
|
|
|
|
|
|
|
|
|
|
UXToggle[] newToggles = new UXToggle[newCapacity];
|
|
|
|
|
for (int i = 0; i < m_ToggleCount; i++)
|
|
|
|
|
newToggles[i] = m_Toggles[i];
|
|
|
|
|
|
|
|
|
|
m_Toggles = newToggles;
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
private void RemoveAt(int index)
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
int lastIndex = m_ToggleCount - 1;
|
|
|
|
|
for (int i = index; i < lastIndex; i++)
|
|
|
|
|
m_Toggles[i] = m_Toggles[i + 1];
|
|
|
|
|
|
|
|
|
|
if (lastIndex >= 0)
|
|
|
|
|
m_Toggles[lastIndex] = null;
|
|
|
|
|
|
|
|
|
|
m_ToggleCount = lastIndex;
|
2025-12-19 20:26:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SelectAdjacent(bool forward)
|
|
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
if (m_ToggleCount == 0)
|
2025-12-19 20:26:22 +08:00
|
|
|
return;
|
|
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
int index = ResolveCurrentIndex();
|
|
|
|
|
if (index < 0)
|
|
|
|
|
index = forward ? -1 : 0;
|
2025-12-19 20:26:22 +08:00
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
for (int step = 0; step < m_ToggleCount; step++)
|
2025-12-19 20:26:22 +08:00
|
|
|
{
|
2026-04-28 20:52:06 +08:00
|
|
|
index = forward ? index + 1 : index - 1;
|
|
|
|
|
if (index >= m_ToggleCount)
|
|
|
|
|
index = 0;
|
|
|
|
|
else if (index < 0)
|
|
|
|
|
index = m_ToggleCount - 1;
|
|
|
|
|
|
|
|
|
|
UXToggle toggle = m_Toggles[index];
|
|
|
|
|
if (toggle == null || !toggle.IsActive() || !toggle.IsInteractable())
|
2025-12-19 20:26:22 +08:00
|
|
|
continue;
|
|
|
|
|
|
2026-04-28 20:52:06 +08:00
|
|
|
toggle.isOn = true;
|
2025-12-19 20:26:22 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2025-12-09 20:30:11 +08:00
|
|
|
}
|
2026-04-28 20:52:06 +08:00
|
|
|
|
|
|
|
|
private int ResolveCurrentIndex()
|
|
|
|
|
{
|
|
|
|
|
if (m_CurrentIndex >= 0 && m_CurrentIndex < m_ToggleCount && m_Toggles[m_CurrentIndex] == m_CurrentToggle)
|
|
|
|
|
return m_CurrentIndex;
|
|
|
|
|
|
|
|
|
|
m_CurrentIndex = FindFirstActiveIndex();
|
|
|
|
|
m_CurrentToggle = m_CurrentIndex >= 0 ? m_Toggles[m_CurrentIndex] : null;
|
|
|
|
|
return m_CurrentIndex;
|
|
|
|
|
}
|
2025-08-08 20:56:31 +08:00
|
|
|
}
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|