(Feat) Add Remmaping
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Ashwild.Player;
|
||||
|
||||
namespace Ashwild.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings sub-panel that lists every rebindable binding from the shared InputConfig as rows and
|
||||
/// drives interactive rebinds. It is the orchestrator: the InputManager performs the rebind on the
|
||||
/// asset, the SettingsManager persists it, and this panel refreshes the rows. It also owns the
|
||||
/// modal rebind prompt (a "press a key" overlay that becomes a "X already uses this key →
|
||||
/// Replace / Cancel" conflict prompt) — the prompt objects live under the SettingsPanel so they
|
||||
/// render in front of everything. Rows show layout-aware glyphs, so AZERTY reads correctly.
|
||||
/// </summary>
|
||||
public class KeybindingsSettingsSubPanel : SettingsSubPanel
|
||||
{
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("Row List")]
|
||||
[SerializeField] private Transform rowContainer;
|
||||
[SerializeField] private GameObject rowPrefab;
|
||||
[SerializeField] private Button resetAllButton;
|
||||
|
||||
[Header("Rebind Prompt")]
|
||||
[Tooltip("Standalone prompt overlay — its own object under the SettingsPanel, not a child here.")]
|
||||
[SerializeField] private RebindPromptUI prompt;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
private readonly List<KeybindRowUI> rows = new List<KeybindRowUI>();
|
||||
private bool built;
|
||||
|
||||
/// <summary>
|
||||
/// The shared input config, pulled from the SettingsManager (assigned there, not here).
|
||||
/// </summary>
|
||||
private InputConfig config;
|
||||
|
||||
/// <summary>
|
||||
/// The conflict awaiting the player's Replace/Cancel choice, kept so the buttons can resolve it.
|
||||
/// </summary>
|
||||
private InputManager.RebindOutcome pendingOutcome;
|
||||
private bool hasPendingConflict;
|
||||
private KeybindRowUI activeRow;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Wires the reset-all button and the prompt's Replace/Cancel buttons.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
if (resetAllButton != null) resetAllButton.onClick.AddListener(ResetAll);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aborts any rebind/conflict in flight when the panel is hidden (category switch / closed).
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
if (InputManager.Instance != null) InputManager.Instance.CancelOngoingRebind();
|
||||
if (hasPendingConflict) OnConflictCancel();
|
||||
HidePrompt();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Builds the rows once, then repaints every current key (so reopening shows live bindings).
|
||||
/// </summary>
|
||||
public override void Refresh()
|
||||
{
|
||||
config = SettingsManager.Instance != null ? SettingsManager.Instance.InputConfig : null;
|
||||
if (config == null)
|
||||
{
|
||||
Debug.LogError("[KeybindingsSettingsSubPanel] No InputConfig available from SettingsManager.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
HidePrompt();
|
||||
if (!built) BuildRows();
|
||||
RefreshAll();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Row Building
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates one row per config binding and binds its callbacks.
|
||||
/// </summary>
|
||||
private void BuildRows()
|
||||
{
|
||||
if (rowPrefab == null || rowContainer == null)
|
||||
{
|
||||
Debug.LogError($"[KeybindingsSettingsSubPanel] Row prefab/container missing on '{name}'.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (RebindableEntry entry in config.Bindings)
|
||||
{
|
||||
GameObject go = Instantiate(rowPrefab, rowContainer);
|
||||
KeybindRowUI row = go.GetComponent<KeybindRowUI>();
|
||||
if (row == null)
|
||||
{
|
||||
Debug.LogError("[KeybindingsSettingsSubPanel] Row prefab has no KeybindRowUI.", this);
|
||||
Destroy(go);
|
||||
continue;
|
||||
}
|
||||
row.Initialize(entry, OnRowRebind, OnRowReset);
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
built = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Row Callbacks
|
||||
|
||||
/// <summary>
|
||||
/// Opens the prompt and starts capturing a new key for the clicked row.
|
||||
/// </summary>
|
||||
private void OnRowRebind(KeybindRowUI row)
|
||||
{
|
||||
if (InputManager.Instance == null) return;
|
||||
|
||||
activeRow = row;
|
||||
ShowListening();
|
||||
InputManager.Instance.StartInteractiveRebind(row.ActionName, row.BindingIndex, OnRebindOutcome);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the capture result: persist + refresh on success, raise the conflict prompt on a
|
||||
/// clash, or just close on cancel.
|
||||
/// </summary>
|
||||
private void OnRebindOutcome(InputManager.RebindOutcome outcome)
|
||||
{
|
||||
switch (outcome.Result)
|
||||
{
|
||||
case InputManager.RebindResult.Completed:
|
||||
HidePrompt();
|
||||
Persist();
|
||||
RefreshAll();
|
||||
break;
|
||||
case InputManager.RebindResult.Conflict:
|
||||
pendingOutcome = outcome;
|
||||
hasPendingConflict = true;
|
||||
ShowConflict(LabelFor(outcome.ConflictActionName));
|
||||
break;
|
||||
default:
|
||||
HidePrompt();
|
||||
if (activeRow != null) RefreshRow(activeRow);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a conflict by swapping the two keys (the other action takes this one's old key).
|
||||
/// </summary>
|
||||
private void OnConflictReplace()
|
||||
{
|
||||
if (!hasPendingConflict) return;
|
||||
|
||||
if (InputManager.Instance != null) InputManager.Instance.ApplySwap(pendingOutcome);
|
||||
ClearConflict();
|
||||
HidePrompt();
|
||||
Persist();
|
||||
RefreshAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a conflict by undoing the new binding (the other action keeps its key).
|
||||
/// </summary>
|
||||
private void OnConflictCancel()
|
||||
{
|
||||
if (!hasPendingConflict) return;
|
||||
|
||||
if (InputManager.Instance != null) InputManager.Instance.RevertRebind(pendingOutcome);
|
||||
ClearConflict();
|
||||
HidePrompt();
|
||||
RefreshAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the clicked row's binding to default and persists.
|
||||
/// </summary>
|
||||
private void OnRowReset(KeybindRowUI row)
|
||||
{
|
||||
if (InputManager.Instance == null) return;
|
||||
|
||||
InputManager.Instance.ResetBinding(row.ActionName, row.BindingIndex);
|
||||
Persist();
|
||||
RefreshRow(row);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets every binding to default and persists.
|
||||
/// </summary>
|
||||
private void ResetAll()
|
||||
{
|
||||
if (InputManager.Instance == null) return;
|
||||
|
||||
InputManager.Instance.ResetAllBindings();
|
||||
Persist();
|
||||
RefreshAll();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Prompt
|
||||
|
||||
/// <summary>
|
||||
/// Shows the prompt in "press a key" mode.
|
||||
/// </summary>
|
||||
private void ShowListening()
|
||||
{
|
||||
if (prompt != null) prompt.ShowListening();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches the open prompt to the conflict state for the named action, wiring its buttons.
|
||||
/// </summary>
|
||||
private void ShowConflict(string conflictLabel)
|
||||
{
|
||||
if (prompt != null) prompt.ShowConflict(conflictLabel, OnConflictReplace, OnConflictCancel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the prompt entirely.
|
||||
/// </summary>
|
||||
private void HidePrompt()
|
||||
{
|
||||
if (prompt != null) prompt.Hide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the pending-conflict state.
|
||||
/// </summary>
|
||||
private void ClearConflict()
|
||||
{
|
||||
hasPendingConflict = false;
|
||||
pendingOutcome = default;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Refresh
|
||||
|
||||
/// <summary>
|
||||
/// Persists the current bindings through the SettingsManager.
|
||||
/// </summary>
|
||||
private void Persist()
|
||||
{
|
||||
if (SettingsManager.Instance != null) SettingsManager.Instance.SaveKeybinds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repaints the current key on every row.
|
||||
/// </summary>
|
||||
private void RefreshAll()
|
||||
{
|
||||
foreach (KeybindRowUI row in rows) RefreshRow(row);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repaints one row's current key, choosing a glyph from the config when available.
|
||||
/// </summary>
|
||||
private void RefreshRow(KeybindRowUI row)
|
||||
{
|
||||
if (InputManager.Instance == null) return;
|
||||
|
||||
string display = InputManager.Instance.GetBindingDisplayString(row.ActionName, row.BindingIndex);
|
||||
Sprite icon = config != null ? config.GetIcon(display) : null;
|
||||
if (icon == null && config != null)
|
||||
{
|
||||
string controlPath = InputManager.Instance.GetBindingControlPath(row.ActionName, row.BindingIndex);
|
||||
icon = config.GetIcon(controlPath);
|
||||
}
|
||||
row.RefreshDisplay(display, icon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The display label authored for an action (first matching binding), or the raw name.
|
||||
/// </summary>
|
||||
private string LabelFor(string actionName)
|
||||
{
|
||||
if (config != null)
|
||||
foreach (RebindableEntry entry in config.Bindings)
|
||||
if (entry.ActionName == actionName)
|
||||
return entry.DisplayLabel;
|
||||
return actionName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user