Files
crystall-punk-14/Content.Client/EscapeMenu/UI/OptionsMenu.KeyRebind.cs

502 lines
19 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
2021-06-09 22:19:39 +02:00
using Content.Client.HUD.UI;
using Content.Client.Stylesheets;
using Content.Shared.Input;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
2021-02-21 12:38:56 +01:00
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
2021-06-09 22:19:39 +02:00
namespace Content.Client.EscapeMenu.UI
{
public sealed partial class OptionsMenu
{
private sealed class KeyRebindControl : Control
{
// List of key functions that must be registered as toggle instead.
private static readonly HashSet<BoundKeyFunction> ToggleFunctions = new()
{
EngineKeyFunctions.ShowDebugMonitors,
EngineKeyFunctions.HideUI,
};
[Dependency] private readonly IInputManager _inputManager = default!;
private BindButton? _currentlyRebinding;
private readonly Dictionary<BoundKeyFunction, KeyControl> _keyControls =
new();
private readonly List<Action> _deferCommands = new();
public KeyRebindControl()
{
IoCManager.InjectDependencies(this);
Button resetAllButton;
var vBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Margin = new Thickness(2, 0, 0, 0)
};
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
new ScrollContainer
{
2021-02-21 12:38:56 +01:00
VerticalExpand = true,
Children = {vBox}
},
new StripeBack
{
HasBottomEdge = false,
HasMargins = false,
Children =
{
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
2021-02-21 12:38:56 +01:00
new Control {MinSize = (2, 0)},
new Label
{
StyleClasses = {StyleBase.StyleClassLabelSubText},
2021-02-25 21:08:06 +01:00
Text = Loc.GetString("ui-options-binds-explanation")
},
(resetAllButton = new Button
{
2021-02-25 21:08:06 +01:00
Text = Loc.GetString("ui-options-binds-reset-all"),
StyleClasses = {StyleBase.ButtonCaution},
HorizontalExpand = true,
2021-02-21 12:38:56 +01:00
HorizontalAlignment = HAlignment.Right
})
}
}
}
}
}
});
2021-02-21 12:38:56 +01:00
resetAllButton.OnPressed += _ =>
{
_deferCommands.Add(() =>
{
_inputManager.ResetAllBindings();
_inputManager.SaveToUserData();
});
};
var first = true;
void AddHeader(string headerContents)
{
if (!first)
{
2021-02-21 12:38:56 +01:00
vBox.AddChild(new Control {MinSize = (0, 8)});
}
first = false;
vBox.AddChild(new Label
{
2021-02-25 21:08:06 +01:00
Text = Loc.GetString(headerContents),
FontColorOverride = StyleNano.NanoGold,
StyleClasses = {StyleNano.StyleClassLabelKeyText}
});
}
2021-02-25 21:08:06 +01:00
void AddButton(BoundKeyFunction function)
{
2021-02-25 21:08:06 +01:00
var control = new KeyControl(this, function);
vBox.AddChild(control);
_keyControls.Add(function, control);
}
2021-02-25 21:08:06 +01:00
AddHeader("ui-options-header-movement");
AddButton(EngineKeyFunctions.MoveUp);
AddButton(EngineKeyFunctions.MoveLeft);
AddButton(EngineKeyFunctions.MoveDown);
AddButton(EngineKeyFunctions.MoveRight);
AddButton(EngineKeyFunctions.Walk);
AddHeader("ui-options-header-interaction-basic");
AddButton(EngineKeyFunctions.Use);
AddButton(ContentKeyFunctions.WideAttack);
AddButton(ContentKeyFunctions.ActivateItemInHand);
AddButton(ContentKeyFunctions.ActivateItemInWorld);
AddButton(ContentKeyFunctions.Drop);
AddButton(ContentKeyFunctions.ExamineEntity);
AddButton(ContentKeyFunctions.SwapHands);
AddHeader("ui-options-header-interaction-adv");
AddButton(ContentKeyFunctions.SmartEquipBackpack);
AddButton(ContentKeyFunctions.SmartEquipBelt);
AddButton(ContentKeyFunctions.ThrowItemInHand);
AddButton(ContentKeyFunctions.TryPullObject);
AddButton(ContentKeyFunctions.MovePulledObject);
AddButton(ContentKeyFunctions.ReleasePulledObject);
AddButton(ContentKeyFunctions.Point);
AddHeader("ui-options-header-ui");
AddButton(ContentKeyFunctions.FocusChat);
Chairbender Chat (#3794) * #272 restructure and restyle chat line edit section * #272 no arrow, actually change id on channel changer * #272 nice round chat channel picker * #272 add chat channel selection logic, and auto-select when a prefix is entered * #272 consistent width of chat channel btn * #272 only show admin channel filter if asay perms * #272 add tutorial info on chat prefixes * #272 added chat filter button * #272 added chat filter button * #272 WIP on filter popup * #272 fix filter popup pressed / unpressed logic * #272 fix filter popup positioning and layout * #272 WIP channel filter logic * #272 WIP channel filter logic * #272 WIP refactoring how chatbox / manager manages available filters and channels to send on * #272 WIP implementing filtering UI / logic and refactoring how chat UI is managed * #272 fix various bugs with new chat filter / selector logic * #272 remove outdated todos * #272 WIP working chat window resize * #272 bounded chatbox resizing * #272 alertUI moves with resized chat * #272 WIP making alertUI not be too large when changing size / UIScale * #272 WIP fixing window / uiscale adjustment * #272 WIP hacky approach for resizing, will try another approach * #272 implement hacky approach for bounded chat resize * #272 no resizing of lobby chat * #272 WIP adding unread marker to chat filters * #272 basic working unread chat message indicators * #272 WIP adding horizontal channel selector items * #272 horizontal channel selector popup * #272 workaround for chat selector staying highlighted when right clicking it while toggled * #272 workaround for chat selector staying highlighted when right clicking it while toggled * #272 wip trying to add tests for chatbox * #272 remove test, not really possible with current system * #272 merge latest * #272 merge latest * #272 fix csproj changes * It works if you disable the lobby * Fixes lobby chat * Adds more channel focusses * Channel cycler * Address review * Address nitpicks * Address more of the review * Fix chat post-viewport * Finalize review stuff Co-authored-by: chairbender <kwhipke1@gmail.com> Co-authored-by: ike709 <sparebytes@protonmail.com>
2021-04-20 18:39:39 -05:00
AddButton(ContentKeyFunctions.FocusLocalChat);
AddButton(ContentKeyFunctions.FocusRadio);
2021-02-25 21:08:06 +01:00
AddButton(ContentKeyFunctions.FocusOOC);
AddButton(ContentKeyFunctions.FocusAdminChat);
Chat improvements. (#4283) * UI is an abbreviation, in XAML. * Chat improvements. Changing the "selected" channel on the chat box is now only done via the tab cycle or clicking the button. Prefix chars like [ will temporarily replace the active chat channel. This is based 100% on message box contents so there's no input eating garbage or anything. Pressing specific channel focusing keys inserts the correct prefix character, potentially replacing an existing one. Existing chat contents are left in place just fine and selected so you can easily delete them (but are not forced to). Channel focusing keys now match the QWERTY key codes. Deadchat works now. Console can no longer be selected as a chat channel, but you can still use it with the / prefix. Refactored the connection between chat manager and chat box so that it's event based, reducing tons of spaghetti everywhere. Main chat box control uses XAML now. General cleanup. Added focus hotkeys for deadchat/console. Also added prefix for deadchat. Local chat is mapped to deadchat when a ghost. Probably more stuff I can't think of right now. * Add preferred channel system to chat box to automatically select local. I can't actually test this works because the non-lobby chat box code is complete disastrous spaghetti and i need to refactor it. * Move chatbox resizing and all that to a subclass. Refine preferred channel & deadchat mapping code further. * Don't do prefixes for channels you don't have access to. * Change format on channel select popup. * Clean up code with console handling somewhat.
2021-07-20 10:29:09 +02:00
AddButton(ContentKeyFunctions.FocusDeadChat);
AddButton(ContentKeyFunctions.FocusConsoleChat);
Chairbender Chat (#3794) * #272 restructure and restyle chat line edit section * #272 no arrow, actually change id on channel changer * #272 nice round chat channel picker * #272 add chat channel selection logic, and auto-select when a prefix is entered * #272 consistent width of chat channel btn * #272 only show admin channel filter if asay perms * #272 add tutorial info on chat prefixes * #272 added chat filter button * #272 added chat filter button * #272 WIP on filter popup * #272 fix filter popup pressed / unpressed logic * #272 fix filter popup positioning and layout * #272 WIP channel filter logic * #272 WIP channel filter logic * #272 WIP refactoring how chatbox / manager manages available filters and channels to send on * #272 WIP implementing filtering UI / logic and refactoring how chat UI is managed * #272 fix various bugs with new chat filter / selector logic * #272 remove outdated todos * #272 WIP working chat window resize * #272 bounded chatbox resizing * #272 alertUI moves with resized chat * #272 WIP making alertUI not be too large when changing size / UIScale * #272 WIP fixing window / uiscale adjustment * #272 WIP hacky approach for resizing, will try another approach * #272 implement hacky approach for bounded chat resize * #272 no resizing of lobby chat * #272 WIP adding unread marker to chat filters * #272 basic working unread chat message indicators * #272 WIP adding horizontal channel selector items * #272 horizontal channel selector popup * #272 workaround for chat selector staying highlighted when right clicking it while toggled * #272 workaround for chat selector staying highlighted when right clicking it while toggled * #272 wip trying to add tests for chatbox * #272 remove test, not really possible with current system * #272 merge latest * #272 merge latest * #272 fix csproj changes * It works if you disable the lobby * Fixes lobby chat * Adds more channel focusses * Channel cycler * Address review * Address nitpicks * Address more of the review * Fix chat post-viewport * Finalize review stuff Co-authored-by: chairbender <kwhipke1@gmail.com> Co-authored-by: ike709 <sparebytes@protonmail.com>
2021-04-20 18:39:39 -05:00
AddButton(ContentKeyFunctions.CycleChatChannelForward);
AddButton(ContentKeyFunctions.CycleChatChannelBackward);
2021-02-25 21:08:06 +01:00
AddButton(ContentKeyFunctions.OpenCharacterMenu);
AddButton(ContentKeyFunctions.OpenContextMenu);
AddButton(ContentKeyFunctions.OpenCraftingMenu);
AddButton(ContentKeyFunctions.OpenInventoryMenu);
AddButton(ContentKeyFunctions.OpenInfo);
2021-02-25 21:08:06 +01:00
AddButton(ContentKeyFunctions.OpenActionsMenu);
AddButton(ContentKeyFunctions.OpenEntitySpawnWindow);
AddButton(ContentKeyFunctions.OpenSandboxWindow);
AddButton(ContentKeyFunctions.OpenTileSpawnWindow);
AddButton(ContentKeyFunctions.OpenAdminMenu);
AddHeader("ui-options-header-misc");
AddButton(ContentKeyFunctions.TakeScreenshot);
AddButton(ContentKeyFunctions.TakeScreenshotNoUI);
AddHeader("ui-options-header-hotbar");
AddButton(ContentKeyFunctions.Hotbar1);
AddButton(ContentKeyFunctions.Hotbar2);
AddButton(ContentKeyFunctions.Hotbar3);
AddButton(ContentKeyFunctions.Hotbar4);
AddButton(ContentKeyFunctions.Hotbar5);
AddButton(ContentKeyFunctions.Hotbar6);
AddButton(ContentKeyFunctions.Hotbar7);
AddButton(ContentKeyFunctions.Hotbar8);
AddButton(ContentKeyFunctions.Hotbar9);
AddButton(ContentKeyFunctions.Hotbar0);
AddButton(ContentKeyFunctions.Loadout1);
AddButton(ContentKeyFunctions.Loadout2);
AddButton(ContentKeyFunctions.Loadout3);
AddButton(ContentKeyFunctions.Loadout4);
AddButton(ContentKeyFunctions.Loadout5);
AddButton(ContentKeyFunctions.Loadout6);
AddButton(ContentKeyFunctions.Loadout7);
AddButton(ContentKeyFunctions.Loadout8);
AddButton(ContentKeyFunctions.Loadout9);
AddHeader("ui-options-header-map-editor");
AddButton(EngineKeyFunctions.EditorPlaceObject);
AddButton(EngineKeyFunctions.EditorCancelPlace);
AddButton(EngineKeyFunctions.EditorGridPlace);
AddButton(EngineKeyFunctions.EditorLinePlace);
AddButton(EngineKeyFunctions.EditorRotateObject);
AddHeader("ui-options-header-dev");
AddButton(EngineKeyFunctions.ShowDebugConsole);
AddButton(EngineKeyFunctions.ShowDebugMonitors);
AddButton(EngineKeyFunctions.HideUI);
foreach (var control in _keyControls.Values)
{
UpdateKeyControl(control);
}
}
private void UpdateKeyControl(KeyControl control)
{
var activeBinds = _inputManager.GetKeyBindings(control.Function);
IKeyBinding? bind1 = null;
IKeyBinding? bind2 = null;
if (activeBinds.Count > 0)
{
bind1 = activeBinds[0];
if (activeBinds.Count > 1)
{
bind2 = activeBinds[1];
}
}
control.BindButton1.Binding = bind1;
control.BindButton1.UpdateText();
control.BindButton2.Binding = bind2;
control.BindButton2.UpdateText();
control.BindButton2.Button.Disabled = activeBinds.Count == 0;
control.ResetButton.Disabled = !_inputManager.IsKeyFunctionModified(control.Function);
}
protected override void EnteredTree()
{
base.EnteredTree();
_inputManager.FirstChanceOnKeyEvent += InputManagerOnFirstChanceOnKeyEvent;
_inputManager.OnKeyBindingAdded += OnKeyBindAdded;
_inputManager.OnKeyBindingRemoved += OnKeyBindRemoved;
}
protected override void ExitedTree()
{
base.ExitedTree();
_inputManager.FirstChanceOnKeyEvent -= InputManagerOnFirstChanceOnKeyEvent;
_inputManager.OnKeyBindingAdded -= OnKeyBindAdded;
_inputManager.OnKeyBindingRemoved -= OnKeyBindRemoved;
}
private void OnKeyBindRemoved(IKeyBinding obj)
{
OnKeyBindModified(obj, true);
}
private void OnKeyBindAdded(IKeyBinding obj)
{
OnKeyBindModified(obj, false);
}
private void OnKeyBindModified(IKeyBinding bind, bool removal)
{
if (!_keyControls.TryGetValue(bind.Function, out var keyControl))
{
return;
}
if (removal && _currentlyRebinding?.KeyControl == keyControl)
{
// Don't do update if the removal was from initiating a rebind.
return;
}
UpdateKeyControl(keyControl);
if (_currentlyRebinding == keyControl.BindButton1 || _currentlyRebinding == keyControl.BindButton2)
{
_currentlyRebinding = null;
}
}
private void InputManagerOnFirstChanceOnKeyEvent(KeyEventArgs keyEvent, KeyEventType type)
{
DebugTools.Assert(IsInsideTree);
if (_currentlyRebinding == null)
{
return;
}
keyEvent.Handle();
if (type != KeyEventType.Up)
{
return;
}
var key = keyEvent.Key;
// Figure out modifiers based on key event.
// TODO: this won't allow for combinations with keys other than the standard modifier keys,
// even though the input system totally supports it.
var mods = new Keyboard.Key[3];
var i = 0;
if (keyEvent.Control && key != Keyboard.Key.Control)
{
mods[i] = Keyboard.Key.Control;
i += 1;
}
if (keyEvent.Shift && key != Keyboard.Key.Shift)
{
mods[i] = Keyboard.Key.Shift;
i += 1;
}
if (keyEvent.Alt && key != Keyboard.Key.Alt)
{
mods[i] = Keyboard.Key.Alt;
i += 1;
}
// The input system can only handle 3 modifier keys so if you hold all 4 of the modifier keys
// then system gets the shaft, I guess.
if (keyEvent.System && i != 3 && key != Keyboard.Key.LSystem && key != Keyboard.Key.RSystem)
{
mods[i] = Keyboard.Key.LSystem;
}
var function = _currentlyRebinding.KeyControl.Function;
var bindType = KeyBindingType.State;
if (ToggleFunctions.Contains(function))
{
bindType = KeyBindingType.Toggle;
}
var registration = new KeyBindingRegistration
{
Function = function,
BaseKey = key,
Mod1 = mods[0],
Mod2 = mods[1],
Mod3 = mods[2],
Priority = 0,
Type = bindType,
CanFocus = key == Keyboard.Key.MouseLeft
|| key == Keyboard.Key.MouseRight
|| key == Keyboard.Key.MouseMiddle,
CanRepeat = false
};
_inputManager.RegisterBinding(registration);
// OnKeyBindModified will cause _currentlyRebinding to be reset and the UI to update.
_inputManager.SaveToUserData();
}
private void RebindButtonPressed(BindButton button)
{
if (_currentlyRebinding != null)
{
return;
}
_currentlyRebinding = button;
2021-02-25 21:08:06 +01:00
_currentlyRebinding.Button.Text = Loc.GetString("ui-options-key-prompt");
if (button.Binding != null)
{
_deferCommands.Add(() =>
{
// Have to do defer this or else there will be an exception in InputManager.
// Because this IS fired from an input event.
_inputManager.RemoveBinding(button.Binding);
});
}
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_deferCommands.Count == 0)
{
return;
}
foreach (var command in _deferCommands)
{
command();
}
_deferCommands.Clear();
}
private sealed class KeyControl : Control
{
public readonly BoundKeyFunction Function;
public readonly BindButton BindButton1;
public readonly BindButton BindButton2;
public readonly Button ResetButton;
2021-02-25 21:08:06 +01:00
public KeyControl(KeyRebindControl parent, BoundKeyFunction function)
{
Function = function;
var name = new Label
{
2021-02-25 21:08:06 +01:00
Text = Loc.GetString(
$"ui-options-function-{CaseConversion.PascalToKebab(function.FunctionName)}"),
2021-02-21 12:38:56 +01:00
HorizontalExpand = true,
HorizontalAlignment = HAlignment.Left
};
BindButton1 = new BindButton(parent, this, StyleBase.ButtonOpenRight);
BindButton2 = new BindButton(parent, this, StyleBase.ButtonOpenLeft);
2021-02-25 21:08:06 +01:00
ResetButton = new Button {Text = Loc.GetString("ui-options-bind-reset"), StyleClasses = {StyleBase.ButtonCaution}};
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
2021-02-21 12:38:56 +01:00
new Control {MinSize = (5, 0)},
name,
BindButton1,
BindButton2,
2021-02-21 12:38:56 +01:00
new Control {MinSize = (10, 0)},
ResetButton
}
};
ResetButton.OnPressed += args =>
{
parent._deferCommands.Add(() =>
{
parent._inputManager.ResetBindingsFor(function);
parent._inputManager.SaveToUserData();
});
};
AddChild(hBox);
}
}
private sealed class BindButton : Control
{
private readonly KeyRebindControl _control;
public readonly KeyControl KeyControl;
public readonly Button Button;
public IKeyBinding? Binding;
public BindButton(KeyRebindControl control, KeyControl keyControl, string styleClass)
{
_control = control;
KeyControl = keyControl;
Button = new Button {StyleClasses = {styleClass}};
UpdateText();
AddChild(Button);
Button.OnPressed += args =>
{
control.RebindButtonPressed(this);
};
Button.OnKeyBindDown += ButtonOnOnKeyBindDown;
2021-02-21 12:38:56 +01:00
MinSize = (200, 0);
}
private void ButtonOnOnKeyBindDown(GUIBoundKeyEventArgs args)
{
if (args.Function == EngineKeyFunctions.UIRightClick)
{
if (Binding != null)
{
_control._deferCommands.Add(() =>
{
_control._inputManager.RemoveBinding(Binding);
_control._inputManager.SaveToUserData();
});
}
args.Handle();
}
}
public void UpdateText()
{
2021-02-25 21:08:06 +01:00
Button.Text = Binding?.GetKeyString() ?? Loc.GetString("ui-options-unbound");
}
}
}
}
}