This commit is contained in:
陈思海 2026-03-18 16:01:18 +08:00
parent b50ab115a9
commit 7b4feec0f0
6 changed files with 6 additions and 64 deletions

View File

@ -76,8 +76,7 @@ namespace UnityEngine.UI
{ {
Press(); Press();
PlayAudio(clickAudioClip); PlayAudio(clickAudioClip);
// if we get set disabled during the press
// don't run the coroutine.
if (!IsActive() || !IsInteractable()) if (!IsActive() || !IsInteractable())
return; return;

View File

@ -21,7 +21,6 @@ namespace UnityEngine.UI
[SerializeField] [SerializeField]
private List<UXToggle> m_Toggles = new List<UXToggle>(); private List<UXToggle> m_Toggles = new List<UXToggle>();
// 新增:默认选中的 Toggle可在 Inspector 设置)
[SerializeField] [SerializeField]
private UXToggle m_DefaultToggle; private UXToggle m_DefaultToggle;
@ -76,7 +75,6 @@ namespace UnityEngine.UI
if (m_Toggles.Contains(toggle)) if (m_Toggles.Contains(toggle))
m_Toggles.Remove(toggle); m_Toggles.Remove(toggle);
// 如果被移除的正好是默认选项,则清空默认项
if (m_DefaultToggle == toggle) if (m_DefaultToggle == toggle)
{ {
m_DefaultToggle = null; m_DefaultToggle = null;
@ -91,32 +89,27 @@ namespace UnityEngine.UI
if (!m_Toggles.Contains(toggle)) if (!m_Toggles.Contains(toggle))
m_Toggles.Add(toggle); m_Toggles.Add(toggle);
// 当组内已有其他开启项,并且不允许 all-off 时,
// 如果新加入的 toggle 本身为 on则需要把它关闭以维持单选。
if (!allowSwitchOff) if (!allowSwitchOff)
{ {
// 如果已经有一个 active 的 toggle且不是刚加入的这个并且刚加入的 toggle isOn 为 true则关闭刚加入的 toggle
var firstActive = GetFirstActiveToggle(); var firstActive = GetFirstActiveToggle();
if (firstActive != null && firstActive != toggle && toggle.isOn) if (firstActive != null && firstActive != toggle && toggle.isOn)
{ {
// 我们使用不触发回调的方式避免编辑时产生不必要的事件调用
toggle.SetIsOnWithoutNotify(false); toggle.SetIsOnWithoutNotify(false);
} }
else if (firstActive == null) else if (firstActive == null)
{ {
// 没有任何 active 且不允许 all-off如果 group 指定了 defaultToggle优先选中它但仅当 default 在本组内且可交互)
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle)) if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle))
{ {
var dt = m_DefaultToggle; var dt = m_DefaultToggle;
if (dt != null && dt != toggle) if (dt != null && dt != toggle)
{ {
// 确保默认项被选中editor/runtime 都适用)
dt.isOn = true; dt.isOn = true;
NotifyToggleOn(dt); NotifyToggleOn(dt);
} }
else if (dt == toggle) else if (dt == toggle)
{ {
// 新加入项就是默认项,确保它为 on
toggle.isOn = true; toggle.isOn = true;
NotifyToggleOn(toggle); NotifyToggleOn(toggle);
} }
@ -125,22 +118,19 @@ namespace UnityEngine.UI
} }
} }
// 新增:判断组里是否包含某 toggle用于运行时/编辑器避免重复注册)
public bool ContainsToggle(UXToggle toggle) public bool ContainsToggle(UXToggle toggle)
{ {
return m_Toggles != null && m_Toggles.Contains(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() public void EnsureValidState()
{ {
if (m_Toggles == null) if (m_Toggles == null)
m_Toggles = new List<UXToggle>(); m_Toggles = new List<UXToggle>();
// Remove null references first
m_Toggles.RemoveAll(x => x == null); m_Toggles.RemoveAll(x => x == null);
// 如果不允许 all-off优先尝试选中 defaultToggle否则选中第一个。
if (!allowSwitchOff && !AnyTogglesOn() && m_Toggles.Count != 0) if (!allowSwitchOff && !AnyTogglesOn() && m_Toggles.Count != 0)
{ {
UXToggle toSelect = null; UXToggle toSelect = null;
@ -164,7 +154,6 @@ namespace UnityEngine.UI
if (activeToggles.Count() > 1) if (activeToggles.Count() > 1)
{ {
// 如果 defaultToggle 是开启的,优先保留它
UXToggle firstActive = GetFirstActiveToggle(); UXToggle firstActive = GetFirstActiveToggle();
foreach (UXToggle toggle in activeToggles) foreach (UXToggle toggle in activeToggles)
@ -178,8 +167,6 @@ namespace UnityEngine.UI
} }
} }
// 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++) for (int i = 0; i < m_Toggles.Count; i++)
{ {
var t = m_Toggles[i]; var t = m_Toggles[i];
@ -188,7 +175,6 @@ namespace UnityEngine.UI
if (t.group != this) if (t.group != this)
{ {
// 使用 setter 会触发必要的注册/注销逻辑
t.group = this; t.group = this;
} }
} }
@ -206,7 +192,6 @@ namespace UnityEngine.UI
public UXToggle GetFirstActiveToggle() public UXToggle GetFirstActiveToggle()
{ {
// 优先返回 defaultToggle如果它处于 on 且在组内)
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle) && m_DefaultToggle.isOn) if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle) && m_DefaultToggle.isOn)
return m_DefaultToggle; return m_DefaultToggle;

View File

@ -101,10 +101,9 @@ namespace UnityEngine.UI
base.OnDidApplyAnimationProperties(); base.OnDidApplyAnimationProperties();
} }
// Centralized group setter logic.
private void SetToggleGroup(UXGroup newGroup, bool setMemberValue) private void SetToggleGroup(UXGroup newGroup, bool setMemberValue)
{ {
// 如果组没有改变,仍然需要确保组里包含此 toggle修复编辑器中批量拖拽只注册最后一项的问题
if (m_Group == newGroup) if (m_Group == newGroup)
{ {
if (setMemberValue) if (setMemberValue)
@ -115,21 +114,18 @@ namespace UnityEngine.UI
newGroup.RegisterToggle(this); newGroup.RegisterToggle(this);
} }
// 尝试同步组状态,确保编辑器批量赋值时能稳定显示
if (newGroup != null) if (newGroup != null)
newGroup.EnsureValidState(); newGroup.EnsureValidState();
return; return;
} }
// 从旧组注销(如果存在)
if (m_Group != null) if (m_Group != null)
m_Group.UnregisterToggle(this); m_Group.UnregisterToggle(this);
if (setMemberValue) if (setMemberValue)
m_Group = newGroup; m_Group = newGroup;
// 注册到新组(不再强依赖 IsActive(),以保证编辑器批量赋值时也能正确注册)
if (newGroup != null) if (newGroup != null)
{ {
if (!newGroup.ContainsToggle(this)) if (!newGroup.ContainsToggle(this))
@ -137,11 +133,9 @@ namespace UnityEngine.UI
newGroup.RegisterToggle(this); newGroup.RegisterToggle(this);
} }
// 如果正在 on通知组维持单选逻辑
if (isOn) if (isOn)
newGroup.NotifyToggleOn(this); newGroup.NotifyToggleOn(this);
// 同步组的内部状态,确保 Inspector 列表正确显示
newGroup.EnsureValidState(); newGroup.EnsureValidState();
} }
} }
@ -157,19 +151,14 @@ namespace UnityEngine.UI
Set(value, false); Set(value, false);
} }
// 在 UXToggle 类内(建议放在类底部较靠近 Set 和 PlayEffect 的位置)加入:
// 覆盖 DoStateTransition保证 isOn 时视觉上总是 Selected除非 Disabled
protected override void DoStateTransition(Selectable.SelectionState state, bool instant) protected override void DoStateTransition(Selectable.SelectionState state, bool instant)
{ {
// 如果被禁用,照常显示 Disabled
if (state == Selectable.SelectionState.Disabled) if (state == Selectable.SelectionState.Disabled)
{ {
base.DoStateTransition(state, instant); base.DoStateTransition(state, instant);
return; return;
} }
// 当 isOn 为 true 时,总是以 Selected 的样式渲染(但不改变 EventSystem 的真实 selection
if (m_IsOn) if (m_IsOn)
state = Selectable.SelectionState.Selected; state = Selectable.SelectionState.Selected;
@ -199,11 +188,8 @@ namespace UnityEngine.UI
onValueChanged.Invoke(m_IsOn); onValueChanged.Invoke(m_IsOn);
} }
// 触发选择态视觉刷新(当 isOn 为 true 显示 Selectedfalse 时回到 normal/hover/... 的计算结果)
// instant 参数:如果 ToggleTransition 是 None 我们使用 instant为保持和原来 PlayEffect 一致。
bool instant = (toggleTransition == Toggle.ToggleTransition.None); bool instant = (toggleTransition == Toggle.ToggleTransition.None);
// 传入的 state当 isOn 为 trueDoStateTransition 会将其替换为 Selected
// 当 isOn 为 false使用 currentSelectionState 以让正常的鼠标/键盘状态生效。
var stateToApply = m_IsOn ? Selectable.SelectionState.Selected : currentSelectionState; var stateToApply = m_IsOn ? Selectable.SelectionState.Selected : currentSelectionState;
DoStateTransition(stateToApply, instant); DoStateTransition(stateToApply, instant);
} }

