HotkeySettings.razor

Razor panel for the game's hotkey rebinding screen. Lists hotkey definitions grouped by category, shows current and default bindings, and lets the player start a capture, clear one binding, restore one action, or reset all to defaults, with a two-step confirm on the reset and localized Russian/English text.

@using Sandbox
@using Sandbox.UI
@using Socer
@using System
@using System.Linq
@inherits Panel

<div class="hotkey-settings localized-text @GamePreferences.TextSizeClass">
    <div class="hotkey-intro">
        <label class="hotkey-title">@T("Управление под вас","Your controls")</label>
        <label class="hotkey-help">@T("Выберите действие и нажмите новую клавишу или сочетание. Изменения сохраняются сразу.","Choose an action, then press a new key or key combination. Changes are saved immediately.")</label>
    </div>
    <div class="hotkey-guide">
        <label>@T("Сочетания: Ctrl, Alt или Shift + клавиша.","Combinations: Ctrl, Alt or Shift + a key.")</label>
        <label>@T("Во время назначения: Esc — отменить, Delete — убрать привязку.","While assigning: Esc cancels, Delete removes the binding.")</label>
        <label>@T("Занятые сочетания не перезаписываются. Сначала освободите нужную клавишу.","Conflicting bindings are kept. Clear the occupied key before reusing it.")</label>
    </div>

    @if(GameHotkeys.Capturing)
    {
        <div class="hotkey-capture-banner">
            <div><label>@T("Нажмите клавишу…","Press a key…")</label><small>@CapturingLabel()</small></div>
            <button onclick=@Cancel>@T("Отменить","Cancel")</button>
        </div>
    }
    @if(!string.IsNullOrEmpty(ActionMessage))
    {
        <label class="hotkey-message @(ActionFailed?"warning":"")">@ActionMessage</label>
    }
    else if(!string.IsNullOrEmpty(GameHotkeys.CaptureMessage))
    {
        <label class="hotkey-message @(GameHotkeys.Capturing?"warning":"")">@GameHotkeys.CaptureMessage</label>
    }

    <div class="hotkey-reset-bar">
        <label class="hotkey-help">@T("Можно убрать любую привязку. Действия в меню останутся доступны мышью.","Any binding can be removed. Menu actions remain available with the mouse.")</label>
        <button class="hotkey-reset-all" onclick=@ArmReset>@T("Сбросить все","Reset all")</button>
    </div>
    @if(ResetArmed)
    {
        <div class="hotkey-reset-confirm">
            <label>@T("Вернуть исходные клавиши для всех действий? Ваши сочетания будут заменены.","Restore the default keys for every action? This replaces your custom bindings.")</label>
            <div><button class="confirm" onclick=@ResetAll>@T("Да, сбросить","Restore defaults")</button><button onclick=@(()=>{ResetArmed=false;GameAudio.Tap();})>@T("Отмена","Cancel")</button></div>
        </div>
    }

    @foreach(var group in GameHotkeys.Definitions.GroupBy(d=>d.Group(GamePreferences.Russian)))
    {
        <div class="hotkey-group">
            <div class="hotkey-group-heading"><label>@group.Key</label><small>@group.Count()</small></div>
            @foreach(var definition in group)
            {
                var action=definition;
                bool listening=GameHotkeys.Capturing && GameHotkeys.CapturingId==action.Id;
                bool unbound=string.IsNullOrEmpty(GameHotkeys.Binding(action.Id));
                <div class="hotkey-row @(listening?"listening":"")">
                    <div class="hotkey-row-copy"><label class="hotkey-action">@action.Label(GamePreferences.Russian)</label><small>@T("По умолчанию: ","Default: ")@GameHotkeys.DisplayBinding(action.DefaultBinding)</small></div>
                    <div class="hotkey-row-controls">
                        <button class="hotkey-binding @(unbound?"unbound":"")" title="@T("Назначить клавишу или сочетание","Assign a key or combination")" onclick=@(()=>Begin(action.Id))>
                            <label>@(listening?T("Нажмите клавишу…","Press a key…"):GameHotkeys.Display(action.Id))</label><small>@(listening?"Esc":unbound?T("Назначить","Assign"):T("Изменить","Change"))</small>
                        </button>
                        <button class="hotkey-clear" disabled=@unbound title="@T("Убрать эту привязку","Remove this binding")" onclick=@(()=>Clear(action.Id))>@T("Убрать","Clear")</button>
                        <button class="hotkey-default" disabled="@(string.Equals(GameHotkeys.Binding(action.Id),action.DefaultBinding,StringComparison.OrdinalIgnoreCase))" title="@T("Вернуть исходную клавишу для этого действия","Restore this action's default key")" onclick=@(()=>ResetOne(action.Id,action.DefaultBinding))>@T("По умолчанию","Default")</button>
                    </div>
                    @if(listening)
                    {
                        <label class="hotkey-row-status">@(string.IsNullOrEmpty(GameHotkeys.CaptureMessage)?T("Esc — отменить · Delete — убрать привязку","Esc cancels · Delete removes the binding"):GameHotkeys.CaptureMessage)</label>
                    }
                    else if(ActionFailed && ActionId==action.Id)
                    {
                        <label class="hotkey-row-status">@ActionMessage</label>
                    }
                </div>
            }
        </div>
    }
</div>

@code {
    bool ResetArmed,ActionFailed;
    string ActionMessage="",ActionId="";
    static string T(string ru,string en)=>GamePreferences.Russian?ru:en;
    string CapturingLabel(){foreach(var definition in GameHotkeys.Definitions)if(definition.Id==GameHotkeys.CapturingId)return definition.Label(GamePreferences.Russian);return "";}
    void Begin(string id){ResetArmed=false;ActionMessage=ActionId="";InputFocus.Clear();GameHotkeys.BeginCapture(id);GameAudio.Tap();}
    void Cancel(){ActionMessage=ActionId="";GameHotkeys.CancelCapture();GameAudio.Tap();}
    void Clear(string id){ResetArmed=false;ActionMessage=ActionId="";GameHotkeys.CancelCapture();GameHotkeys.ClearBinding(id);GameAudio.Tap();}
    void ResetOne(string id,string binding)
    {
        ResetArmed=false;ActionId=id;GameHotkeys.CancelCapture();
        ActionMessage=GameHotkeys.Bind(id,binding);ActionFailed=!string.IsNullOrEmpty(ActionMessage);GameAudio.Tap();
    }
    void ArmReset(){GameHotkeys.CancelCapture();ActionMessage=ActionId="";ResetArmed=true;GameAudio.Tap();}
    void ResetAll()
    {
        GameHotkeys.CancelCapture();GameHotkeys.ResetDefaults();ResetArmed=false;ActionFailed=false;ActionId="";
        ActionMessage="";GameAudio.Tap();
    }
    protected override int BuildHash()=>HashCode.Combine(GamePreferences.Revision,GameHotkeys.Revision,GameHotkeys.Capturing,
        GameHotkeys.CapturingId,GameHotkeys.CaptureMessage,ResetArmed,HashCode.Combine(ActionMessage,ActionId,ActionFailed));
}