diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index b43edd6064..5c56fa52fc 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -21,6 +21,17 @@ + + + OptionsMenu.cs + + + OptionsMenu.cs + + + OptionsMenu.cs + + diff --git a/Content.Client/EscapeMenuOwner.cs b/Content.Client/EscapeMenuOwner.cs index 11a4ebdacb..e3d0d016db 100644 --- a/Content.Client/EscapeMenuOwner.cs +++ b/Content.Client/EscapeMenuOwner.cs @@ -2,29 +2,19 @@ using Content.Client.UserInterface; using Robust.Client.Console; using Robust.Client.Interfaces.Input; -using Robust.Client.Interfaces.Placement; -using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Interfaces.State; using Robust.Shared.Input; using Robust.Shared.Input.Binding; -using Robust.Shared.Interfaces.Configuration; -using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Prototypes; namespace Content.Client { internal sealed class EscapeMenuOwner : IEscapeMenuOwner { [Dependency] private readonly IClientConsole _clientConsole = default!; - [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; - [Dependency] private readonly IPlacementManager _placementManager = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IStateManager _stateManager = default!; - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; [Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly ILocalizationManager _localizationManager = default!; @@ -42,8 +32,7 @@ namespace Content.Client if (obj.NewState is GameScreenBase) { // Switched TO GameScreen. - _escapeMenu = new EscapeMenu(_clientConsole, _tileDefinitionManager, _placementManager, - _prototypeManager, _resourceCache, _configurationManager, _localizationManager); + _escapeMenu = new EscapeMenu(_clientConsole, _localizationManager); _escapeMenu.OnClose += () => _gameHud.EscapeButtonDown = false; diff --git a/Content.Client/State/MainMenu.cs b/Content.Client/State/MainMenu.cs index 473cb03d90..e7199e5c23 100644 --- a/Content.Client/State/MainMenu.cs +++ b/Content.Client/State/MainMenu.cs @@ -56,7 +56,7 @@ namespace Content.Client.State _client.RunLevelChanged += RunLevelChanged; - OptionsMenu = new OptionsMenu(_configurationManager); + OptionsMenu = new OptionsMenu(); } /// diff --git a/Content.Client/UserInterface/EscapeMenu.cs b/Content.Client/UserInterface/EscapeMenu.cs index 7b3ba644ce..3b58839962 100644 --- a/Content.Client/UserInterface/EscapeMenu.cs +++ b/Content.Client/UserInterface/EscapeMenu.cs @@ -1,24 +1,14 @@ using Robust.Client.Console; -using Robust.Client.Interfaces.Placement; -using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Interfaces.Configuration; -using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Prototypes; namespace Content.Client.UserInterface { internal sealed class EscapeMenu : SS14Window { private readonly IClientConsole _console; - private readonly ITileDefinitionManager _tileDefinitionManager; - private readonly IPlacementManager _placementManager; - private readonly IPrototypeManager _prototypeManager; - private readonly IResourceCache _resourceCache; - private readonly IConfigurationManager _configSystem; private readonly ILocalizationManager _localizationManager; private BaseButton DisconnectButton; @@ -26,20 +16,10 @@ namespace Content.Client.UserInterface private BaseButton OptionsButton; private OptionsMenu optionsMenu; - public EscapeMenu(IClientConsole console, - ITileDefinitionManager tileDefinitionManager, - IPlacementManager placementManager, - IPrototypeManager prototypeManager, - IResourceCache resourceCache, - IConfigurationManager configSystem, ILocalizationManager localizationManager) + public EscapeMenu(IClientConsole console, ILocalizationManager localizationManager) { - _configSystem = configSystem; _localizationManager = localizationManager; _console = console; - _tileDefinitionManager = tileDefinitionManager; - _placementManager = placementManager; - _prototypeManager = prototypeManager; - _resourceCache = resourceCache; IoCManager.InjectDependencies(this); @@ -48,7 +28,7 @@ namespace Content.Client.UserInterface private void PerformLayout() { - optionsMenu = new OptionsMenu(_configSystem); + optionsMenu = new OptionsMenu(); Resizable = false; @@ -95,10 +75,5 @@ namespace Content.Client.UserInterface optionsMenu.Dispose(); } } - - public override void Close() - { - base.Close(); - } } } diff --git a/Content.Client/UserInterface/KeyRebindControl.cs b/Content.Client/UserInterface/KeyRebindControl.cs new file mode 100644 index 0000000000..805fa26b5d --- /dev/null +++ b/Content.Client/UserInterface/KeyRebindControl.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Content.Client.UserInterface.Stylesheets; +using Content.Shared.Input; +using Robust.Client.Input; +using Robust.Client.Interfaces.Input; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Input; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +#nullable enable + +namespace Content.Client.UserInterface +{ +} diff --git a/Content.Client/UserInterface/KeyRebindWindow.cs b/Content.Client/UserInterface/KeyRebindWindow.cs deleted file mode 100644 index f23834f8ec..0000000000 --- a/Content.Client/UserInterface/KeyRebindWindow.cs +++ /dev/null @@ -1,468 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Client.UserInterface.Stylesheets; -using Content.Shared.Input; -using JetBrains.Annotations; -using Robust.Client.Input; -using Robust.Client.Interfaces.Console; -using Robust.Client.Interfaces.Input; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Input; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -#nullable enable - -namespace Content.Client.UserInterface -{ - [UsedImplicitly] - public sealed class RebindCommand : IConsoleCommand - { - public string Command => "rebind"; - public string Description => ""; - public string Help => ""; - - public bool Execute(IDebugConsole console, params string[] args) - { - new KeyRebindWindow().OpenCentered(); - return false; - } - } - - public sealed class KeyRebindWindow : SS14Window - { - // List of key functions that must be registered as toggle instead. - private static readonly HashSet ToggleFunctions = new HashSet - { - EngineKeyFunctions.ShowDebugMonitors, - EngineKeyFunctions.HideUI, - }; - - [Dependency] private readonly IInputManager _inputManager = default!; - - private BindButton? _currentlyRebinding; - - private readonly Dictionary _keyControls = - new Dictionary(); - - private readonly List _deferCommands = new List(); - - public KeyRebindWindow() - { - IoCManager.InjectDependencies(this); - - Title = Loc.GetString("Controls"); - - CustomMinimumSize = (800, 400); - - Button resetAllButton; - var vBox = new VBoxContainer(); - Contents.AddChild(new VBoxContainer - { - Children = - { - new ScrollContainer - { - SizeFlagsVertical = SizeFlags.FillExpand, - Children = {vBox} - }, - - new HBoxContainer - { - Children = - { - new Label - { - StyleClasses = {StyleBase.StyleClassLabelSubText}, - Text = "Click to change binding, right-click to clear" - }, - (resetAllButton = new Button - { - Text = "Reset ALL keybinds", - StyleClasses = {StyleBase.ButtonCaution}, - SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Expand - }) - } - } - } - }); - - resetAllButton.OnPressed += args => - { - _deferCommands.Add(() => - { - _inputManager.ResetAllBindings(); - _inputManager.SaveToUserData(); - }); - }; - - void AddHeader(string headerContents) - { - vBox.AddChild(new Control {CustomMinimumSize = (0, 8)}); - vBox.AddChild(new Label - { - Text = headerContents, - FontColorOverride = StyleNano.NanoGold, - StyleClasses = {StyleNano.StyleClassLabelKeyText} - }); - } - - void AddButton(BoundKeyFunction function, string name) - { - var control = new KeyControl(this, name, function); - vBox.AddChild(control); - _keyControls.Add(function, control); - } - - AddHeader("Movement"); - AddButton(EngineKeyFunctions.MoveUp, "Move up"); - AddButton(EngineKeyFunctions.MoveLeft, "Move left"); - AddButton(EngineKeyFunctions.MoveDown, "Move down"); - AddButton(EngineKeyFunctions.MoveRight, "Move right"); - AddButton(EngineKeyFunctions.Walk, "Walk"); - - AddHeader("Basic Interaction"); - AddButton(EngineKeyFunctions.Use, "Use"); - AddButton(ContentKeyFunctions.WideAttack, "Wide attack"); - AddButton(ContentKeyFunctions.ActivateItemInHand, "Activate item in hand"); - AddButton(ContentKeyFunctions.ActivateItemInWorld, "Activate item in world"); - AddButton(ContentKeyFunctions.Drop, "Drop item"); - AddButton(ContentKeyFunctions.ExamineEntity, "Examine"); - AddButton(ContentKeyFunctions.SwapHands, "Swap hands"); - AddButton(ContentKeyFunctions.ToggleCombatMode, "Toggle combat mode"); - - AddHeader("Advanced Interaction"); - AddButton(ContentKeyFunctions.SmartEquipBackpack, "Smart-equip to backpack"); - AddButton(ContentKeyFunctions.SmartEquipBelt, "Smart-equip to belt"); - AddButton(ContentKeyFunctions.ThrowItemInHand, "Throw item"); - AddButton(ContentKeyFunctions.TryPullObject, "Pull object"); - AddButton(ContentKeyFunctions.MovePulledObject, "Move pulled object"); - AddButton(ContentKeyFunctions.Point, "Point at location"); - - - AddHeader("User Interface"); - AddButton(ContentKeyFunctions.FocusChat, "Focus chat"); - AddButton(ContentKeyFunctions.FocusOOC, "Focus chat (OOC)"); - AddButton(ContentKeyFunctions.FocusAdminChat, "Focus chat (admin)"); - AddButton(ContentKeyFunctions.OpenCharacterMenu, "Open character menu"); - AddButton(ContentKeyFunctions.OpenContextMenu, "Open context menu"); - AddButton(ContentKeyFunctions.OpenCraftingMenu, "Open crafting menu"); - AddButton(ContentKeyFunctions.OpenInventoryMenu, "Open inventory"); - AddButton(ContentKeyFunctions.OpenTutorial, "Open tutorial"); - AddButton(ContentKeyFunctions.OpenEntitySpawnWindow, "Open entity spawn menu"); - AddButton(ContentKeyFunctions.OpenSandboxWindow, "Open sandbox menu"); - AddButton(ContentKeyFunctions.OpenTileSpawnWindow, "Open tile spawn menu"); - AddButton(ContentKeyFunctions.OpenAdminMenu, "Open admin menu"); - - AddHeader("Miscellaneous"); - AddButton(ContentKeyFunctions.TakeScreenshot, "Take screenshot"); - AddButton(ContentKeyFunctions.TakeScreenshotNoUI, "Take screenshot (without UI)"); - - AddHeader("Map Editor"); - AddButton(EngineKeyFunctions.EditorPlaceObject, "Place object"); - AddButton(EngineKeyFunctions.EditorCancelPlace, "Cancel placement"); - AddButton(EngineKeyFunctions.EditorGridPlace, "Place in grid"); - AddButton(EngineKeyFunctions.EditorLinePlace, "Place line"); - AddButton(EngineKeyFunctions.EditorRotateObject, "Rotate"); - - AddHeader("Development"); - AddButton(EngineKeyFunctions.ShowDebugConsole, "Open Console"); - AddButton(EngineKeyFunctions.ShowDebugMonitors, "Show Debug Monitors"); - AddButton(EngineKeyFunctions.HideUI, "Hide UI"); - - 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 Opened() - { - base.Opened(); - - _inputManager.FirstChanceOnKeyEvent += InputManagerOnFirstChanceOnKeyEvent; - _inputManager.OnKeyBindingAdded += OnKeyBindAdded; - _inputManager.OnKeyBindingRemoved += OnKeyBindRemoved; - } - - public override void Close() - { - base.Close(); - - _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(IsOpen); - - 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; - _currentlyRebinding.Button.Text = Loc.GetString("Press a key..."); - - 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 - { - private readonly KeyRebindWindow _window; - public readonly BoundKeyFunction Function; - public readonly BindButton BindButton1; - public readonly BindButton BindButton2; - public readonly Button ResetButton; - - public KeyControl(KeyRebindWindow window, string niceName, BoundKeyFunction function) - { - Function = function; - _window = window; - var name = new Label - { - Text = Loc.GetString(niceName), - SizeFlagsHorizontal = SizeFlags.Expand - }; - - BindButton1 = new BindButton(window, this, StyleBase.ButtonOpenRight); - BindButton2 = new BindButton(window, this, StyleBase.ButtonOpenLeft); - ResetButton = new Button {Text = "Reset", StyleClasses = {StyleBase.ButtonCaution}}; - - var hBox = new HBoxContainer - { - Children = - { - new Control {CustomMinimumSize = (5, 0)}, - name, - BindButton1, - BindButton2, - new Control {CustomMinimumSize = (10, 0)}, - ResetButton - } - }; - - ResetButton.OnPressed += args => - { - _window._deferCommands.Add(() => - { - _window._inputManager.ResetBindingsFor(function); - _window._inputManager.SaveToUserData(); - }); - }; - - AddChild(hBox); - } - } - - private sealed class BindButton : Control - { - private readonly KeyRebindWindow _window; - public readonly KeyControl KeyControl; - public readonly Button Button; - public IKeyBinding? Binding; - - public BindButton(KeyRebindWindow window, KeyControl keyControl, string styleClass) - { - _window = window; - KeyControl = keyControl; - Button = new Button {StyleClasses = {styleClass}}; - UpdateText(); - AddChild(Button); - - Button.OnPressed += args => - { - window.RebindButtonPressed(this); - }; - - Button.OnKeyBindDown += ButtonOnOnKeyBindDown; - - CustomMinimumSize = (200, 0); - } - - private void ButtonOnOnKeyBindDown(GUIBoundKeyEventArgs args) - { - if (args.Function == EngineKeyFunctions.UIRightClick) - { - if (Binding != null) - { - _window._deferCommands.Add(() => - { - _window._inputManager.RemoveBinding(Binding); - _window._inputManager.SaveToUserData(); - }); - } - - args.Handle(); - } - } - - public void UpdateText() - { - Button.Text = Binding?.GetKeyString() ?? "Unbound"; - } - } - } -} diff --git a/Content.Client/UserInterface/OptionsMenu.Audio.cs b/Content.Client/UserInterface/OptionsMenu.Audio.cs new file mode 100644 index 0000000000..ce8a1da9b7 --- /dev/null +++ b/Content.Client/UserInterface/OptionsMenu.Audio.cs @@ -0,0 +1,21 @@ +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.IoC; + +namespace Content.Client.UserInterface +{ + public sealed partial class OptionsMenu + { + private sealed class AudioControl : Control + { + public AudioControl(IConfigurationManager cfg) + { + AddChild(new Placeholder(IoCManager.Resolve()) + { + PlaceholderText = "Pretend there's a bunch of volume sliders here." + }); + } + } + } +} diff --git a/Content.Client/UserInterface/OptionsMenu.Graphics.cs b/Content.Client/UserInterface/OptionsMenu.Graphics.cs new file mode 100644 index 0000000000..d4c655b6d2 --- /dev/null +++ b/Content.Client/UserInterface/OptionsMenu.Graphics.cs @@ -0,0 +1,185 @@ +using Robust.Client.Graphics; +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.IoC; +using Robust.Shared.Localization; + +namespace Content.Client.UserInterface +{ + public sealed partial class OptionsMenu + { + private sealed class GraphicsControl : Control + { + private readonly IConfigurationManager _cfg; + + private readonly Button ApplyButton; + private readonly CheckBox VSyncCheckBox; + private readonly CheckBox FullscreenCheckBox; + private readonly OptionButton LightingPresetOption; + + + public GraphicsControl(IConfigurationManager cfg) + { + _cfg = cfg; + var vBox = new VBoxContainer(); + + var contents = new VBoxContainer(); + + VSyncCheckBox = new CheckBox {Text = Loc.GetString("VSync")}; + contents.AddChild(VSyncCheckBox); + VSyncCheckBox.OnToggled += OnCheckBoxToggled; + + FullscreenCheckBox = new CheckBox {Text = Loc.GetString("Fullscreen")}; + contents.AddChild(FullscreenCheckBox); + FullscreenCheckBox.OnToggled += OnCheckBoxToggled; + + LightingPresetOption = new OptionButton {CustomMinimumSize = (100, 0)}; + LightingPresetOption.AddItem(Loc.GetString("Very Low")); + LightingPresetOption.AddItem(Loc.GetString("Low")); + LightingPresetOption.AddItem(Loc.GetString("Medium")); + LightingPresetOption.AddItem(Loc.GetString("High")); + LightingPresetOption.OnItemSelected += OnLightingQualityChanged; + + var lightingHBox = new HBoxContainer + { + Children = + { + new Label {Text = Loc.GetString("Lighting Quality:")}, + new Control {CustomMinimumSize = (4, 0)}, + LightingPresetOption + } + }; + contents.AddChild(lightingHBox); + + ApplyButton = new Button + { + Text = Loc.GetString("Apply"), TextAlign = Label.AlignMode.Center, + SizeFlagsHorizontal = SizeFlags.ShrinkEnd + }; + + var resourceCache = IoCManager.Resolve(); + + contents.AddChild(new Placeholder(resourceCache) + { + SizeFlagsVertical = SizeFlags.FillExpand, + PlaceholderText = "UI Scaling" + }); + + contents.AddChild(new Placeholder(resourceCache) + { + SizeFlagsVertical = SizeFlags.FillExpand, + PlaceholderText = "Viewport settings" + }); + + vBox.AddChild(new MarginContainer + { + MarginLeftOverride = 2, + MarginTopOverride = 2, + MarginRightOverride = 2, + SizeFlagsVertical = SizeFlags.FillExpand, + Children = + { + contents + } + }); + + vBox.AddChild(new StripeBack + { + HasBottomEdge = false, + HasMargins = false, + Children = + { + ApplyButton + } + }); + ApplyButton.OnPressed += OnApplyButtonPressed; + + VSyncCheckBox.Pressed = _cfg.GetCVar("display.vsync"); + FullscreenCheckBox.Pressed = ConfigIsFullscreen; + LightingPresetOption.SelectId(GetConfigLightingQuality()); + + + AddChild(vBox); + } + + private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args) + { + _cfg.SetCVar("display.vsync", VSyncCheckBox.Pressed); + SetConfigLightingQuality(LightingPresetOption.SelectedId); + _cfg.SetCVar("display.windowmode", + (int) (FullscreenCheckBox.Pressed ? WindowMode.Fullscreen : WindowMode.Windowed)); + _cfg.SaveToFile(); + UpdateApplyButton(); + } + + private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args) + { + UpdateApplyButton(); + } + + private void OnLightingQualityChanged(OptionButton.ItemSelectedEventArgs args) + { + LightingPresetOption.SelectId(args.Id); + UpdateApplyButton(); + } + + private void UpdateApplyButton() + { + var isVSyncSame = VSyncCheckBox.Pressed == _cfg.GetCVar("display.vsync"); + var isFullscreenSame = FullscreenCheckBox.Pressed == ConfigIsFullscreen; + var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality(); + ApplyButton.Disabled = isVSyncSame && isFullscreenSame && isLightingQualitySame; + } + + private bool ConfigIsFullscreen => + _cfg.GetCVar("display.windowmode") == (int) WindowMode.Fullscreen; + + private int GetConfigLightingQuality() + { + var val = _cfg.GetCVar("display.lightmapdivider"); + var soft = _cfg.GetCVar("display.softshadows"); + if (val >= 8) + { + return 0; + } + else if ((val >= 2) && !soft) + { + return 1; + } + else if (val >= 2) + { + return 2; + } + else + { + return 3; + } + } + + private void SetConfigLightingQuality(int value) + { + switch (value) + { + case 0: + _cfg.SetCVar("display.lightmapdivider", 8); + _cfg.SetCVar("display.softshadows", false); + break; + case 1: + _cfg.SetCVar("display.lightmapdivider", 2); + _cfg.SetCVar("display.softshadows", false); + break; + case 2: + _cfg.SetCVar("display.lightmapdivider", 2); + _cfg.SetCVar("display.softshadows", true); + break; + case 3: + _cfg.SetCVar("display.lightmapdivider", 1); + _cfg.SetCVar("display.softshadows", true); + break; + } + } + } + } +} diff --git a/Content.Client/UserInterface/OptionsMenu.KeyRebind.cs b/Content.Client/UserInterface/OptionsMenu.KeyRebind.cs new file mode 100644 index 0000000000..c158a48895 --- /dev/null +++ b/Content.Client/UserInterface/OptionsMenu.KeyRebind.cs @@ -0,0 +1,473 @@ +#nullable enable +using System; +using System.Collections.Generic; +using Content.Client.UserInterface.Stylesheets; +using Content.Shared.Input; +using Robust.Client.Input; +using Robust.Client.Interfaces.Input; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Input; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Client.UserInterface +{ + 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 ToggleFunctions = new HashSet + { + EngineKeyFunctions.ShowDebugMonitors, + EngineKeyFunctions.HideUI, + }; + + [Dependency] private readonly IInputManager _inputManager = default!; + + private BindButton? _currentlyRebinding; + + private readonly Dictionary _keyControls = + new Dictionary(); + + private readonly List _deferCommands = new List(); + + public KeyRebindControl() + { + IoCManager.InjectDependencies(this); + + Button resetAllButton; + var vBox = new VBoxContainer(); + AddChild(new VBoxContainer + { + Children = + { + new ScrollContainer + { + SizeFlagsVertical = SizeFlags.FillExpand, + Children = + { + new MarginContainer + { + MarginLeftOverride = 2, + Children = + { + vBox + } + } + } + }, + + new StripeBack + { + HasBottomEdge = false, + HasMargins = false, + Children = + { + new HBoxContainer + { + Children = + { + new Control {CustomMinimumSize = (2, 0)}, + new Label + { + StyleClasses = {StyleBase.StyleClassLabelSubText}, + Text = "Click to change binding, right-click to clear" + }, + (resetAllButton = new Button + { + Text = "Reset ALL keybinds", + StyleClasses = {StyleBase.ButtonCaution}, + SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Expand + }) + } + } + } + } + } + }); + + resetAllButton.OnPressed += args => + { + _deferCommands.Add(() => + { + _inputManager.ResetAllBindings(); + _inputManager.SaveToUserData(); + }); + }; + + var first = true; + + void AddHeader(string headerContents) + { + if (!first) + { + vBox.AddChild(new Control {CustomMinimumSize = (0, 8)}); + } + + first = false; + vBox.AddChild(new Label + { + Text = headerContents, + FontColorOverride = StyleNano.NanoGold, + StyleClasses = {StyleNano.StyleClassLabelKeyText} + }); + } + + void AddButton(BoundKeyFunction function, string name) + { + var control = new KeyControl(this, name, function); + vBox.AddChild(control); + _keyControls.Add(function, control); + } + + AddHeader("Movement"); + AddButton(EngineKeyFunctions.MoveUp, "Move up"); + AddButton(EngineKeyFunctions.MoveLeft, "Move left"); + AddButton(EngineKeyFunctions.MoveDown, "Move down"); + AddButton(EngineKeyFunctions.MoveRight, "Move right"); + AddButton(EngineKeyFunctions.Walk, "Walk"); + + AddHeader("Basic Interaction"); + AddButton(EngineKeyFunctions.Use, "Use"); + AddButton(ContentKeyFunctions.WideAttack, "Wide attack"); + AddButton(ContentKeyFunctions.ActivateItemInHand, "Activate item in hand"); + AddButton(ContentKeyFunctions.ActivateItemInWorld, "Activate item in world"); + AddButton(ContentKeyFunctions.Drop, "Drop item"); + AddButton(ContentKeyFunctions.ExamineEntity, "Examine"); + AddButton(ContentKeyFunctions.SwapHands, "Swap hands"); + AddButton(ContentKeyFunctions.ToggleCombatMode, "Toggle combat mode"); + + AddHeader("Advanced Interaction"); + AddButton(ContentKeyFunctions.SmartEquipBackpack, "Smart-equip to backpack"); + AddButton(ContentKeyFunctions.SmartEquipBelt, "Smart-equip to belt"); + AddButton(ContentKeyFunctions.ThrowItemInHand, "Throw item"); + AddButton(ContentKeyFunctions.TryPullObject, "Pull object"); + AddButton(ContentKeyFunctions.MovePulledObject, "Move pulled object"); + AddButton(ContentKeyFunctions.Point, "Point at location"); + + + AddHeader("User Interface"); + AddButton(ContentKeyFunctions.FocusChat, "Focus chat"); + AddButton(ContentKeyFunctions.FocusOOC, "Focus chat (OOC)"); + AddButton(ContentKeyFunctions.FocusAdminChat, "Focus chat (admin)"); + AddButton(ContentKeyFunctions.OpenCharacterMenu, "Open character menu"); + AddButton(ContentKeyFunctions.OpenContextMenu, "Open context menu"); + AddButton(ContentKeyFunctions.OpenCraftingMenu, "Open crafting menu"); + AddButton(ContentKeyFunctions.OpenInventoryMenu, "Open inventory"); + AddButton(ContentKeyFunctions.OpenTutorial, "Open tutorial"); + AddButton(ContentKeyFunctions.OpenEntitySpawnWindow, "Open entity spawn menu"); + AddButton(ContentKeyFunctions.OpenSandboxWindow, "Open sandbox menu"); + AddButton(ContentKeyFunctions.OpenTileSpawnWindow, "Open tile spawn menu"); + AddButton(ContentKeyFunctions.OpenAdminMenu, "Open admin menu"); + + AddHeader("Miscellaneous"); + AddButton(ContentKeyFunctions.TakeScreenshot, "Take screenshot"); + AddButton(ContentKeyFunctions.TakeScreenshotNoUI, "Take screenshot (without UI)"); + + AddHeader("Map Editor"); + AddButton(EngineKeyFunctions.EditorPlaceObject, "Place object"); + AddButton(EngineKeyFunctions.EditorCancelPlace, "Cancel placement"); + AddButton(EngineKeyFunctions.EditorGridPlace, "Place in grid"); + AddButton(EngineKeyFunctions.EditorLinePlace, "Place line"); + AddButton(EngineKeyFunctions.EditorRotateObject, "Rotate"); + + AddHeader("Development"); + AddButton(EngineKeyFunctions.ShowDebugConsole, "Open Console"); + AddButton(EngineKeyFunctions.ShowDebugMonitors, "Show Debug Monitors"); + AddButton(EngineKeyFunctions.HideUI, "Hide UI"); + + 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; + _currentlyRebinding.Button.Text = Loc.GetString("Press a key..."); + + 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; + + public KeyControl(KeyRebindControl parent, string niceName, BoundKeyFunction function) + { + Function = function; + var name = new Label + { + Text = Loc.GetString(niceName), + SizeFlagsHorizontal = SizeFlags.Expand + }; + + BindButton1 = new BindButton(parent, this, StyleBase.ButtonOpenRight); + BindButton2 = new BindButton(parent, this, StyleBase.ButtonOpenLeft); + ResetButton = new Button {Text = "Reset", StyleClasses = {StyleBase.ButtonCaution}}; + + var hBox = new HBoxContainer + { + Children = + { + new Control {CustomMinimumSize = (5, 0)}, + name, + BindButton1, + BindButton2, + new Control {CustomMinimumSize = (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; + + CustomMinimumSize = (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() + { + Button.Text = Binding?.GetKeyString() ?? "Unbound"; + } + } + } + } +} diff --git a/Content.Client/UserInterface/OptionsMenu.cs b/Content.Client/UserInterface/OptionsMenu.cs index 4ecd54d249..515916722d 100644 --- a/Content.Client/UserInterface/OptionsMenu.cs +++ b/Content.Client/UserInterface/OptionsMenu.cs @@ -1,136 +1,45 @@ -using Robust.Client.Graphics; -using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Maths; +#nullable enable + namespace Content.Client.UserInterface { - public sealed class OptionsMenu : SS14Window + public sealed partial class OptionsMenu : SS14Window { - private readonly Button ApplyButton; - private readonly CheckBox VSyncCheckBox; - private readonly CheckBox FullscreenCheckBox; - private readonly OptionButton LightingPresetOption; - private readonly IConfigurationManager configManager; + [Dependency] private readonly IConfigurationManager _configManager = default!; - protected override Vector2? CustomSize => (180, 160); + protected override Vector2? CustomSize => (800, 450); - public OptionsMenu(IConfigurationManager configMan) + public OptionsMenu() { - configManager = configMan; + IoCManager.InjectDependencies(this); - Title = "Options"; + Title = Loc.GetString("Game Options"); - var vBox = new VBoxContainer(); - Contents.AddChild(vBox); - //vBox.SetAnchorAndMarginPreset(LayoutPreset.Wide); + GraphicsControl graphicsControl; + KeyRebindControl rebindControl; + AudioControl audioControl; - VSyncCheckBox = new CheckBox {Text = "VSync"}; - vBox.AddChild(VSyncCheckBox); - VSyncCheckBox.OnToggled += OnCheckBoxToggled; - - FullscreenCheckBox = new CheckBox {Text = "Fullscreen"}; - vBox.AddChild(FullscreenCheckBox); - FullscreenCheckBox.OnToggled += OnCheckBoxToggled; - - vBox.AddChild(new Label {Text = "Lighting Quality"}); - - LightingPresetOption = new OptionButton(); - LightingPresetOption.AddItem("Very Low"); - LightingPresetOption.AddItem("Low"); - LightingPresetOption.AddItem("Medium"); - LightingPresetOption.AddItem("High"); - vBox.AddChild(LightingPresetOption); - LightingPresetOption.OnItemSelected += OnLightingQualityChanged; - - ApplyButton = new Button + var tabs = new TabContainer { - Text = "Apply", TextAlign = Label.AlignMode.Center, - SizeFlagsVertical = SizeFlags.ShrinkCenter + Children = + { + (graphicsControl = new GraphicsControl(_configManager)), + (rebindControl = new KeyRebindControl()), + (audioControl = new AudioControl(_configManager)), + } }; - vBox.AddChild(ApplyButton); - ApplyButton.OnPressed += OnApplyButtonPressed; - VSyncCheckBox.Pressed = configManager.GetCVar("display.vsync"); - FullscreenCheckBox.Pressed = ConfigIsFullscreen; - LightingPresetOption.SelectId(GetConfigLightingQuality()); - } + TabContainer.SetTabTitle(graphicsControl, Loc.GetString("Graphics")); + TabContainer.SetTabTitle(rebindControl, Loc.GetString("Controls")); + TabContainer.SetTabTitle(audioControl, Loc.GetString("Audio")); - private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args) - { - configManager.SetCVar("display.vsync", VSyncCheckBox.Pressed); - SetConfigLightingQuality(LightingPresetOption.SelectedId); - configManager.SetCVar("display.windowmode", - (int) (FullscreenCheckBox.Pressed ? WindowMode.Fullscreen : WindowMode.Windowed)); - configManager.SaveToFile(); - UpdateApplyButton(); - } - - private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args) - { - UpdateApplyButton(); - } - - private void OnLightingQualityChanged(OptionButton.ItemSelectedEventArgs args) - { - LightingPresetOption.SelectId(args.Id); - UpdateApplyButton(); - } - - private void UpdateApplyButton() - { - var isVSyncSame = VSyncCheckBox.Pressed == configManager.GetCVar("display.vsync"); - var isFullscreenSame = FullscreenCheckBox.Pressed == ConfigIsFullscreen; - var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality(); - ApplyButton.Disabled = isVSyncSame && isFullscreenSame && isLightingQualitySame; - } - - private bool ConfigIsFullscreen => - configManager.GetCVar("display.windowmode") == (int) WindowMode.Fullscreen; - - private int GetConfigLightingQuality() - { - var val = configManager.GetCVar("display.lightmapdivider"); - var soft = configManager.GetCVar("display.softshadows"); - if (val >= 8) - { - return 0; - } - else if ((val >= 2) && !soft) - { - return 1; - } - else if (val >= 2) - { - return 2; - } - else - { - return 3; - } - } - private void SetConfigLightingQuality(int value) - { - switch (value) - { - case 0: - configManager.SetCVar("display.lightmapdivider", 8); - configManager.SetCVar("display.softshadows", false); - break; - case 1: - configManager.SetCVar("display.lightmapdivider", 2); - configManager.SetCVar("display.softshadows", false); - break; - case 2: - configManager.SetCVar("display.lightmapdivider", 2); - configManager.SetCVar("display.softshadows", true); - break; - case 3: - configManager.SetCVar("display.lightmapdivider", 1); - configManager.SetCVar("display.softshadows", true); - break; - } + Contents.AddChild(tabs); } } }