非适配手柄 增加可选UX_NAVIGATION 宏
优化部分结构
This commit is contained in:
陈思海 2026-03-26 16:12:50 +08:00
parent fb9846d10c
commit dc8923564b
11 changed files with 487 additions and 536 deletions

View File

@ -15,7 +15,7 @@ public static class GlyphService
private static InputGlyphDatabase _database; private static InputGlyphDatabase _database;
public static InputGlyphDatabase Database static InputGlyphDatabase Database
{ {
get get
{ {
@ -362,5 +362,4 @@ public static class GlyphService
return StartsWithDevice(path, "<Gamepad>") || StartsWithDevice(path, "<Joystick>") || ContainsAny(path, OtherGamepadGroupHints); return StartsWithDevice(path, "<Gamepad>") || StartsWithDevice(path, "<Joystick>") || ContainsAny(path, OtherGamepadGroupHints);
} }
} }
} }

View File

@ -6,9 +6,9 @@ using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using AlicizaX; using AlicizaX;
public class InputBindingManager : MonoSingleton<InputBindingManager> public sealed class InputBindingManager : MonoServiceBehaviour<InputBindingManager>
{ {
public const string NULL_BINDING = "__NULL__"; private const string NULL_BINDING = "__NULL__";
private const string KEYBOARD_DEVICE = "<Keyboard>"; private const string KEYBOARD_DEVICE = "<Keyboard>";
private const string MOUSE_DELTA = "<Mouse>/delta"; private const string MOUSE_DELTA = "<Mouse>/delta";
private const string MOUSE_SCROLL = "<Mouse>/scroll"; private const string MOUSE_SCROLL = "<Mouse>/scroll";
@ -22,7 +22,7 @@ public class InputBindingManager : MonoSingleton<InputBindingManager>
private const string FILE_NAME = "input_bindings.json"; private const string FILE_NAME = "input_bindings.json";
public bool debugMode = false; public bool debugMode = false;
internal InputActionRebindingExtensions.RebindingOperation rebindOperation; private InputActionRebindingExtensions.RebindingOperation rebindOperation;
private bool isApplyPending = false; private bool isApplyPending = false;
private string defaultBindingsJson = string.Empty; private string defaultBindingsJson = string.Empty;
private string cachedSavePath; private string cachedSavePath;
@ -31,21 +31,6 @@ public class InputBindingManager : MonoSingleton<InputBindingManager>
private readonly Dictionary<string, (ActionMap map, ActionMap.Action action)> actionLookup = new(StringComparer.Ordinal); private readonly Dictionary<string, (ActionMap map, ActionMap.Action action)> actionLookup = new(StringComparer.Ordinal);
private readonly Dictionary<Guid, (ActionMap map, ActionMap.Action action)> actionLookupById = new(); private readonly Dictionary<Guid, (ActionMap map, ActionMap.Action action)> actionLookupById = new();
private readonly HashSet<string> ambiguousActionNames = new(StringComparer.Ordinal); private readonly HashSet<string> ambiguousActionNames = new(StringComparer.Ordinal);
private event Action _onInputsInit;
public event Action OnInputsInit
{
add
{
_onInputsInit += value;
// 重放行为:如果已经初始化,立即调用
if (isInputsInitialized)
{
value?.Invoke();
}
}
remove { _onInputsInit -= value; }
}
public event Action<bool, HashSet<RebindContext>> OnApply; public event Action<bool, HashSet<RebindContext>> OnApply;
public event Action<RebindContext> OnRebindPrepare; public event Action<RebindContext> OnRebindPrepare;
@ -54,12 +39,11 @@ public class InputBindingManager : MonoSingleton<InputBindingManager>
public event Action<RebindContext, RebindContext> OnRebindConflict; public event Action<RebindContext, RebindContext> OnRebindConflict;
public static event Action BindingsChanged; public static event Action BindingsChanged;
private bool isInputsInitialized = false;
public IReadOnlyDictionary<string, ActionMap> ActionMaps => actionMap; public IReadOnlyDictionary<string, ActionMap> ActionMaps => actionMap;
public IReadOnlyCollection<RebindContext> PreparedRebinds => preparedRebinds; public IReadOnlyCollection<RebindContext> PreparedRebinds => preparedRebinds;
public string SavePath private string SavePath
{ {
get get
{ {
@ -83,16 +67,9 @@ public class InputBindingManager : MonoSingleton<InputBindingManager>
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);
} }
protected override void Awake()
protected override void OnServiceInitialize()
{ {
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
if (actions == null) if (actions == null)
{ {
Log.Error("InputBindingManager: InputActionAsset not assigned."); Log.Error("InputBindingManager: InputActionAsset not assigned.");
@ -133,23 +110,14 @@ public class InputBindingManager : MonoSingleton<InputBindingManager>
} }
} }
isInputsInitialized = true;
_onInputsInit?.Invoke();
actions.Enable(); actions.Enable();
} }
protected override void OnDestroy() private void OnDestroy()
{ {
if (_instance == this)
{
_instance = null;
}
rebindOperation?.Dispose(); rebindOperation?.Dispose();
rebindOperation = null; rebindOperation = null;
// 清除所有事件处理器
_onInputsInit = null;
OnApply = null; OnApply = null;
OnRebindPrepare = null; OnRebindPrepare = null;
OnRebindStart = null; OnRebindStart = null;
@ -379,161 +347,6 @@ public class InputBindingManager : MonoSingleton<InputBindingManager>
} }
} }
/* ---------------- Public API ---------------- */
/// <summary>
/// 根据操作名称获取输入操作
/// </summary>
/// <param name="actionName">操作名称</param>
/// <returns>输入操作,未找到则返回 null</returns>
public static InputAction Action(string actionName)
{
var instance = Instance;
if (instance == null) return null;
if (TryGetAction(actionName, out InputAction action))
{
return action;
}
if (instance.ambiguousActionNames.Contains(actionName))
{
Log.Error($"[InputBindingManager] Action name '{actionName}' is ambiguous. Use 'MapName/{actionName}' instead.");
return null;
}
Log.Error($"[InputBindingManager] Could not find action '{actionName}'");
return null;
}
public static bool TryGetAction(string actionName, out InputAction action)
{
var instance = Instance;
if (instance == null || string.IsNullOrWhiteSpace(actionName))
{
action = null;
return false;
}
if (instance.actionLookup.TryGetValue(actionName, out var result))
{
action = result.action.action;
return true;
}
action = null;
return false;
}
/// <summary>
/// 开始重新绑定指定的输入操作
/// </summary>
/// <param name="actionName">操作名称</param>
/// <param name="compositePartName">复合部分名称(可选)</param>
public static void StartRebind(string actionName, string compositePartName = null)
{
var action = Action(actionName);
if (action == null) return;
// 自动决定 bindingIndex 和 deviceMatch
int bindingIndex = Instance.FindBestBindingIndexForKeyboard(action, compositePartName);
if (bindingIndex < 0)
{
Log.Error($"[InputBindingManager] No suitable binding found for action '{actionName}' (part={compositePartName ?? "<null>"})");
return;
}
Instance.actions.Disable();
Instance.PerformInteractiveRebinding(action, bindingIndex, KEYBOARD_DEVICE, true);
Instance.OnRebindStart?.Invoke();
if (Instance.debugMode)
{
Log.Info("[InputBindingManager] Rebind started");
}
}
/// <summary>
/// 取消当前的重新绑定操作
/// </summary>
public static void CancelRebind() => Instance.rebindOperation?.Cancel();
/// <summary>
/// 确认并应用准备好的重新绑定
/// </summary>
/// <param name="clearConflicts">是否清除冲突</param>
/// <returns>是否成功应用</returns>
public static async Task<bool> ConfirmApply(bool clearConflicts = true)
{
if (!Instance.isApplyPending) return false;
try
{
// 在清除之前创建准备好的重绑定的副本
HashSet<RebindContext> appliedContexts = Instance.OnApply != null
? new HashSet<RebindContext>(Instance.preparedRebinds)
: null;
foreach (var ctx in Instance.preparedRebinds)
{
if (!string.IsNullOrEmpty(ctx.overridePath))
{
if (ctx.overridePath == NULL_BINDING)
{
ctx.action.RemoveBindingOverride(ctx.bindingIndex);
}
else
{
ctx.action.ApplyBindingOverride(ctx.bindingIndex, ctx.overridePath);
}
}
var bp = GetBindingPath(ctx.action, ctx.bindingIndex);
if (bp != null)
{
bp.EffectivePath = (ctx.overridePath == NULL_BINDING) ? string.Empty : ctx.overridePath;
}
}
Instance.preparedRebinds.Clear();
await Instance.WriteOverridesToDiskAsync();
BindingsChanged?.Invoke();
Instance.OnApply?.Invoke(true, appliedContexts);
Instance.isApplyPending = false;
if (Instance.debugMode)
{
Log.Info("[InputBindingManager] Apply confirmed and saved.");
}
return true;
}
catch (Exception ex)
{
Log.Error("[InputBindingManager] Failed to apply binds: " + ex);
Instance.OnApply?.Invoke(false, null);
return false;
}
}
/// <summary>
/// 丢弃准备好的重新绑定
/// </summary>
public static void DiscardPrepared()
{
if (!Instance.isApplyPending) return;
// 在清除之前创建准备好的重绑定的副本(用于事件通知)
HashSet<RebindContext> discardedContexts = Instance.OnApply != null
? new HashSet<RebindContext>(Instance.preparedRebinds)
: null;
Instance.preparedRebinds.Clear();
Instance.isApplyPending = false;
Instance.OnApply?.Invoke(false, discardedContexts);
if (Instance.debugMode)
{
Log.Info("[InputBindingManager] Prepared rebinds discarded.");
}
}
private void PerformInteractiveRebinding(InputAction action, int bindingIndex, string deviceMatchPath = null, bool excludeMouseMovementAndScroll = true) private void PerformInteractiveRebinding(InputAction action, int bindingIndex, string deviceMatchPath = null, bool excludeMouseMovementAndScroll = true)
{ {
@ -692,79 +505,6 @@ public class InputBindingManager : MonoSingleton<InputBindingManager>
} }
} }
/// <summary>
/// 重置所有绑定到默认值
/// </summary>
public async Task ResetToDefaultAsync()
{
try
{
if (!string.IsNullOrEmpty(defaultBindingsJson))
{
actions.LoadBindingOverridesFromJson(defaultBindingsJson);
}
else
{
foreach (var map in actionMap.Values)
{
foreach (var a in map.actions.Values)
{
for (int b = 0; b < a.action.bindings.Count; b++)
{
a.action.RemoveBindingOverride(b);
}
}
}
}
RefreshBindingPathsFromActions();
await WriteOverridesToDiskAsync();
BindingsChanged?.Invoke();
if (debugMode)
{
Log.Info("Reset to default and saved.");
}
}
catch (Exception ex)
{
Log.Error("Failed to reset defaults: " + ex);
}
}
/// <summary>
/// 获取指定操作的绑定路径
/// </summary>
/// <param name="actionName">操作名称</param>
/// <param name="bindingIndex">绑定索引</param>
/// <returns>绑定路径,未找到则返回 null</returns>
public static BindingPath GetBindingPath(string actionName, int bindingIndex = 0)
{
var instance = Instance;
if (instance == null) return null;
if (instance.TryGetActionRecord(actionName, out var result)
&& result.action.bindings.TryGetValue(bindingIndex, out var binding))
{
return binding.bindingPath;
}
return null;
}
public static BindingPath GetBindingPath(InputAction action, int bindingIndex = 0)
{
var instance = Instance;
if (instance == null || action == null) return null;
if (instance.TryGetActionRecord(action, out var result)
&& result.action.bindings.TryGetValue(bindingIndex, out var binding))
{
return binding.bindingPath;
}
return null;
}
private bool TryGetActionRecord(string actionName, out (ActionMap map, ActionMap.Action action) result) private bool TryGetActionRecord(string actionName, out (ActionMap map, ActionMap.Action action) result)
{ {
return actionLookup.TryGetValue(actionName, out result); return actionLookup.TryGetValue(actionName, out result);
@ -781,6 +521,7 @@ public class InputBindingManager : MonoSingleton<InputBindingManager>
return false; return false;
} }
#region Public API
// 为键盘选择最佳绑定索引;如果 compositePartName != null 则查找部分 // 为键盘选择最佳绑定索引;如果 compositePartName != null 则查找部分
/// <summary> /// <summary>
@ -827,18 +568,225 @@ public class InputBindingManager : MonoSingleton<InputBindingManager>
return fallbackNonComposite >= 0 ? fallbackNonComposite : fallbackPart; return fallbackNonComposite >= 0 ? fallbackNonComposite : fallbackPart;
} }
public static InputBindingManager Instance /// <summary>
/// 根据操作名称获取输入操作
/// </summary>
/// <param name="actionName">操作名称</param>
/// <returns>输入操作,未找到则返回 null</returns>
public static InputAction Action(string actionName)
{ {
get var instance= AppServices.Require<InputBindingManager>();
if (instance.TryGetAction(actionName, out InputAction action))
{ {
if (_instance == null) return action;
{ }
_instance = FindObjectOfType<InputBindingManager>();
}
return _instance; if (instance.ambiguousActionNames.Contains(actionName))
{
Log.Error($"[InputBindingManager] Action name '{actionName}' is ambiguous. Use 'MapName/{actionName}' instead.");
return null;
}
Log.Error($"[InputBindingManager] Could not find action '{actionName}'");
return null;
}
public bool TryGetAction(string actionName, out InputAction action)
{
if (string.IsNullOrWhiteSpace(actionName))
{
action = null;
return false;
}
if (actionLookup.TryGetValue(actionName, out var result))
{
action = result.action.action;
return true;
}
action = null;
return false;
}
/// <summary>
/// 开始重新绑定指定的输入操作
/// </summary>
/// <param name="actionName">操作名称</param>
/// <param name="compositePartName">复合部分名称(可选)</param>
public void StartRebind(string actionName, string compositePartName = null)
{
var action = Action(actionName);
if (action == null) return;
// 自动决定 bindingIndex 和 deviceMatch
int bindingIndex = FindBestBindingIndexForKeyboard(action, compositePartName);
if (bindingIndex < 0)
{
Log.Error($"[InputBindingManager] No suitable binding found for action '{actionName}' (part={compositePartName ?? "<null>"})");
return;
}
actions.Disable();
PerformInteractiveRebinding(action, bindingIndex, KEYBOARD_DEVICE, true);
OnRebindStart?.Invoke();
if (debugMode)
{
Log.Info("[InputBindingManager] Rebind started");
} }
} }
private static InputBindingManager _instance; /// <summary>
/// 取消当前的重新绑定操作
/// </summary>
public void CancelRebind() => rebindOperation?.Cancel();
/// <summary>
/// 确认并应用准备好的重新绑定
/// </summary>
/// <param name="clearConflicts">是否清除冲突</param>
/// <returns>是否成功应用</returns>
public async Task<bool> ConfirmApply(bool clearConflicts = true)
{
if (!isApplyPending) return false;
try
{
// 在清除之前创建准备好的重绑定的副本
HashSet<RebindContext> appliedContexts = OnApply != null
? new HashSet<RebindContext>(preparedRebinds)
: null;
foreach (var ctx in preparedRebinds)
{
if (!string.IsNullOrEmpty(ctx.overridePath))
{
if (ctx.overridePath == NULL_BINDING)
{
ctx.action.RemoveBindingOverride(ctx.bindingIndex);
}
else
{
ctx.action.ApplyBindingOverride(ctx.bindingIndex, ctx.overridePath);
}
}
var bp = GetBindingPath(ctx.action, ctx.bindingIndex);
if (bp != null)
{
bp.EffectivePath = (ctx.overridePath == NULL_BINDING) ? string.Empty : ctx.overridePath;
}
}
preparedRebinds.Clear();
await WriteOverridesToDiskAsync();
BindingsChanged?.Invoke();
OnApply?.Invoke(true, appliedContexts);
isApplyPending = false;
if (debugMode)
{
Log.Info("[InputBindingManager] Apply confirmed and saved.");
}
return true;
}
catch (Exception ex)
{
Log.Error("[InputBindingManager] Failed to apply binds: " + ex);
OnApply?.Invoke(false, null);
return false;
}
}
/// <summary>
/// 丢弃准备好的重新绑定
/// </summary>
public void DiscardPrepared()
{
if (!isApplyPending) return;
// 在清除之前创建准备好的重绑定的副本(用于事件通知)
HashSet<RebindContext> discardedContexts = OnApply != null
? new HashSet<RebindContext>(preparedRebinds)
: null;
preparedRebinds.Clear();
isApplyPending = false;
OnApply?.Invoke(false, discardedContexts);
if (debugMode)
{
Log.Info("[InputBindingManager] Prepared rebinds discarded.");
}
}
/// <summary>
/// 重置所有绑定到默认值
/// </summary>
public async Task ResetToDefaultAsync()
{
try
{
if (!string.IsNullOrEmpty(defaultBindingsJson))
{
actions.LoadBindingOverridesFromJson(defaultBindingsJson);
}
else
{
foreach (var map in actionMap.Values)
{
foreach (var a in map.actions.Values)
{
for (int b = 0; b < a.action.bindings.Count; b++)
{
a.action.RemoveBindingOverride(b);
}
}
}
}
RefreshBindingPathsFromActions();
await WriteOverridesToDiskAsync();
BindingsChanged?.Invoke();
if (debugMode)
{
Log.Info("Reset to default and saved.");
}
}
catch (Exception ex)
{
Log.Error("Failed to reset defaults: " + ex);
}
}
/// <summary>
/// 获取指定操作的绑定路径
/// </summary>
/// <param name="actionName">操作名称</param>
/// <param name="bindingIndex">绑定索引</param>
/// <returns>绑定路径,未找到则返回 null</returns>
public BindingPath GetBindingPath(string actionName, int bindingIndex = 0)
{
if (TryGetActionRecord(actionName, out var result)
&& result.action.bindings.TryGetValue(bindingIndex, out var binding))
{
return binding.bindingPath;
}
return null;
}
public BindingPath GetBindingPath(InputAction action, int bindingIndex = 0)
{
if (action == null) return null;
if (TryGetActionRecord(action, out var result)
&& result.action.bindings.TryGetValue(bindingIndex, out var binding))
{
return binding.bindingPath;
}
return null;
}
#endregion
} }

