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/GameObjects/Components/LightBehaviourComponent.cs b/Content.Client/GameObjects/Components/LightBehaviourComponent.cs index 83ff452c0e..4e50dd2905 100644 --- a/Content.Client/GameObjects/Components/LightBehaviourComponent.cs +++ b/Content.Client/GameObjects/Components/LightBehaviourComponent.cs @@ -1,5 +1,4 @@ - -using System; +using System; using System.Collections.Generic; using Robust.Client.GameObjects; using Robust.Shared.Animations; @@ -13,8 +12,6 @@ using Robust.Shared.Log; using Robust.Shared.Maths; using Robust.Shared.Interfaces.Serialization; using Robust.Client.Animations; -using Robust.Shared.Interfaces.GameObjects; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Robust.Client.GameObjects.Components.Animations; using System.Linq; @@ -43,7 +40,7 @@ namespace Content.Client.GameObjects.Components protected IRobustRandom RobustRandom = default; private float _maxTime = default; - + public virtual void ExposeData(ObjectSerializer serializer) { serializer.DataField(this, x => x.ID, "id", string.Empty); @@ -83,7 +80,7 @@ namespace Content.Client.GameObjects.Components MaxTime = MaxDuration; } - owner.Length = TimeSpan.FromSeconds(MaxTime); + owner.Length = TimeSpan.FromSeconds(MaxTime); } public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback() @@ -344,7 +341,7 @@ namespace Content.Client.GameObjects.Components /// A component which applies a specific behaviour to a PointLightComponent on its owner. /// [RegisterComponent] - public class LightBehaviourComponent : SharedLightBehaviourComponent + public class LightBehaviourComponent : SharedLightBehaviourComponent { private const string KeyPrefix = nameof(LightBehaviourComponent); @@ -387,7 +384,7 @@ namespace Content.Client.GameObjects.Components container.LightBehaviour.Initialize(_lightComponent); } - // we need to initialize all behaviours before starting any + // we need to initialize all behaviours before starting any foreach (var container in _animations) { if (container.LightBehaviour.Enabled) diff --git a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs index 4c5d5dce36..2cf1b964c4 100644 --- a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs +++ b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs @@ -34,7 +34,7 @@ namespace Content.Client.GameObjects.Components.Storage base.OnAdd(); Window = new StorageWindow() - {StorageEntity = this, Title = Owner.Name}; + { StorageEntity = this, Title = Owner.Name }; } public override void OnRemove() @@ -55,7 +55,7 @@ namespace Content.Client.GameObjects.Components.Storage break; //Opens the UI case OpenStorageUIMessage _: - OpenUI(); + ToggleUI(); break; case CloseStorageUIMessage _: CloseUI(); @@ -76,11 +76,14 @@ namespace Content.Client.GameObjects.Components.Storage } /// - /// Opens the storage UI + /// Opens the storage UI if closed. Closes it if opened. /// - private void OpenUI() + private void ToggleUI() { - Window.Open(); + if (Window.IsOpen) + Window.Close(); + else + Window.Open(); } private void CloseUI() @@ -107,8 +110,8 @@ namespace Content.Client.GameObjects.Components.Storage private Label Information; public ClientStorageComponent StorageEntity; - private StyleBoxFlat _HoveredBox = new StyleBoxFlat {BackgroundColor = Color.Black.WithAlpha(0.35f)}; - private StyleBoxFlat _unHoveredBox = new StyleBoxFlat {BackgroundColor = Color.Black.WithAlpha(0.0f)}; + private StyleBoxFlat _HoveredBox = new StyleBoxFlat { BackgroundColor = Color.Black.WithAlpha(0.35f) }; + private StyleBoxFlat _unHoveredBox = new StyleBoxFlat { BackgroundColor = Color.Black.WithAlpha(0.0f) }; protected override Vector2? CustomSize => (180, 320); diff --git a/Content.Client/State/MainMenu.cs b/Content.Client/State/MainMenu.cs index 06c7e46a3a..e7199e5c23 100644 --- a/Content.Client/State/MainMenu.cs +++ b/Content.Client/State/MainMenu.cs @@ -1,5 +1,6 @@ using System; using System.Text.RegularExpressions; +using Content.Client.UserInterface; using Robust.Client; using Robust.Client.Interfaces; using Robust.Client.Interfaces.ResourceManagement; @@ -7,7 +8,6 @@ using Robust.Client.Interfaces.UserInterface; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; @@ -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/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 new file mode 100644 index 0000000000..515916722d --- /dev/null +++ b/Content.Client/UserInterface/OptionsMenu.cs @@ -0,0 +1,45 @@ +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 partial class OptionsMenu : SS14Window + { + [Dependency] private readonly IConfigurationManager _configManager = default!; + + protected override Vector2? CustomSize => (800, 450); + + public OptionsMenu() + { + IoCManager.InjectDependencies(this); + + Title = Loc.GetString("Game Options"); + + GraphicsControl graphicsControl; + KeyRebindControl rebindControl; + AudioControl audioControl; + + var tabs = new TabContainer + { + Children = + { + (graphicsControl = new GraphicsControl(_configManager)), + (rebindControl = new KeyRebindControl()), + (audioControl = new AudioControl(_configManager)), + } + }; + + TabContainer.SetTabTitle(graphicsControl, Loc.GetString("Graphics")); + TabContainer.SetTabTitle(rebindControl, Loc.GetString("Controls")); + TabContainer.SetTabTitle(audioControl, Loc.GetString("Audio")); + + Contents.AddChild(tabs); + } + } +} diff --git a/Content.Client/UserInterface/StripeBack.cs b/Content.Client/UserInterface/StripeBack.cs index 852c71deab..a43ab0c0fe 100644 --- a/Content.Client/UserInterface/StripeBack.cs +++ b/Content.Client/UserInterface/StripeBack.cs @@ -12,6 +12,7 @@ namespace Content.Client.UserInterface private bool _hasTopEdge = true; private bool _hasBottomEdge = true; + private bool _hasMargins = true; public const string StylePropertyBackground = "background"; @@ -35,6 +36,16 @@ namespace Content.Client.UserInterface } } + public bool HasMargins + { + get => _hasMargins; + set + { + _hasMargins = value; + MinimumSizeChanged(); + } + } + protected override Vector2 CalculateMinimumSize() { var size = Vector2.Zero; @@ -44,14 +55,16 @@ namespace Content.Client.UserInterface size = Vector2.ComponentMax(size, child.CombinedMinimumSize); } + var padSize = HasMargins ? PadSize : 0; + if (HasBottomEdge) { - size += (0, PadSize + EdgeSize); + size += (0, padSize + EdgeSize); } if (HasTopEdge) { - size += (0, PadSize + EdgeSize); + size += (0, padSize + EdgeSize); } return size; @@ -61,14 +74,16 @@ namespace Content.Client.UserInterface { var box = SizeBox; + var padSize = HasMargins ? PadSize : 0; + if (HasTopEdge) { - box += (0, PadSize + EdgeSize, 0, 0); + box += (0, padSize + EdgeSize, 0, 0); } if (HasBottomEdge) { - box += (0, 0, 0, -(PadSize + EdgeSize)); + box += (0, 0, 0, -(padSize + EdgeSize)); } foreach (var child in Children) @@ -81,16 +96,18 @@ namespace Content.Client.UserInterface { UIBox2 centerBox = PixelSizeBox; + var padSize = HasMargins ? PadSize : 0; + if (HasTopEdge) { - centerBox += (0, (PadSize + EdgeSize) * UIScale, 0, 0); - handle.DrawRect(new UIBox2(0, PadSize * UIScale, PixelWidth, centerBox.Top), EdgeColor); + centerBox += (0, (padSize + EdgeSize) * UIScale, 0, 0); + handle.DrawRect(new UIBox2(0, padSize * UIScale, PixelWidth, centerBox.Top), EdgeColor); } if (HasBottomEdge) { - centerBox += (0, 0, 0, -((PadSize + EdgeSize) * UIScale)); - handle.DrawRect(new UIBox2(0, centerBox.Bottom, PixelWidth, PixelHeight - PadSize * UIScale), EdgeColor); + centerBox += (0, 0, 0, -((padSize + EdgeSize) * UIScale)); + handle.DrawRect(new UIBox2(0, centerBox.Bottom, PixelWidth, PixelHeight - padSize * UIScale), EdgeColor); } GetActualStyleBox()?.Draw(handle, centerBox); diff --git a/Content.Client/UserInterface/Stylesheets/StyleBase.cs b/Content.Client/UserInterface/Stylesheets/StyleBase.cs index 939c23ecb5..eaa74072ec 100644 --- a/Content.Client/UserInterface/Stylesheets/StyleBase.cs +++ b/Content.Client/UserInterface/Stylesheets/StyleBase.cs @@ -17,6 +17,8 @@ namespace Content.Client.UserInterface.Stylesheets public const string ButtonOpenLeft = "OpenLeft"; public const string ButtonOpenBoth = "OpenBoth"; + public const string ButtonCaution = "Caution"; + public abstract Stylesheet Stylesheet { get; } protected StyleRule[] BaseRules { get; } diff --git a/Content.Client/UserInterface/Stylesheets/StyleNano.cs b/Content.Client/UserInterface/Stylesheets/StyleNano.cs index 475ba6d33f..fa7329bcea 100644 --- a/Content.Client/UserInterface/Stylesheets/StyleNano.cs +++ b/Content.Client/UserInterface/Stylesheets/StyleNano.cs @@ -31,6 +31,11 @@ namespace Content.Client.UserInterface.Stylesheets public static readonly Color ButtonColorPressed = Color.FromHex("#3e6c45"); public static readonly Color ButtonColorDisabled = Color.FromHex("#30313c"); + public static readonly Color ButtonColorCautionDefault = Color.FromHex("#ab3232"); + public static readonly Color ButtonColorCautionHovered = Color.FromHex("#cf2f2f"); + public static readonly Color ButtonColorCautionPressed = Color.FromHex("#3e6c45"); + public static readonly Color ButtonColorCautionDisabled = Color.FromHex("#602a2a"); + //Used by the APC and SMES menus public const string StyleClassPowerStateNone = "PowerStateNone"; public const string StyleClassPowerStateLow = "PowerStateLow"; @@ -70,30 +75,6 @@ namespace Content.Client.UserInterface.Stylesheets var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png"); - static (StyleBox, StyleBox, StyleBox, StyleBox) ButtonPermutations(StyleBoxTexture @base) - { - var normal = new StyleBoxTexture(@base) {Modulate = ButtonColorDefault}; - var hover = new StyleBoxTexture(@base) {Modulate = ButtonColorHovered}; - var pressed = new StyleBoxTexture(@base) {Modulate = ButtonColorPressed}; - var disabled = new StyleBoxTexture(@base) {Modulate = ButtonColorDisabled}; - - return (normal, hover, pressed, disabled); - } - - // Button styles. - var (buttonNormal, buttonHover, buttonPressed, buttonDisabled) - = ButtonPermutations(BaseButton); - - var (buttonRNormal, buttonRHover, buttonRPressed, buttonRDisabled) - = ButtonPermutations(BaseButtonOpenRight); - - var (buttonLNormal, buttonLHover, buttonLPressed, buttonLDisabled) - = ButtonPermutations(BaseButtonOpenLeft); - - var (buttonBNormal, buttonBHover, buttonBPressed, buttonBDisabled) - = ButtonPermutations(BaseButtonOpenBoth); - - var lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png"); var lineEdit = new StyleBoxTexture { @@ -297,80 +278,60 @@ namespace Content.Client.UserInterface.Stylesheets new StyleProperty(Control.StylePropertyModulateSelf, Color.FromHex("#753131")), }), - // Regular buttons! - new StyleRule(new SelectorElement(typeof(ContainerButton), new[] { ContainerButton.StyleClassButton }, null, new[] {ContainerButton.StylePseudoClassNormal}), new[] - { - new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonNormal), - }), - new StyleRule(new SelectorElement(typeof(ContainerButton), new[] { ContainerButton.StyleClassButton }, null, new[] {ContainerButton.StylePseudoClassHover}), new[] - { - new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonHover), - }), - new StyleRule(new SelectorElement(typeof(ContainerButton), new[] { ContainerButton.StyleClassButton }, null, new[] {ContainerButton.StylePseudoClassPressed}), new[] - { - new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonPressed), - }), - new StyleRule(new SelectorElement(typeof(ContainerButton), new[] { ContainerButton.StyleClassButton }, null, new[] {ContainerButton.StylePseudoClassDisabled}), new[] - { - new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonDisabled), - }), + // Shapes for the buttons. + Element().Class(ContainerButton.StyleClassButton) + .Prop(ContainerButton.StylePropertyStyleBox, BaseButton), + + Element().Class(ContainerButton.StyleClassButton) + .Class(ButtonOpenRight) + .Prop(ContainerButton.StylePropertyStyleBox, BaseButtonOpenRight), + + Element().Class(ContainerButton.StyleClassButton) + .Class(ButtonOpenLeft) + .Prop(ContainerButton.StylePropertyStyleBox, BaseButtonOpenLeft), + + Element().Class(ContainerButton.StyleClassButton) + .Class(ButtonOpenBoth) + .Prop(ContainerButton.StylePropertyStyleBox, BaseButtonOpenBoth), new StyleRule(new SelectorElement(typeof(Label), new[] { Button.StyleClassButton }, null, null), new[] { new StyleProperty(Label.StylePropertyAlignMode, Label.AlignMode.Center), }), - // Right open buttons. - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenRight) + // Colors for the buttons. + Element().Class(ContainerButton.StyleClassButton) .Pseudo(ContainerButton.StylePseudoClassNormal) - .Prop(ContainerButton.StylePropertyStyleBox, buttonRNormal), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDefault), - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenRight) + Element().Class(ContainerButton.StyleClassButton) .Pseudo(ContainerButton.StylePseudoClassHover) - .Prop(ContainerButton.StylePropertyStyleBox, buttonRHover), + .Prop(Control.StylePropertyModulateSelf, ButtonColorHovered), - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenRight) + Element().Class(ContainerButton.StyleClassButton) .Pseudo(ContainerButton.StylePseudoClassPressed) - .Prop(ContainerButton.StylePropertyStyleBox, buttonRPressed), + .Prop(Control.StylePropertyModulateSelf, ButtonColorPressed), - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenRight) + Element().Class(ContainerButton.StyleClassButton) .Pseudo(ContainerButton.StylePseudoClassDisabled) - .Prop(ContainerButton.StylePropertyStyleBox, buttonRDisabled), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDisabled), - // Left open buttons. - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenLeft) + // Colors for the caution buttons. + Element().Class(ContainerButton.StyleClassButton).Class(ButtonCaution) .Pseudo(ContainerButton.StylePseudoClassNormal) - .Prop(ContainerButton.StylePropertyStyleBox, buttonLNormal), + .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDefault), - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenLeft) + Element().Class(ContainerButton.StyleClassButton).Class(ButtonCaution) .Pseudo(ContainerButton.StylePseudoClassHover) - .Prop(ContainerButton.StylePropertyStyleBox, buttonLHover), + .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionHovered), - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenLeft) + Element().Class(ContainerButton.StyleClassButton).Class(ButtonCaution) .Pseudo(ContainerButton.StylePseudoClassPressed) - .Prop(ContainerButton.StylePropertyStyleBox, buttonLPressed), + .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionPressed), - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenLeft) + Element().Class(ContainerButton.StyleClassButton).Class(ButtonCaution) .Pseudo(ContainerButton.StylePseudoClassDisabled) - .Prop(ContainerButton.StylePropertyStyleBox, buttonLDisabled), - - // "Both" open buttons - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenBoth) - .Pseudo(ContainerButton.StylePseudoClassNormal) - .Prop(ContainerButton.StylePropertyStyleBox, buttonBNormal), - - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenBoth) - .Pseudo(ContainerButton.StylePseudoClassHover) - .Prop(ContainerButton.StylePropertyStyleBox, buttonBHover), - - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenBoth) - .Pseudo(ContainerButton.StylePseudoClassPressed) - .Prop(ContainerButton.StylePropertyStyleBox, buttonBPressed), - - Element().Class(ContainerButton.StyleClassButton).Class(ButtonOpenBoth) - .Pseudo(ContainerButton.StylePseudoClassDisabled) - .Prop(ContainerButton.StylePropertyStyleBox, buttonBDisabled), - + .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled), new StyleRule(new SelectorChild( new SelectorElement(typeof(Button), null, null, new[] {ContainerButton.StylePseudoClassDisabled}), @@ -649,25 +610,28 @@ namespace Content.Client.UserInterface.Stylesheets }), // Those top menu buttons. + Element() + .Prop(Button.StylePropertyStyleBox, BaseButton), + new StyleRule( new SelectorElement(typeof(GameHud.TopButton), null, null, new[] {Button.StylePseudoClassNormal}), new[] { - new StyleProperty(Button.StylePropertyStyleBox, buttonNormal), + new StyleProperty(Button.StylePropertyModulateSelf, ButtonColorDefault), }), new StyleRule( new SelectorElement(typeof(GameHud.TopButton), null, null, new[] {Button.StylePseudoClassPressed}), new[] { - new StyleProperty(Button.StylePropertyStyleBox, buttonPressed), + new StyleProperty(Button.StylePropertyModulateSelf, ButtonColorPressed), }), new StyleRule( new SelectorElement(typeof(GameHud.TopButton), null, null, new[] {Button.StylePseudoClassHover}), new[] { - new StyleProperty(Button.StylePropertyStyleBox, buttonHover), + new StyleProperty(Button.StylePropertyModulateSelf, ButtonColorHovered), }), new StyleRule( @@ -758,21 +722,25 @@ namespace Content.Client.UserInterface.Stylesheets }), // OptionButton + new StyleRule(new SelectorElement(typeof(OptionButton), null, null, null), new[] + { + new StyleProperty(ContainerButton.StylePropertyStyleBox, BaseButton), + }), new StyleRule(new SelectorElement(typeof(OptionButton), null, null, new[] {ContainerButton.StylePseudoClassNormal}), new[] { - new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonNormal), + new StyleProperty(Control.StylePropertyModulateSelf, ButtonColorDefault), }), new StyleRule(new SelectorElement(typeof(OptionButton), null, null, new[] {ContainerButton.StylePseudoClassHover}), new[] { - new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonHover), + new StyleProperty(Control.StylePropertyModulateSelf, ButtonColorHovered), }), new StyleRule(new SelectorElement(typeof(OptionButton), null, null, new[] {ContainerButton.StylePseudoClassPressed}), new[] { - new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonPressed), + new StyleProperty(Control.StylePropertyModulateSelf, ButtonColorPressed), }), new StyleRule(new SelectorElement(typeof(OptionButton), null, null, new[] {ContainerButton.StylePseudoClassDisabled}), new[] { - new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonDisabled), + new StyleProperty(Control.StylePropertyModulateSelf, ButtonColorDisabled), }), new StyleRule(new SelectorElement(typeof(TextureRect), new[] {OptionButton.StyleClassOptionTriangle}, null, null), new[] diff --git a/Content.Client/UserInterface/Stylesheets/StyleSpace.cs b/Content.Client/UserInterface/Stylesheets/StyleSpace.cs index 696321ab7c..3411e91032 100644 --- a/Content.Client/UserInterface/Stylesheets/StyleSpace.cs +++ b/Content.Client/UserInterface/Stylesheets/StyleSpace.cs @@ -18,6 +18,11 @@ namespace Content.Client.UserInterface.Stylesheets public static readonly Color ButtonColorPressed = Color.FromHex("#3e6c45"); public static readonly Color ButtonColorDisabled = Color.FromHex("#30313c"); + public static readonly Color ButtonColorCautionDefault = Color.FromHex("#ab3232"); + public static readonly Color ButtonColorCautionHovered = Color.FromHex("#cf2f2f"); + public static readonly Color ButtonColorCautionPressed = Color.FromHex("#3e6c45"); + public static readonly Color ButtonColorCautionDisabled = Color.FromHex("#602a2a"); + public override Stylesheet Stylesheet { get; } public StyleSpace(IResourceCache resCache) : base(resCache) @@ -25,29 +30,6 @@ namespace Content.Client.UserInterface.Stylesheets var notoSans10 = resCache.GetFont("/Textures/Interface/Nano/NotoSans/NotoSans-Regular.ttf", 10); var notoSansBold16 = resCache.GetFont("/Textures/Interface/Nano/NotoSans/NotoSans-Bold.ttf", 16); - static (StyleBox, StyleBox, StyleBox, StyleBox) ButtonPermutations(StyleBoxTexture @base) - { - var normal = new StyleBoxTexture(@base) {Modulate = ButtonColorDefault}; - var hover = new StyleBoxTexture(@base) {Modulate = ButtonColorHovered}; - var pressed = new StyleBoxTexture(@base) {Modulate = ButtonColorPressed}; - var disabled = new StyleBoxTexture(@base) {Modulate = ButtonColorDisabled}; - - return (normal, hover, pressed, disabled); - } - - // Button styles. - var (buttonNormal, buttonHover, buttonPressed, buttonDisabled) - = ButtonPermutations(BaseButton); - - var (buttonRNormal, buttonRHover, buttonRPressed, buttonRDisabled) - = ButtonPermutations(BaseButtonOpenRight); - - var (buttonLNormal, buttonLHover, buttonLPressed, buttonLDisabled) - = ButtonPermutations(BaseButtonOpenLeft); - - var (buttonBNormal, buttonBHover, buttonBPressed, buttonBDisabled) - = ButtonPermutations(BaseButtonOpenBoth); - Stylesheet = new Stylesheet(BaseRules.Concat(new StyleRule[] { Element