// Copyright (c) 2025-2026 GeWuYou // SPDX-License-Identifier: Apache-2.0 using GFramework.Game.Abstractions.Input; using GFramework.Game.Input; namespace GFramework.Godot.Input; /// /// 提供基于 Godot `InputMap` 的输入绑定存储实现。 /// /// /// 该类型把 Godot 原生 `InputEvent` / `InputMap` 适配到 `GFramework.Game.Abstractions.Input` 契约。 /// 项目可以直接用它做重绑定、动作快照导出导入,以及“当前活跃设备”识别。 /// public sealed class GodotInputBindingStore : IInputBindingStore, IInputDeviceTracker { private readonly IGodotInputMapBackend _backend; private readonly InputBindingStore _state; private readonly InputDeviceTracker _deviceTracker; /// /// 初始化一个基于全局 `InputMap` 的输入绑定存储。 /// public GodotInputBindingStore() : this(new GodotInputMapBackend()) { } /// /// 初始化一个可测试的输入绑定存储。 /// /// 要使用的 `InputMap` 后端。 internal GodotInputBindingStore(IGodotInputMapBackend backend) { _backend = backend ?? throw new ArgumentNullException(nameof(backend)); _state = new InputBindingStore(CaptureSnapshotFromBackend()); _deviceTracker = new InputDeviceTracker(); } /// public InputDeviceContext CurrentDevice => _deviceTracker.CurrentDevice; /// public InputActionBinding GetBindings(string actionName) { ReloadFromBackend(); return _state.GetBindings(actionName); } /// public InputBindingSnapshot ExportSnapshot() { ReloadFromBackend(); return _state.ExportSnapshot(); } /// public void ImportSnapshot(InputBindingSnapshot snapshot) { ArgumentNullException.ThrowIfNull(snapshot); ReloadFromBackend(); foreach (var action in snapshot.Actions) { ApplyActionBindings(action); } } /// public void SetPrimaryBinding(string actionName, InputBindingDescriptor binding, bool swapIfTaken = true) { ReloadFromBackend(); _state.SetPrimaryBinding(actionName, binding, swapIfTaken); ApplySnapshot(_state.ExportSnapshot()); } /// public void ResetAction(string actionName) { _backend.ResetAction(actionName); ReloadFromBackend(); } /// public void ResetAll() { _backend.ResetAll(); ReloadFromBackend(); } /// /// 使用 Godot 原生输入事件更新当前活跃设备上下文。 /// /// 原生输入事件。 public void UpdateDeviceFromInput(InputEvent inputEvent) { var context = GodotInputBindingCodec.GetDeviceContext(inputEvent); _deviceTracker.Update(context); } private void ApplyActionBindings(InputActionBinding actionBinding) { _backend.SetBindings(actionBinding.ActionName, actionBinding.Bindings); } private void ApplySnapshot(InputBindingSnapshot snapshot) { foreach (var actionBinding in snapshot.Actions) { ApplyActionBindings(actionBinding); } } private InputBindingSnapshot CaptureSnapshotFromBackend() { var actions = _backend.GetActionNames() .Select(CreateActionBinding) .ToArray(); return new InputBindingSnapshot(actions); } private InputActionBinding CreateActionBinding(string actionName) { return new InputActionBinding(actionName, _backend.GetBindings(actionName).ToArray()); } private void ReloadFromBackend() { _state.ImportSnapshot(CaptureSnapshotFromBackend()); } }