View File

@ -31,20 +31,18 @@ public sealed class InputGlyph : InputGlyphBehaviourBase
public UnityEvent onNotMatched; public UnityEvent onNotMatched;
} }
[Header("Source")] [Header("Source")] [SerializeField] private ActionSourceMode actionSourceMode = ActionSourceMode.ActionReference;
[SerializeField] private ActionSourceMode actionSourceMode = ActionSourceMode.ActionReference;
[SerializeField] private InputActionReference actionReference; [SerializeField] private InputActionReference actionReference;
[SerializeField] private Component hotkeyTrigger; [SerializeField] private Component hotkeyTrigger;
[SerializeField] private string actionName; [SerializeField] private string actionName;
[SerializeField] private string compositePartName; [SerializeField] private string compositePartName;
[Header("Output")] [Header("Output")] [SerializeField] private OutputMode outputMode = OutputMode.Image;
[SerializeField] private OutputMode outputMode = OutputMode.Image;
[SerializeField] private Image targetImage; [SerializeField] private Image targetImage;
[SerializeField] private TMP_Text targetText; [SerializeField] private TMP_Text targetText;
[Header("Platform Events")] [Header("Platform Events")] [SerializeField]
[SerializeField] private List<DeviceCategoryEvent> categoryEvents = new(); private List<DeviceCategoryEvent> categoryEvents = new();
private Sprite _cachedSprite; private Sprite _cachedSprite;
private string _templateText; private string _templateText;
@ -177,7 +175,7 @@ public sealed class InputGlyph : InputGlyphBehaviourBase
case ActionSourceMode.HotkeyTrigger: case ActionSourceMode.HotkeyTrigger:
return ResolveHotkeyAction(); return ResolveHotkeyAction();
case ActionSourceMode.ActionName: case ActionSourceMode.ActionName:
return InputBindingManager.TryGetAction(actionName, out InputAction action) ? action : null; return InputBindingManager.Action(actionName);
default: default:
return null; return null;
} }