View File

@ -25,7 +25,6 @@ namespace UnityEngine.UI
} }
} }
// 改成 Component[](或 MonoBehaviour[]Unity 可以序列化 Component 引用
[SerializeField] private Component[] hotButtons; [SerializeField] private Component[] hotButtons;
internal void BindHotKeys() internal void BindHotKeys()

View File

@ -16,7 +16,6 @@ namespace UnityEngine.UI
Performed = 1 Performed = 1
} }
// 优化1: 使用struct减少GC
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct HotkeyRegistration internal struct HotkeyRegistration
{ {
@ -31,7 +30,6 @@ namespace UnityEngine.UI
} }
} }
// 优化2: 使用struct存储handler信息
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct HandlerInfo internal struct HandlerInfo
{ {
@ -47,28 +45,23 @@ namespace UnityEngine.UI
internal static class UXHotkeyRegisterManager internal static class UXHotkeyRegisterManager
{ {
// 优化3: 使用数组池替代List避免频繁扩容
private const int INITIAL_CAPACITY = 32; private const int INITIAL_CAPACITY = 32;
private const int MAX_REGISTRATIONS_PER_ACTION = 16; private const int MAX_REGISTRATIONS_PER_ACTION = 16;
// 优化4: 使用固定大小的数组池
private static HotkeyRegistration[][] _registrationPool; private static HotkeyRegistration[][] _registrationPool;
private static int[] _registrationCounts; private static int[] _registrationCounts;
private static string[] _actionIds; private static string[] _actionIds;
private static int _actionCount; private static int _actionCount;
private static int _actionCapacity; private static int _actionCapacity;
// 优化5: 使用数组替代Dictionary更快的查找
private static HandlerInfo[] _handlers; private static HandlerInfo[] _handlers;
private static InputActionReference[] _actionRefs; private static InputActionReference[] _actionRefs;
// 优化6: Button到ActionId的映射使用数组索引
private static IHotkeyTrigger[] _buttons; private static IHotkeyTrigger[] _buttons;
private static int[] _buttonToActionIndex; private static int[] _buttonToActionIndex;
private static int _buttonCount; private static int _buttonCount;
private static int _buttonCapacity; private static int _buttonCapacity;
// 优化7: 缓存委托,避免每次创建
private static Action<InputAction.CallbackContext>[] _cachedHandlers; private static Action<InputAction.CallbackContext>[] _cachedHandlers;
private static int _cachedHandlerCount; private static int _cachedHandlerCount;
@ -128,7 +121,6 @@ namespace UnityEngine.UI
string actionId = action.action.id.ToString(); string actionId = action.action.id.ToString();
int actionIndex = FindOrCreateActionIndex(actionId); int actionIndex = FindOrCreateActionIndex(actionId);
// 添加注册信息
ref int count = ref _registrationCounts[actionIndex]; ref int count = ref _registrationCounts[actionIndex];
if (count >= MAX_REGISTRATIONS_PER_ACTION) if (count >= MAX_REGISTRATIONS_PER_ACTION)
{ {
@ -139,11 +131,9 @@ namespace UnityEngine.UI
_registrationPool[actionIndex][count] = new HotkeyRegistration(button, pressType); _registrationPool[actionIndex][count] = new HotkeyRegistration(button, pressType);
count++; count++;
// 记录button映射
int buttonIndex = FindOrCreateButtonIndex(button); int buttonIndex = FindOrCreateButtonIndex(button);
_buttonToActionIndex[buttonIndex] = actionIndex; _buttonToActionIndex[buttonIndex] = actionIndex;
// 优化8: 只在第一次注册时创建handler
if (count == 1) if (count == 1)
{ {
Action<InputAction.CallbackContext> handler = GetOrCreateHandler(actionIndex); Action<InputAction.CallbackContext> handler = GetOrCreateHandler(actionIndex);
@ -178,7 +168,6 @@ namespace UnityEngine.UI
if (actionIndex < 0) if (actionIndex < 0)
return; return;
// 从注册列表中移除
ref int count = ref _registrationCounts[actionIndex]; ref int count = ref _registrationCounts[actionIndex];
HotkeyRegistration[] registrations = _registrationPool[actionIndex]; HotkeyRegistration[] registrations = _registrationPool[actionIndex];
@ -188,12 +177,10 @@ namespace UnityEngine.UI
{ {
EHotkeyPressType pressType = registrations[i].pressType; EHotkeyPressType pressType = registrations[i].pressType;
// 优化9: Swap-remove避免数组移动
registrations[i] = registrations[count - 1]; registrations[i] = registrations[count - 1];
registrations[count - 1] = default; registrations[count - 1] = default;
count--; count--;
// 如果是最后一个注册清理handler
if (count == 0) if (count == 0)
{ {
ref HandlerInfo handlerInfo = ref _handlers[actionIndex]; ref HandlerInfo handlerInfo = ref _handlers[actionIndex];
@ -223,12 +210,10 @@ namespace UnityEngine.UI
} }
} }
// 清理button映射
_buttons[buttonIndex] = null; _buttons[buttonIndex] = null;
_buttonToActionIndex[buttonIndex] = -1; _buttonToActionIndex[buttonIndex] = -1;
} }
// 优化10: 使用缓存的委托,避免闭包分配
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Action<InputAction.CallbackContext> GetOrCreateHandler(int actionIndex) private static Action<InputAction.CallbackContext> GetOrCreateHandler(int actionIndex)
{ {
@ -242,7 +227,6 @@ namespace UnityEngine.UI
Array.Resize(ref _cachedHandlers, actionIndex + 1); Array.Resize(ref _cachedHandlers, actionIndex + 1);
} }
// 优化11: 使用静态方法 + 参数传递,避免闭包
int capturedIndex = actionIndex; int capturedIndex = actionIndex;
_cachedHandlers[actionIndex] = ctx => OnHotkeyTriggered(capturedIndex); _cachedHandlers[actionIndex] = ctx => OnHotkeyTriggered(capturedIndex);
return _cachedHandlers[actionIndex]; return _cachedHandlers[actionIndex];
@ -254,24 +238,20 @@ namespace UnityEngine.UI
int count = _registrationCounts[actionIndex]; int count = _registrationCounts[actionIndex];
if (count > 0) if (count > 0)
{ {
// 触发最后一个注册的button栈顶
ref HotkeyRegistration registration = ref _registrationPool[actionIndex][count - 1]; ref HotkeyRegistration registration = ref _registrationPool[actionIndex][count - 1];
registration.button?.HotkeyActionTrigger(); registration.button?.HotkeyActionTrigger();
} }
} }
// 优化12: 线性查找小数据集比Dictionary更快
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int FindOrCreateActionIndex(string actionId) private static int FindOrCreateActionIndex(string actionId)
{ {
// 查找现有
for (int i = 0; i < _actionCount; i++) for (int i = 0; i < _actionCount; i++)
{ {
if (_actionIds[i] == actionId) if (_actionIds[i] == actionId)
return i; return i;
} }
// 创建新的
if (_actionCount >= _actionCapacity) if (_actionCount >= _actionCapacity)
{ {
ExpandActionCapacity(); ExpandActionCapacity();
@ -285,14 +265,12 @@ namespace UnityEngine.UI
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int FindOrCreateButtonIndex(IHotkeyTrigger button) private static int FindOrCreateButtonIndex(IHotkeyTrigger button)
{ {
// 查找现有
for (int i = 0; i < _buttonCount; i++) for (int i = 0; i < _buttonCount; i++)
{ {
if (ReferenceEquals(_buttons[i], button)) if (ReferenceEquals(_buttons[i], button))
return i; return i;
} }
// 查找空槽
for (int i = 0; i < _buttonCapacity; i++) for (int i = 0; i < _buttonCapacity; i++)
{ {
if (_buttons[i] == null) if (_buttons[i] == null)
@ -304,7 +282,6 @@ namespace UnityEngine.UI
} }
} }
// 扩容
if (_buttonCount >= _buttonCapacity) if (_buttonCount >= _buttonCapacity)
{ {
ExpandButtonCapacity(); ExpandButtonCapacity();
@ -358,7 +335,6 @@ namespace UnityEngine.UI
_buttonCapacity = newCapacity; _buttonCapacity = newCapacity;
} }
// 优化13: 批量操作API
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterHotkeyBatch(Span<IHotkeyTrigger> buttons, InputActionReference action, EHotkeyPressType pressType) public static void RegisterHotkeyBatch(Span<IHotkeyTrigger> buttons, InputActionReference action, EHotkeyPressType pressType)
{ {
@ -377,7 +353,6 @@ namespace UnityEngine.UI
} }
} }
// 调试信息
#if UNITY_EDITOR #if UNITY_EDITOR
public static string GetDebugInfo() public static string GetDebugInfo()
{ {
@ -411,7 +386,6 @@ public static class UXHotkeyHotkeyExtension
} }
} }
// 优化14: 批量绑定
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BindHotKeyBatch(this IHotkeyTrigger[] buttons) public static void BindHotKeyBatch(this IHotkeyTrigger[] buttons)
{ {

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using System.Linq; using System.Linq;
namespace UnityEngine.UI namespace UnityEngine.UI
{ {