// 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();
var snapshotActionNames = snapshot.Actions
.Select(static action => action.ActionName)
.ToHashSet(StringComparer.Ordinal);
var removedActionNames = _state.ExportSnapshot().Actions
.Select(static action => action.ActionName)
.Where(actionName => !snapshotActionNames.Contains(actionName))
.ToArray();
foreach (var actionName in removedActionNames)
{
_backend.SetBindings(actionName, Array.Empty());
}
foreach (var action in snapshot.Actions)
{
ApplyActionBindings(action);
}
ReloadFromBackend();
}
///
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());
}
}