View File

@ -1,245 +1,245 @@
using System; // using System;
using System.Collections.Generic; // using System.Collections.Generic;
using System.Threading.Tasks; // using System.Threading.Tasks;
using TMPro; // using TMPro;
using UnityEngine; // using UnityEngine;
using UnityEngine.InputSystem; // using UnityEngine.InputSystem;
using UnityEngine.UI; // using UnityEngine.UI;
//
public class TestRebindScript : MonoBehaviour // public class TestRebindScript : MonoBehaviour
{ // {
[Header("UI")] public UXButton btn; // [Header("UI")] public UXButton btn;
public TextMeshProUGUI bindKeyText; // public TextMeshProUGUI bindKeyText;
public Image targetImage; // public Image targetImage;
//
[Tooltip("如果不使用 actionReference则用 name 在全局 manager 查找")] // [Tooltip("如果不使用 actionReference则用 name 在全局 manager 查找")]
public string actionName = "movement"; // public string actionName = "movement";
//
[Header("Optional composite part (WASD style)")] [Tooltip("如果需要绑定 composite 的某一部分(例如 Up/Down/Left/Right填这个留空表示绑定非 composite 或整体 binding")] // [Header("Optional composite part (WASD style)")] [Tooltip("如果需要绑定 composite 的某一部分(例如 Up/Down/Left/Right填这个留空表示绑定非 composite 或整体 binding")]
public string compositePartName = ""; // public string compositePartName = "";
//
[Header("Behavior")] [Tooltip("如果 true在 Prepare 后自动调用 ConfirmApply() 并保存;否则等待手动 ConfirmPrepared()/CancelPrepared()")] // [Header("Behavior")] [Tooltip("如果 true在 Prepare 后自动调用 ConfirmApply() 并保存;否则等待手动 ConfirmPrepared()/CancelPrepared()")]
public bool autoConfirm = false; // public bool autoConfirm = false;
//
/// <summary> // /// <summary>
/// 启动时初始化并订阅事件 // /// 启动时初始化并订阅事件
/// </summary> // /// </summary>
private void Start() // private void Start()
{ // {
if (btn != null) btn.onClick.AddListener(OnBtnClicked); // if (btn != null) btn.onClick.AddListener(OnBtnClicked);
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged; // InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
InputBindingManager.BindingsChanged += OnBindingsChanged; // InputBindingManager.BindingsChanged += OnBindingsChanged;
UpdateBindingText(); // UpdateBindingText();
//
if (InputBindingManager.Instance != null) // if (InputBindingManager.Instance != null)
{ // {
// 订阅事件 // // 订阅事件
InputBindingManager.Instance.OnRebindPrepare += OnRebindPrepareHandler; // InputBindingManager.Instance.OnRebindPrepare += OnRebindPrepareHandler;
InputBindingManager.Instance.OnApply += OnApplyHandler; // InputBindingManager.Instance.OnApply += OnApplyHandler;
InputBindingManager.Instance.OnRebindEnd += OnRebindEndHandler; // InputBindingManager.Instance.OnRebindEnd += OnRebindEndHandler;
InputBindingManager.Instance.OnRebindConflict += OnRebindConflictHandler; // InputBindingManager.Instance.OnRebindConflict += OnRebindConflictHandler;
} // }
} // }
//
/// <summary> // /// <summary>
/// 禁用时取消订阅事件 // /// 禁用时取消订阅事件
/// </summary> // /// </summary>
private void OnDisable() // private void OnDisable()
{ // {
if (btn != null) btn.onClick.RemoveListener(OnBtnClicked); // if (btn != null) btn.onClick.RemoveListener(OnBtnClicked);
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged; // InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
InputBindingManager.BindingsChanged -= OnBindingsChanged; // InputBindingManager.BindingsChanged -= OnBindingsChanged;
//
if (InputBindingManager.Instance != null) // if (InputBindingManager.Instance != null)
{ // {
InputBindingManager.Instance.OnRebindPrepare -= OnRebindPrepareHandler; // InputBindingManager.Instance.OnRebindPrepare -= OnRebindPrepareHandler;
InputBindingManager.Instance.OnApply -= OnApplyHandler; // InputBindingManager.Instance.OnApply -= OnApplyHandler;
InputBindingManager.Instance.OnRebindEnd -= OnRebindEndHandler; // InputBindingManager.Instance.OnRebindEnd -= OnRebindEndHandler;
InputBindingManager.Instance.OnRebindConflict -= OnRebindConflictHandler; // InputBindingManager.Instance.OnRebindConflict -= OnRebindConflictHandler;
} // }
} // }
//
/// <summary> // /// <summary>
/// 重新绑定准备完成的处理器 // /// 重新绑定准备完成的处理器
/// </summary> // /// </summary>
private void OnRebindPrepareHandler(InputBindingManager.RebindContext ctx) // private void OnRebindPrepareHandler(InputBindingManager.RebindContext ctx)
{ // {
if (IsTargetContext(ctx)) // if (IsTargetContext(ctx))
{ // {
var disp = ctx.overridePath == InputBindingManager.NULL_BINDING ? "<Cleared>" : ctx.overridePath; // var disp = ctx.overridePath == InputBindingManager.NULL_BINDING ? "<Cleared>" : ctx.overridePath;
bindKeyText.text = disp; // bindKeyText.text = disp;
if (autoConfirm) _ = ConfirmPreparedAsync(); // if (autoConfirm) _ = ConfirmPreparedAsync();
} // }
} // }
//
/// <summary> // /// <summary>
/// 应用重新绑定的处理器 // /// 应用重新绑定的处理器
/// </summary> // /// </summary>
private void OnApplyHandler(bool success, HashSet<InputBindingManager.RebindContext> appliedContexts) // private void OnApplyHandler(bool success, HashSet<InputBindingManager.RebindContext> appliedContexts)
{ // {
if (appliedContexts != null) // if (appliedContexts != null)
{ // {
// 仅当任何应用/丢弃的上下文与此实例匹配时才更新 // // 仅当任何应用/丢弃的上下文与此实例匹配时才更新
foreach (var ctx in appliedContexts) // foreach (var ctx in appliedContexts)
{ // {
if (IsTargetContext(ctx)) // if (IsTargetContext(ctx))
{ // {
UpdateBindingText(); // UpdateBindingText();
break; // break;
} // }
} // }
} // }
} // }
//
/// <summary> // /// <summary>
/// 重新绑定结束的处理器 // /// 重新绑定结束的处理器
/// </summary> // /// </summary>
private void OnRebindEndHandler(bool success, InputBindingManager.RebindContext context) // private void OnRebindEndHandler(bool success, InputBindingManager.RebindContext context)
{ // {
if (IsTargetContext(context)) // if (IsTargetContext(context))
{ // {
UpdateBindingText(); // UpdateBindingText();
} // }
} // }
//
/// <summary> // /// <summary>
/// 重新绑定冲突的处理器 // /// 重新绑定冲突的处理器
/// </summary> // /// </summary>
private void OnRebindConflictHandler(InputBindingManager.RebindContext prepared, InputBindingManager.RebindContext conflict) // private void OnRebindConflictHandler(InputBindingManager.RebindContext prepared, InputBindingManager.RebindContext conflict)
{ // {
// 如果准备的或冲突的上下文匹配此实例,则更新 // // 如果准备的或冲突的上下文匹配此实例,则更新
if (IsTargetContext(prepared) || IsTargetContext(conflict)) // if (IsTargetContext(prepared) || IsTargetContext(conflict))
{ // {
UpdateBindingText(); // UpdateBindingText();
} // }
} // }
//
/// <summary> // /// <summary>
/// 设备变更的回调 // /// 设备变更的回调
/// </summary> // /// </summary>
private void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory _) // private void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory _)
{ // {
UpdateBindingText(); // UpdateBindingText();
} // }
//
private void OnBindingsChanged() // private void OnBindingsChanged()
{ // {
UpdateBindingText(); // UpdateBindingText();
} // }
//
/// <summary> // /// <summary>
/// 获取当前的输入操作 // /// 获取当前的输入操作
/// </summary> // /// </summary>
private InputAction GetAction() // private InputAction GetAction()
{ // {
return InputBindingManager.Action(actionName); // return InputBindingManager.Action(actionName);
} // }
//
/// <summary> // /// <summary>
/// 判断上下文是否为目标上下文 // /// 判断上下文是否为目标上下文
/// </summary> // /// </summary>
private bool IsTargetContext(InputBindingManager.RebindContext ctx) // private bool IsTargetContext(InputBindingManager.RebindContext ctx)
{ // {
if (ctx == null || ctx.action == null) return false; // if (ctx == null || ctx.action == null) return false;
var action = GetAction(); // var action = GetAction();
if (action == null) return false; // if (action == null) return false;
//
// 必须匹配操作 // // 必须匹配操作
if (ctx.action != action) return false; // if (ctx.action != action) return false;
//
// 如果指定了复合部分,需要匹配绑定索引 // // 如果指定了复合部分,需要匹配绑定索引
if (!string.IsNullOrEmpty(compositePartName)) // if (!string.IsNullOrEmpty(compositePartName))
{ // {
// 获取上下文索引处的绑定 // // 获取上下文索引处的绑定
if (ctx.bindingIndex < 0 || ctx.bindingIndex >= action.bindings.Count) // if (ctx.bindingIndex < 0 || ctx.bindingIndex >= action.bindings.Count)
return false; // return false;
//
var binding = action.bindings[ctx.bindingIndex]; // var binding = action.bindings[ctx.bindingIndex];
//
// 检查绑定的名称是否与我们的复合部分匹配 // // 检查绑定的名称是否与我们的复合部分匹配
return string.Equals(binding.name, compositePartName, StringComparison.OrdinalIgnoreCase); // return string.Equals(binding.name, compositePartName, StringComparison.OrdinalIgnoreCase);
} // }
//
// 如果未指定复合部分,仅匹配操作就足够了 // // 如果未指定复合部分,仅匹配操作就足够了
return true; // return true;
} // }
//
/// <summary> // /// <summary>
/// 按钮点击的回调 // /// 按钮点击的回调
/// </summary> // /// </summary>
private void OnBtnClicked() // private void OnBtnClicked()
{ // {
// 使用管理器 API我们传递部分名称以便管理器可以在需要时选择适当的绑定 // // 使用管理器 API我们传递部分名称以便管理器可以在需要时选择适当的绑定
InputBindingManager.StartRebind(actionName, string.IsNullOrEmpty(compositePartName) ? null : compositePartName); // InputBindingManager.StartRebind(actionName, string.IsNullOrEmpty(compositePartName) ? null : compositePartName);
} // }
//
/// <summary> // /// <summary>
/// 确认准备好的重新绑定(公共方法) // /// 确认准备好的重新绑定(公共方法)
/// </summary> // /// </summary>
public async void ConfirmPrepared() // public async void ConfirmPrepared()
{ // {
bool ok = await ConfirmPreparedAsync(); // bool ok = await ConfirmPreparedAsync();
if (!ok) Debug.LogError("ConfirmPrepared: apply failed."); // if (!ok) Debug.LogError("ConfirmPrepared: apply failed.");
} // }
//
/// <summary> // /// <summary>
/// 确认准备好的重新绑定(异步) // /// 确认准备好的重新绑定(异步)
/// </summary> // /// </summary>
private async Task<bool> ConfirmPreparedAsync() // private async Task<bool> ConfirmPreparedAsync()
{ // {
try // try
{ // {
var task = InputBindingManager.ConfirmApply(); // var task = InputBindingManager.ConfirmApply();
return await task; // return await task;
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
Debug.LogError(ex); // Debug.LogError(ex);
return false; // return false;
} // }
} // }
//
/// <summary> // /// <summary>
/// 取消准备好的重新绑定 // /// 取消准备好的重新绑定
/// </summary> // /// </summary>
public void CancelPrepared() // public void CancelPrepared()
{ // {
InputBindingManager.DiscardPrepared(); // InputBindingManager.DiscardPrepared();
// UpdateBindingText 将通过 OnApply 事件自动调用 // // UpdateBindingText 将通过 OnApply 事件自动调用
} // }
//
/// <summary> // /// <summary>
/// 更新绑定文本和图标显示 // /// 更新绑定文本和图标显示
/// </summary> // /// </summary>
private void UpdateBindingText() // private void UpdateBindingText()
{ // {
var action = GetAction(); // var action = GetAction();
var deviceCat = InputDeviceWatcher.CurrentCategory; // var deviceCat = InputDeviceWatcher.CurrentCategory;
if (action == null) // if (action == null)
{ // {
bindKeyText.text = "<no action>"; // bindKeyText.text = "<no action>";
if (targetImage != null) targetImage.sprite = null; // if (targetImage != null) targetImage.sprite = null;
return; // return;
} // }
//
//
bindKeyText.text = GlyphService.GetDisplayNameFromInputAction(action, compositePartName, deviceCat); // bindKeyText.text = GlyphService.GetDisplayNameFromInputAction(action, compositePartName, deviceCat);
//
//
try // try
{ // {
if (GlyphService.TryGetUISpriteForActionPath(action, compositePartName, deviceCat, out Sprite sprite)) // if (GlyphService.TryGetUISpriteForActionPath(action, compositePartName, deviceCat, out Sprite sprite))
{ // {
if (targetImage != null) targetImage.sprite = sprite; // if (targetImage != null) targetImage.sprite = sprite;
} // }
else // else
{ // {
if (targetImage != null) targetImage.sprite = null; // if (targetImage != null) targetImage.sprite = null;
} // }
} // }
catch // catch
{ // {
if (targetImage != null) targetImage.sprite = null; // if (targetImage != null) targetImage.sprite = null;
} // }
} // }
} // }

View File

@ -477,7 +477,11 @@ namespace UnityEngine.UI
foreach (var scope in _scopes.Values) foreach (var scope in _scopes.Values)
{ {
if (!IsScopeActive(scope) || !UXNavigationRuntime.IsHolderWithinTopScope(scope.Holder)) if (!IsScopeActive(scope)
#if UX_NAVIGATION
|| !UXNavigationRuntime.IsHolderWithinTopScope(scope.Holder)
#endif
)
{ {
continue; continue;
} }
@ -493,7 +497,9 @@ namespace UnityEngine.UI
foreach (var scope in _scopes.Values) foreach (var scope in _scopes.Values)
{ {
if (IsScopeActive(scope) if (IsScopeActive(scope)
#if UX_NAVIGATION
&& UXNavigationRuntime.IsHolderWithinTopScope(scope.Holder) && UXNavigationRuntime.IsHolderWithinTopScope(scope.Holder)
#endif
&& !_ancestorHolders.Contains(scope.Holder)) && !_ancestorHolders.Contains(scope.Holder))
{ {
_leafScopes.Add(scope); _leafScopes.Add(scope);

View File

@ -1,4 +1,4 @@
#if INPUTSYSTEM_SUPPORT #if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
namespace UnityEngine.UI namespace UnityEngine.UI
{ {
public enum UXInputMode : byte public enum UXInputMode : byte

View File

@ -1,4 +1,4 @@
#if INPUTSYSTEM_SUPPORT #if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
using System; using System;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Controls;

View File

@ -1,4 +1,4 @@
#if INPUTSYSTEM_SUPPORT #if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
namespace UnityEngine.UI namespace UnityEngine.UI
{ {
[DisallowMultipleComponent] [DisallowMultipleComponent]

View File

@ -1,4 +1,4 @@
#if INPUTSYSTEM_SUPPORT #if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
using System.Collections.Generic; using System.Collections.Generic;
using AlicizaX; using AlicizaX;
using AlicizaX.UI.Runtime; using AlicizaX.UI.Runtime;

View File

@ -1,4 +1,4 @@
#if INPUTSYSTEM_SUPPORT #if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
using System.Collections.Generic; using System.Collections.Generic;
using AlicizaX.UI.Runtime; using AlicizaX.UI.Runtime;

View File

@ -1,4 +1,4 @@
#if INPUTSYSTEM_SUPPORT #if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
namespace UnityEngine.UI namespace UnityEngine.UI
{ {
[DisallowMultipleComponent] [DisallowMultipleComponent]