]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Rewrite the options menu (#28389)
authorPieter-Jan Briers <pieterjan.briers+git@gmail.com>
Sat, 22 Jun 2024 04:11:14 +0000 (06:11 +0200)
committerGitHub <noreply@github.com>
Sat, 22 Jun 2024 04:11:14 +0000 (14:11 +1000)
* Basic attempt at rewriting how the options menu works, move accessibility settings into their own tab.

* Audio tab uses the new options system.

* Rewrite Misc tab

* Clean up heading styling

* Rewrite options tab and other minor cleanup all over the place.

* Documentation comments and minor cleanup.

---------

Co-authored-by: AJCM <AJCM@tutanota.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
17 files changed:
Content.Client/Options/UI/OptionDropDown.xaml [new file with mode: 0644]
Content.Client/Options/UI/OptionDropDown.xaml.cs [new file with mode: 0644]
Content.Client/Options/UI/OptionSlider.xaml [new file with mode: 0644]
Content.Client/Options/UI/OptionSlider.xaml.cs [new file with mode: 0644]
Content.Client/Options/UI/OptionsMenu.xaml
Content.Client/Options/UI/OptionsMenu.xaml.cs
Content.Client/Options/UI/OptionsTabControlRow.xaml [new file with mode: 0644]
Content.Client/Options/UI/OptionsTabControlRow.xaml.cs [new file with mode: 0644]
Content.Client/Options/UI/Tabs/AccessibilityTab.xaml [new file with mode: 0644]
Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs [new file with mode: 0644]
Content.Client/Options/UI/Tabs/AudioTab.xaml
Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
Content.Client/Options/UI/Tabs/GraphicsTab.xaml
Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs
Content.Client/Options/UI/Tabs/MiscTab.xaml
Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
Resources/Locale/en-US/escape-menu/ui/options-menu.ftl

diff --git a/Content.Client/Options/UI/OptionDropDown.xaml b/Content.Client/Options/UI/OptionDropDown.xaml
new file mode 100644 (file)
index 0000000..58dcdca
--- /dev/null
@@ -0,0 +1,6 @@
+<Control xmlns="https://spacestation14.io">
+    <BoxContainer Orientation="Horizontal">
+        <Label Name="NameLabel" MinWidth="400" />
+        <OptionButton Name="Button" Access="Public" />
+    </BoxContainer>
+</Control>
diff --git a/Content.Client/Options/UI/OptionDropDown.xaml.cs b/Content.Client/Options/UI/OptionDropDown.xaml.cs
new file mode 100644 (file)
index 0000000..506e241
--- /dev/null
@@ -0,0 +1,21 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Options.UI;
+
+/// <summary>
+/// Standard UI control used for drop-downs in the options menu. Intended for use with <see cref="OptionsTabControlRow"/>.
+/// </summary>
+/// <seealso cref="OptionsTabControlRow.AddOptionDropDown{T}"/>
+[GenerateTypedNameReferences]
+public sealed partial class OptionDropDown : Control
+{
+    /// <summary>
+    /// The text describing what this drop-down controls.
+    /// </summary>
+    public string? Title
+    {
+        get => NameLabel.Text;
+        set => NameLabel.Text = value;
+    }
+}
diff --git a/Content.Client/Options/UI/OptionSlider.xaml b/Content.Client/Options/UI/OptionSlider.xaml
new file mode 100644 (file)
index 0000000..fa2d78c
--- /dev/null
@@ -0,0 +1,7 @@
+<Control xmlns="https://spacestation14.io">
+    <BoxContainer Orientation="Horizontal">
+        <Label Name="NameLabel" MinWidth="400" />
+        <Slider Name="Slider" Access="Public" HorizontalExpand="True" />
+        <Label Name="ValueLabel" Access="Public" Margin="8 0 4 0" MinWidth="48" Align="Right" />
+    </BoxContainer>
+</Control>
diff --git a/Content.Client/Options/UI/OptionSlider.xaml.cs b/Content.Client/Options/UI/OptionSlider.xaml.cs
new file mode 100644 (file)
index 0000000..6a377f7
--- /dev/null
@@ -0,0 +1,22 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Options.UI;
+
+/// <summary>
+/// Standard UI control used for sliders in the options menu. Intended for use with <see cref="OptionsTabControlRow"/>.
+/// </summary>
+/// <seealso cref="OptionsTabControlRow.AddOptionSlider"/>
+/// <seealso cref="OptionsTabControlRow.AddOptionPercentSlider"/>
+[GenerateTypedNameReferences]
+public sealed partial class OptionSlider : Control
+{
+    /// <summary>
+    /// The text describing what this slider controls.
+    /// </summary>
+    public string? Title
+    {
+        get => NameLabel.Text;
+        set => NameLabel.Text = value;
+    }
+}
index 4f624c1bb69a68da869825622a5bb9bb4cbf0e3e..90486a196ad1961dbbff5ac813a2a06a34691a47 100644 (file)
@@ -7,5 +7,6 @@
         <tabs:GraphicsTab Name="GraphicsTab" />
         <tabs:KeyRebindTab Name="KeyRebindTab" />
         <tabs:AudioTab Name="AudioTab" />
+        <tabs:AccessibilityTab Name="AccessibilityTab" />
     </TabContainer>
 </DefaultWindow>
index 35a3f751bbfe6ee35f38fe29756b272769bc3fb5..61037f4e4afc4caf4cdfade67d4858164622d65c 100644 (file)
@@ -1,9 +1,6 @@
 using Robust.Client.AutoGenerated;
 using Robust.Client.UserInterface.CustomControls;
 using Robust.Client.UserInterface.XAML;
-using Robust.Shared.IoC;
-using Content.Client.Options.UI.Tabs;
-
 
 namespace Content.Client.Options.UI
 {
@@ -19,13 +16,17 @@ namespace Content.Client.Options.UI
             Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics"));
             Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls"));
             Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
+            Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
 
             UpdateTabs();
         }
 
         public void UpdateTabs()
         {
-            GraphicsTab.UpdateProperties();
+            GraphicsTab.Control.ReloadValues();
+            MiscTab.Control.ReloadValues();
+            AccessibilityTab.Control.ReloadValues();
+            AudioTab.Control.ReloadValues();
         }
     }
 }
diff --git a/Content.Client/Options/UI/OptionsTabControlRow.xaml b/Content.Client/Options/UI/OptionsTabControlRow.xaml
new file mode 100644 (file)
index 0000000..fafdee4
--- /dev/null
@@ -0,0 +1,18 @@
+<Control xmlns="https://spacestation14.io"
+         xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
+    <controls:StripeBack HasBottomEdge="False">
+        <BoxContainer Orientation="Horizontal" Align="End" Margin="2">
+            <Button Name="DefaultButton"
+                    Text="{Loc 'ui-options-default'}"
+                    TextAlign="Center"
+                    Margin="8 0" />
+
+            <Button Name="ResetButton"
+                    Text="{Loc 'ui-options-reset-all'}"
+                    StyleClasses="Caution" />
+            <Button Name="ApplyButton"
+                    Text="{Loc 'ui-options-apply'}"
+                    StyleClasses="OpenLeft" />
+        </BoxContainer>
+    </controls:StripeBack>
+</Control>
diff --git a/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs b/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs
new file mode 100644 (file)
index 0000000..31dd989
--- /dev/null
@@ -0,0 +1,684 @@
+using System.Linq;
+using Content.Client.Stylesheets;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Collections;
+using Robust.Shared.Configuration;
+
+namespace Content.Client.Options.UI;
+
+/// <summary>
+/// Control used on all tabs of the in-game options menu,
+/// contains the "save" and "reset" buttons and controls the entire logic.
+/// </summary>
+/// <remarks>
+/// <para>
+/// Basic operation is simple: options tabs put this control at the bottom of the tab,
+/// they bind UI controls to it with calls such as <see cref="AddOptionCheckBox"/>,
+/// then they call <see cref="Initialize"/>. The rest is all handled by the control.
+/// </para>
+/// <para>
+/// Individual options are implementations of <see cref="BaseOption"/>. See the type for details.
+/// Common implementations for building on top of CVars are already exist,
+/// but tabs can define their own if they need to.
+/// </para>
+/// <para>
+/// Generally, options are added via helper methods such as <see cref="AddOptionCheckBox"/>,
+/// however it is totally possible to directly instantiate the backing types
+/// and add them via <see cref="AddOption{T}"/>.
+/// </para>
+/// <para>
+/// The options system is general purpose enough that <see cref="OptionsTabControlRow"/> does not, itself,
+/// know what a CVar is. It does automatically save CVars to config when save is pressed, but otherwise CVar interaction
+/// is handled by <see cref="BaseOption"/> implementations.
+/// </para>
+/// <para>
+/// Behaviorally, the row has 3 control buttons: save, reset changed, and reset to default.
+/// "Save" writes the configuration changes and saves the configuration.
+/// "Reset changed" discards changes made in the menu and re-loads the saved settings.
+/// "Reset to default" resets the settings on the menu to be the default, out-of-the-box values.
+/// Note that "Reset to default" does not save immediately, the user must still press save manually.
+/// </para>
+/// <para>
+/// The disabled state of the 3 buttons is updated dynamically based on the values of the options.
+/// </para>
+/// </remarks>
+[GenerateTypedNameReferences]
+public sealed partial class OptionsTabControlRow : Control
+{
+    [Dependency] private readonly ILocalizationManager _loc = default!;
+    [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+    private ValueList<BaseOption> _options;
+
+    public OptionsTabControlRow()
+    {
+        RobustXamlLoader.Load(this);
+        IoCManager.InjectDependencies(this);
+
+        ResetButton.StyleClasses.Add(StyleBase.ButtonOpenRight);
+        ApplyButton.OnPressed += ApplyButtonPressed;
+        ResetButton.OnPressed += ResetButtonPressed;
+        DefaultButton.OnPressed += DefaultButtonPressed;
+    }
+
+    /// <summary>
+    /// Add a new option to be tracked by the control.
+    /// </summary>
+    /// <param name="option">The option object that manages this object's logic</param>
+    /// <typeparam name="T">
+    /// The type of option being passed in. Necessary to allow the return type to match the parameter type
+    /// for easy chaining.
+    /// </typeparam>
+    /// <returns>The same <paramref name="option"/> as passed in, for easy chaining.</returns>
+    public T AddOption<T>(T option) where T : BaseOption
+    {
+        _options.Add(option);
+        return option;
+    }
+
+    /// <summary>
+    /// Add a checkbox option backed by a simple boolean CVar.
+    /// </summary>
+    /// <param name="cVar">The CVar represented by the checkbox.</param>
+    /// <param name="checkBox">The UI control for the option.</param>
+    /// <param name="invert">
+    /// If true, the checkbox is inverted relative to the CVar: if the CVar is true, the checkbox will be unchecked.
+    /// </param>
+    /// <returns>The option instance backing the added option.</returns>
+    /// <seealso cref="OptionCheckboxCVar"/>
+    public OptionCheckboxCVar AddOptionCheckBox(CVarDef<bool> cVar, CheckBox checkBox, bool invert = false)
+    {
+        return AddOption(new OptionCheckboxCVar(this, _cfg, cVar, checkBox, invert));
+    }
+
+    /// <summary>
+    /// Add a slider option, displayed in percent, backed by a simple float CVar.
+    /// </summary>
+    /// <param name="cVar">The CVar represented by the slider.</param>
+    /// <param name="slider">The UI control for the option.</param>
+    /// <param name="min">The minimum value the slider should allow. The default value represents "0%"</param>
+    /// <param name="max">The maximum value the slider should allow. The default value represents "100%"</param>
+    /// <param name="scale">
+    /// Scale with which to multiply slider values when mapped to the backing CVar.
+    /// For example, if a scale of 2 is set, a slider at 75% writes a value of 1.5 to the CVar.
+    /// </param>
+    /// <returns>The option instance backing the added option.</returns>
+    /// <remarks>
+    /// <para>
+    /// Note that percentage values are represented as ratios in code, i.e. a value of 100% is "1".
+    /// </para>
+    /// </remarks>
+    public OptionSliderFloatCVar AddOptionPercentSlider(
+        CVarDef<float> cVar,
+        OptionSlider slider,
+        float min = 0,
+        float max = 1,
+        float scale = 1)
+    {
+        return AddOption(new OptionSliderFloatCVar(this, _cfg, cVar, slider, min, max, scale, FormatPercent));
+    }
+
+    /// <summary>
+    /// Add a slider option, backed by a simple integer CVar.
+    /// </summary>
+    /// <param name="cVar">The CVar represented by the slider.</param>
+    /// <param name="slider">The UI control for the option.</param>
+    /// <param name="min">The minimum value the slider should allow.</param>
+    /// <param name="max">The maximum value the slider should allow.</param>
+    /// <param name="format">
+    /// An optional delegate used to format the textual value display of the slider.
+    /// If not provided, the default behavior is to directly format the integer value as text.
+    /// </param>
+    /// <returns>The option instance backing the added option.</returns>
+    public OptionSliderIntCVar AddOptionSlider(
+        CVarDef<int> cVar,
+        OptionSlider slider,
+        int min,
+        int max,
+        Func<OptionSliderIntCVar, int, string>? format = null)
+    {
+        return AddOption(new OptionSliderIntCVar(this, _cfg, cVar, slider, min, max, format ?? FormatInt));
+    }
+
+    /// <summary>
+    /// Add a drop-down option, backed by a CVar.
+    /// </summary>
+    /// <param name="cVar">The CVar represented by the drop-down.</param>
+    /// <param name="dropDown">The UI control for the option.</param>
+    /// <param name="options">
+    /// The set of options that will be shown in the drop-down. Items are ordered as provided.
+    /// </param>
+    /// <typeparam name="T">The type of the CVar being controlled.</typeparam>
+    /// <returns>The option instance backing the added option.</returns>
+    public OptionDropDownCVar<T> AddOptionDropDown<T>(
+        CVarDef<T> cVar,
+        OptionDropDown dropDown,
+        IReadOnlyCollection<OptionDropDownCVar<T>.ValueOption> options)
+        where T : notnull
+    {
+        return AddOption(new OptionDropDownCVar<T>(this, _cfg, cVar, dropDown, options));
+    }
+
+    /// <summary>
+    /// Initializes the control row. This should be called after all options have been added.
+    /// </summary>
+    public void Initialize()
+    {
+        foreach (var option in _options)
+        {
+            option.LoadValue();
+        }
+
+        UpdateButtonState();
+    }
+
+    /// <summary>
+    /// Re-loads options in the settings from backing values.
+    /// Should be called when the options window is opened to make sure all values are up-to-date.
+    /// </summary>
+    public void ReloadValues()
+    {
+        Initialize();
+    }
+
+    /// <summary>
+    /// Called by <see cref="BaseOption"/> to signal that an option's value changed through user interaction.
+    /// </summary>
+    /// <remarks>
+    /// <see cref="BaseOption"/> implementations should not call this function directly,
+    /// instead they should call <see cref="BaseOption.ValueChanged"/>.
+    /// </remarks>
+    public void ValueChanged()
+    {
+        UpdateButtonState();
+    }
+
+    private void UpdateButtonState()
+    {
+        var anyModified = _options.Any(option => option.IsModified());
+        var anyModifiedFromDefault = _options.Any(option => option.IsModifiedFromDefault());
+
+        DefaultButton.Disabled = !anyModifiedFromDefault;
+        ApplyButton.Disabled = !anyModified;
+        ResetButton.Disabled = !anyModified;
+    }
+
+    private void ApplyButtonPressed(BaseButton.ButtonEventArgs obj)
+    {
+        foreach (var option in _options)
+        {
+            if (option.IsModified())
+                option.SaveValue();
+        }
+
+        _cfg.SaveToFile();
+        UpdateButtonState();
+    }
+
+    private void ResetButtonPressed(BaseButton.ButtonEventArgs obj)
+    {
+        foreach (var option in _options)
+        {
+            option.LoadValue();
+        }
+
+        UpdateButtonState();
+    }
+
+    private void DefaultButtonPressed(BaseButton.ButtonEventArgs obj)
+    {
+        foreach (var option in _options)
+        {
+            option.ResetToDefault();
+        }
+
+        UpdateButtonState();
+    }
+
+    private string FormatPercent(OptionSliderFloatCVar slider, float value)
+    {
+        return _loc.GetString("ui-options-value-percent", ("value", value));
+    }
+
+    private static string FormatInt(OptionSliderIntCVar slider, int value)
+    {
+        return value.ToString();
+    }
+}
+
+/// <summary>
+/// Base class of a single "option" for <see cref="OptionsTabControlRow"/>.
+/// </summary>
+/// <remarks>
+/// <para>
+/// Implementations of this class handle loading values from backing storage or defaults,
+/// handling UI controls, and saving. The main <see cref="OptionsTabControlRow"/> does not know what a CVar is.
+/// </para>
+/// <para>
+/// <see cref="BaseOptionCVar{TValue}"/> is a derived class that makes it easier to work with options
+/// backed by a single CVar.
+/// </para>
+/// </remarks>
+/// <param name="controller">The control row that owns this option.</param>
+/// <seealso cref="OptionsTabControlRow"/>
+public abstract class BaseOption(OptionsTabControlRow controller)
+{
+    /// <summary>
+    /// Should be called by derived implementations to indicate that their value changed, due to user interaction.
+    /// </summary>
+    protected virtual void ValueChanged()
+    {
+        controller.ValueChanged();
+    }
+
+    /// <summary>
+    /// Loads the value represented by this option from its backing store, into the UI state.
+    /// </summary>
+    public abstract void LoadValue();
+
+    /// <summary>
+    /// Saves the value in the UI state to the backing store.
+    /// </summary>
+    public abstract void SaveValue();
+
+    /// <summary>
+    /// Resets the UI state to that of the factory-default value. This should not write to the backing store.
+    /// </summary>
+    public abstract void ResetToDefault();
+
+    /// <summary>
+    /// Called to check if this option's UI value is different from the backing store value.
+    /// </summary>
+    /// <returns>If true, the UI value is different and was modified by the user.</returns>
+    public abstract bool IsModified();
+
+    /// <summary>
+    /// Called to check if this option's UI value is different from the backing store's default value.
+    /// </summary>
+    /// <returns>If true, the UI value is different.</returns>
+    public abstract bool IsModifiedFromDefault();
+}
+
+/// <summary>
+/// Derived class of <see cref="BaseOption"/> intended for making mappings to simple CVars easier.
+/// </summary>
+/// <typeparam name="TValue">The type of the CVar.</typeparam>
+/// <seealso cref="OptionsTabControlRow"/>
+public abstract class BaseOptionCVar<TValue> : BaseOption
+    where TValue : notnull
+{
+    /// <summary>
+    /// Raised immediately when the UI value of this option is changed by the user, even before saving.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// This can be used to update parts of the options UI based on the state of a checkbox.
+    /// </para>
+    /// </remarks>
+    public event Action<TValue>? ImmediateValueChanged;
+
+    private readonly IConfigurationManager _cfg;
+    private readonly CVarDef<TValue> _cVar;
+
+    /// <summary>
+    /// Sets and gets the actual CVar value to/from the frontend UI state or control.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// In the simplest case, this function should set a UI control's state to represent the CVar,
+    /// and inversely conver the UI control's state to the CVar value. For simple controls like a checkbox or slider,
+    /// this just means passing through their value property.
+    /// </para>
+    /// </remarks>
+    protected abstract TValue Value { get; set; }
+
+    protected BaseOptionCVar(
+        OptionsTabControlRow controller,
+        IConfigurationManager cfg,
+        CVarDef<TValue> cVar)
+        : base(controller)
+    {
+        _cfg = cfg;
+        _cVar = cVar;
+    }
+
+    public override void LoadValue()
+    {
+        Value = _cfg.GetCVar(_cVar);
+    }
+
+    public override void SaveValue()
+    {
+        _cfg.SetCVar(_cVar, Value);
+    }
+
+    public override void ResetToDefault()
+    {
+        Value = _cVar.DefaultValue;
+    }
+
+    public override bool IsModified()
+    {
+        return !IsValueEqual(Value, _cfg.GetCVar(_cVar));
+    }
+
+    public override bool IsModifiedFromDefault()
+    {
+        return !IsValueEqual(Value, _cVar.DefaultValue);
+    }
+
+    protected virtual bool IsValueEqual(TValue a, TValue b)
+    {
+        // Use different logic for floats so there's some error margin.
+        // This check is handled cleanly at compile-time by the JIT.
+        if (typeof(TValue) == typeof(float))
+            return MathHelper.CloseToPercent((float) (object) a, (float) (object) b);
+
+        return EqualityComparer<TValue>.Default.Equals(a, b);
+    }
+
+    protected override void ValueChanged()
+    {
+        base.ValueChanged();
+
+        ImmediateValueChanged?.Invoke(Value);
+    }
+}
+
+/// <summary>
+/// Implementation of a CVar option that simply corresponds with a <see cref="CheckBox"/>.
+/// </summary>
+/// <remarks>
+/// <para>
+/// Generally, you should just call <c>AddOption</c> methods on <see cref="OptionsTabControlRow"/>
+/// instead of instantiating this type directly.
+/// </para>
+/// </remarks>
+/// <seealso cref="OptionsTabControlRow"/>
+public sealed class OptionCheckboxCVar : BaseOptionCVar<bool>
+{
+    private readonly CheckBox _checkBox;
+    private readonly bool _invert;
+
+    protected override bool Value
+    {
+        get => _checkBox.Pressed ^ _invert;
+        set => _checkBox.Pressed = value ^ _invert;
+    }
+
+    /// <summary>
+    /// Creates a new instance of this type.
+    /// </summary>
+    /// <param name="controller">The control row that owns this option.</param>
+    /// <param name="cfg">The configuration manager to get and set values from.</param>
+    /// <param name="cVar">The CVar that is being controlled by this option.</param>
+    /// <param name="checkBox">The UI control for the option.</param>
+    /// <param name="invert">
+    /// If true, the checkbox is inverted relative to the CVar: if the CVar is true, the checkbox will be unchecked.
+    /// </param>
+    /// <remarks>
+    /// <para>
+    /// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
+    /// such as <see cref="OptionsTabControlRow.AddOptionCheckBox"/> instead of instantiating this type directly.
+    /// </para>
+    /// </remarks>
+    public OptionCheckboxCVar(
+        OptionsTabControlRow controller,
+        IConfigurationManager cfg,
+        CVarDef<bool> cVar,
+        CheckBox checkBox,
+        bool invert)
+        : base(controller, cfg, cVar)
+    {
+        _checkBox = checkBox;
+        _invert = invert;
+        checkBox.OnToggled += _ =>
+        {
+            ValueChanged();
+        };
+    }
+}
+
+/// <summary>
+/// Implementation of a CVar option that simply corresponds with a floating-point <see cref="OptionSlider"/>.
+/// </summary>
+/// <seealso cref="OptionsTabControlRow"/>
+public sealed class OptionSliderFloatCVar : BaseOptionCVar<float>
+{
+    /// <summary>
+    /// Scale with which to multiply slider values when mapped to the backing CVar.
+    /// </summary>
+    /// <remarks>
+    /// For example, if a scale of 2 is set, a slider at 75% writes a value of 1.5 to the CVar.
+    /// </remarks>
+    public float Scale { get; }
+
+    private readonly OptionSlider _slider;
+    private readonly Func<OptionSliderFloatCVar, float, string> _format;
+
+    protected override float Value
+    {
+        get => _slider.Slider.Value * Scale;
+        set
+        {
+            _slider.Slider.Value = value / Scale;
+            UpdateLabelValue();
+        }
+    }
+
+    /// <summary>
+    /// Creates a new instance of this type.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
+    /// such as <see cref="OptionsTabControlRow.AddOptionPercentSlider"/> instead of instantiating this type directly.
+    /// </para>
+    /// </remarks>
+    /// <param name="controller">The control row that owns this option.</param>
+    /// <param name="cfg">The configuration manager to get and set values from.</param>
+    /// <param name="cVar">The CVar that is being controlled by this option.</param>
+    /// <param name="slider">The UI control for the option.</param>
+    /// <param name="minValue">The minimum value the slider should allow.</param>
+    /// <param name="maxValue">The maximum value the slider should allow.</param>
+    /// <param name="scale">
+    /// Scale with which to multiply slider values when mapped to the backing CVar. See <see cref="Scale"/>.
+    /// </param>
+    /// <param name="format">Function that will be called to format the value display next to the slider.</param>
+    public OptionSliderFloatCVar(
+        OptionsTabControlRow controller,
+        IConfigurationManager cfg,
+        CVarDef<float> cVar,
+        OptionSlider slider,
+        float minValue,
+        float maxValue,
+        float scale,
+        Func<OptionSliderFloatCVar, float, string> format) : base(controller, cfg, cVar)
+    {
+        Scale = scale;
+        _slider = slider;
+        _format = format;
+
+        slider.Slider.MinValue = minValue;
+        slider.Slider.MaxValue = maxValue;
+
+        slider.Slider.OnValueChanged += _ =>
+        {
+            ValueChanged();
+            UpdateLabelValue();
+        };
+    }
+
+    private void UpdateLabelValue()
+    {
+        _slider.ValueLabel.Text = _format(this, _slider.Slider.Value);
+    }
+}
+
+/// <summary>
+/// Implementation of a CVar option that simply corresponds with an integer <see cref="OptionSlider"/>.
+/// </summary>
+/// <seealso cref="OptionsTabControlRow"/>
+public sealed class OptionSliderIntCVar : BaseOptionCVar<int>
+{
+    private readonly OptionSlider _slider;
+    private readonly Func<OptionSliderIntCVar, int, string> _format;
+
+    protected override int Value
+    {
+        get => (int) _slider.Slider.Value;
+        set
+        {
+            _slider.Slider.Value = value;
+            UpdateLabelValue();
+        }
+    }
+
+    /// <summary>
+    /// Creates a new instance of this type.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
+    /// such as <see cref="OptionsTabControlRow.AddOptionPercentSlider"/> instead of instantiating this type directly.
+    /// </para>
+    /// </remarks>
+    /// <param name="controller">The control row that owns this option.</param>
+    /// <param name="cfg">The configuration manager to get and set values from.</param>
+    /// <param name="cVar">The CVar that is being controlled by this option.</param>
+    /// <param name="slider">The UI control for the option.</param>
+    /// <param name="minValue">The minimum value the slider should allow.</param>
+    /// <param name="maxValue">The maximum value the slider should allow.</param>
+    /// <param name="format">Function that will be called to format the value display next to the slider.</param>
+    public OptionSliderIntCVar(
+        OptionsTabControlRow controller,
+        IConfigurationManager cfg,
+        CVarDef<int> cVar,
+        OptionSlider slider,
+        int minValue,
+        int maxValue,
+        Func<OptionSliderIntCVar, int, string> format) : base(controller, cfg, cVar)
+    {
+        _slider = slider;
+        _format = format;
+
+        slider.Slider.MinValue = minValue;
+        slider.Slider.MaxValue = maxValue;
+        slider.Slider.Rounded = true;
+
+        slider.Slider.OnValueChanged += _ =>
+        {
+            ValueChanged();
+            UpdateLabelValue();
+        };
+    }
+
+    private void UpdateLabelValue()
+    {
+        _slider.ValueLabel.Text = _format(this, (int) _slider.Slider.Value);
+    }
+}
+
+/// <summary>
+/// Implementation of a CVar option via a drop-down.
+/// </summary>
+/// <seealso cref="OptionsTabControlRow"/>
+public sealed class OptionDropDownCVar<T> : BaseOptionCVar<T> where T : notnull
+{
+    private readonly OptionDropDown _dropDown;
+    private readonly ItemEntry[] _entries;
+
+    protected override T Value
+    {
+        get => (T) _dropDown.Button.SelectedMetadata!;
+        set => _dropDown.Button.SelectId(FindValueId(value));
+    }
+
+    /// <summary>
+    /// Creates a new instance of this type.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
+    /// such as <see cref="OptionsTabControlRow.AddOptionDropDown{T}"/> instead of instantiating this type directly.
+    /// </para>
+    /// </remarks>
+    /// <param name="controller">The control row that owns this option.</param>
+    /// <param name="cfg">The configuration manager to get and set values from.</param>
+    /// <param name="cVar">The CVar that is being controlled by this option.</param>
+    /// <param name="dropDown">The UI control for the option.</param>
+    /// <param name="options">The list of options shown to the user.</param>
+    public OptionDropDownCVar(
+        OptionsTabControlRow controller,
+        IConfigurationManager cfg,
+        CVarDef<T> cVar,
+        OptionDropDown dropDown,
+        IReadOnlyCollection<ValueOption> options) : base(controller, cfg, cVar)
+    {
+        if (options.Count == 0)
+            throw new ArgumentException("Need at least one option!");
+
+        _dropDown = dropDown;
+        _entries = new ItemEntry[options.Count];
+
+        var button = dropDown.Button;
+        var i = 0;
+        foreach (var option in options)
+        {
+            _entries[i] = new ItemEntry
+            {
+                Key = option.Key,
+            };
+
+            button.AddItem(option.Label, i);
+            button.SetItemMetadata(button.GetIdx(i), option.Key);
+            i += 1;
+        }
+
+        dropDown.Button.OnItemSelected += args =>
+        {
+            dropDown.Button.SelectId(args.Id);
+            ValueChanged();
+        };
+    }
+
+    private int FindValueId(T value)
+    {
+        for (var i = 0; i < _entries.Length; i++)
+        {
+            if (IsValueEqual(_entries[i].Key, value))
+                return i;
+        }
+
+        // This will just default select the first entry or whatever.
+        return 0;
+    }
+
+    /// <summary>
+    /// A single option for a drop-down.
+    /// </summary>
+    /// <param name="key">The value that this option has. This is what will be written to the CVar if selected.</param>
+    /// <param name="label">The visual text shown to the user for the option.</param>
+    /// <seealso cref="OptionDropDownCVar{T}"/>
+    /// <seealso cref="OptionsTabControlRow.AddOptionDropDown{T}"/>
+    public sealed class ValueOption(T key, string label)
+    {
+        /// <summary>
+        /// The value that this option has. This is what will be written to the CVar if selected.
+        /// </summary>
+        public readonly T Key = key;
+
+        /// <summary>
+        /// The visual text shown to the user for the option.
+        /// </summary>
+        public readonly string Label = label;
+    }
+
+    private struct ItemEntry
+    {
+        public T Key;
+    }
+}
diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
new file mode 100644 (file)
index 0000000..54d92b2
--- /dev/null
@@ -0,0 +1,16 @@
+<Control xmlns="https://spacestation14.io"
+         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+         xmlns:ui="clr-namespace:Content.Client.Options.UI">
+    <BoxContainer Orientation="Vertical">
+        <ScrollContainer VerticalExpand="True" HScrollEnabled="False">
+            <BoxContainer Orientation="Vertical" Margin="8">
+                <CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
+                <CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
+                <CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" />
+                <ui:OptionSlider Name="ChatWindowOpacitySlider" Title="{Loc 'ui-options-chat-window-opacity'}" />
+                <ui:OptionSlider Name="ScreenShakeIntensitySlider" Title="{Loc 'ui-options-screen-shake-intensity'}" />
+            </BoxContainer>
+        </ScrollContainer>
+        <ui:OptionsTabControlRow Name="Control" Access="Public" />
+    </BoxContainer>
+</Control>
diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs
new file mode 100644 (file)
index 0000000..15182fb
--- /dev/null
@@ -0,0 +1,24 @@
+using Content.Shared.CCVar;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Options.UI.Tabs;
+
+[GenerateTypedNameReferences]
+public sealed partial class AccessibilityTab : Control
+{
+    public AccessibilityTab()
+    {
+        RobustXamlLoader.Load(this);
+
+        Control.AddOptionCheckBox(CCVars.ChatEnableColorName, EnableColorNameCheckBox);
+        Control.AddOptionCheckBox(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox);
+        Control.AddOptionCheckBox(CCVars.ReducedMotion, ReducedMotionCheckBox);
+        Control.AddOptionPercentSlider(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider);
+        Control.AddOptionPercentSlider(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider);
+
+        Control.Initialize();
+    }
+}
+
index e54b0dc34ee845da5bb2d0d54a7f633b695cd64a..c374af31c588fb304d66ffb2ea5b7f1e48685d1e 100644 (file)
 <Control xmlns="https://spacestation14.io"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-         xmlns:s="clr-namespace:Content.Client.Stylesheets"
-         xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
+         xmlns:ui="clr-namespace:Content.Client.Options.UI">
     <BoxContainer Orientation="Vertical">
         <BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
             <Label Text="{Loc 'ui-options-volume-label'}"
-                   FontColorOverride="{x:Static s:StyleNano.NanoGold}"
                    StyleClasses="LabelKeyText"/>
             <BoxContainer Orientation="Vertical" Margin="0 3 0 0">
-                <BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
-                    <Label Text="{Loc 'ui-options-master-volume'}" HorizontalExpand="True" />
-                    <Control MinSize="8 0" />
-                    <Slider Name="MasterVolumeSlider"
-                            MinValue="0"
-                            MaxValue="100"
-                            HorizontalExpand="True"
-                            MinSize="80 0"
-                            Rounded="True" />
-                    <Control MinSize="8 0" />
-                    <Label Name="MasterVolumeLabel" MinSize="48 0" Align="Right" />
-                    <Control MinSize="4 0"/>
-                </BoxContainer>
-                <Control MinSize="0 8" />
-                <BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
-                    <Label Text="{Loc 'ui-options-midi-volume'}" HorizontalExpand="True" />
-                    <Control MinSize="8 0" />
-                    <Slider Name="MidiVolumeSlider"
-                            MinValue="0"
-                            MaxValue="100"
-                            HorizontalExpand="True"
-                            MinSize="80 0"
-                            Rounded="True" />
-                    <Control MinSize="8 0" />
-                    <Label Name="MidiVolumeLabel" MinSize="48 0" Align="Right" />
-                    <Control MinSize="4 0"/>
-                </BoxContainer>
-                <BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
-                    <Label Text="{Loc 'ui-options-ambient-music-volume'}" HorizontalExpand="True" />
-                    <Control MinSize="8 0" />
-                    <Slider Name="AmbientMusicVolumeSlider"
-                            MinValue="0"
-                            MaxValue="100"
-                            HorizontalExpand="True"
-                            MinSize="80 0"
-                            Rounded="True" />
-                    <Control MinSize="8 0" />
-                    <Label Name="AmbientMusicVolumeLabel" MinSize="48 0" Align="Right" />
-                    <Control MinSize="4 0"/>
-                </BoxContainer>
-                <BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
-                    <Label Text="{Loc 'ui-options-ambience-volume'}" HorizontalExpand="True" />
-                    <Control MinSize="8 0" />
-                    <Slider Name="AmbienceVolumeSlider"
-                            MinValue="0"
-                            MaxValue="100"
-                            HorizontalExpand="True"
-                            MinSize="80 0"
-                            Rounded="True" />
-                    <Control MinSize="8 0" />
-                    <Label Name="AmbienceVolumeLabel" MinSize="48 0" Align="Right" />
-                    <Control MinSize="4 0"/>
-                </BoxContainer>
-                <BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
-                    <Label Text="{Loc 'ui-options-lobby-volume'}" HorizontalExpand="True" />
-                    <Control MinSize="8 0" />
-                    <Slider Name="LobbyVolumeSlider"
-                            MinValue="0"
-                            MaxValue="100"
-                            HorizontalExpand="True"
-                            MinSize="80 0"
-                            Rounded="True" />
-                    <Control MinSize="8 0" />
-                    <Label Name="LobbyVolumeLabel" MinSize="48 0" Align="Right" />
-                    <Control MinSize="4 0"/>
-                </BoxContainer>
-                <BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
-                    <Label Text="{Loc 'ui-options-interface-volume'}" HorizontalExpand="True" />
-                    <Control MinSize="8 0" />
-                    <Slider Name="InterfaceVolumeSlider"
-                            MinValue="0"
-                            MaxValue="100"
-                            HorizontalExpand="True"
-                            MinSize="80 0"
-                            Rounded="True" />
-                    <Control MinSize="8 0" />
-                    <Label Name="InterfaceVolumeLabel" MinSize="48 0" Align="Right" />
-                    <Control MinSize="4 0"/>
-                </BoxContainer>
-                <BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
-                    <Label Text="{Loc 'ui-options-ambience-max-sounds'}" HorizontalExpand="True" />
-                    <Control MinSize="8 0" />
-                    <Slider Name="AmbienceSoundsSlider"
-                            MinValue="0"
-                            MaxValue="1"
-                            HorizontalExpand="True"
-                            MinSize="80 0"
-                            Rounded="True" />
-                    <Control MinSize="8 0" />
-                    <Label Name="AmbienceSoundsLabel" MinSize="48 0" Align="Right" />
-                    <Control MinSize="4 0"/>
-                </BoxContainer>
-                <Control MinSize="0 8" />
+                <ui:OptionSlider Name="SliderVolumeMaster" Title="{Loc 'ui-options-master-volume'}"
+                                 Margin="0 0 0 8" />
+                <ui:OptionSlider Name="SliderVolumeMidi" Title="{Loc 'ui-options-midi-volume'}" />
+                <ui:OptionSlider Name="SliderVolumeAmbientMusic" Title="{Loc 'ui-options-ambient-music-volume'}" />
+                <ui:OptionSlider Name="SliderVolumeAmbience" Title="{Loc 'ui-options-ambience-volume'}" />
+                <ui:OptionSlider Name="SliderVolumeLobby" Title="{Loc 'ui-options-lobby-volume'}" />
+                <ui:OptionSlider Name="SliderVolumeInterface" Title="{Loc 'ui-options-interface-volume'}" />
+                <ui:OptionSlider Name="SliderMaxAmbienceSounds" Title="{Loc 'ui-options-ambience-max-sounds'}"
+                                 Margin="0 0 0 8" />
                 <CheckBox Name="LobbyMusicCheckBox" Text="{Loc 'ui-options-lobby-music'}" />
                 <CheckBox Name="RestartSoundsCheckBox" Text="{Loc 'ui-options-restart-sounds'}" />
                 <CheckBox Name="EventMusicCheckBox" Text="{Loc 'ui-options-event-music'}" />
                 <CheckBox Name="AdminSoundsCheckBox" Text="{Loc 'ui-options-admin-sounds'}" />
             </BoxContainer>
         </BoxContainer>
-        <controls:StripeBack HasBottomEdge="False" HasMargins="False">
-            <BoxContainer Orientation="Horizontal"
-                          Align="End"
-                          HorizontalExpand="True"
-                          VerticalExpand="True">
-                <Button Name="ResetButton"
-                        Text="{Loc 'ui-options-reset-all'}"
-                        StyleClasses="Caution"
-                        HorizontalExpand="True"
-                        HorizontalAlignment="Right" />
-                <Control MinSize="2 0" />
-                <Button Name="ApplyButton"
-                        Text="{Loc 'ui-options-apply'}"
-                        TextAlign="Center"
-                        HorizontalAlignment="Right" />
-            </BoxContainer>
-        </controls:StripeBack>
+        <ui:OptionsTabControlRow Name="Control" Access="Public" />
     </BoxContainer>
 </Control>
index 470ca7d799d8f67779eec1967382b716b523a2e0..78186d446c76251c0d91049e0026d3bf8710ff5d 100644 (file)
@@ -3,200 +3,72 @@ using Content.Shared.CCVar;
 using Robust.Client.Audio;
 using Robust.Client.AutoGenerated;
 using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.XAML;
 using Robust.Shared;
 using Robust.Shared.Configuration;
-using Range = Robust.Client.UserInterface.Controls.Range;
 
-namespace Content.Client.Options.UI.Tabs
-{
-    [GenerateTypedNameReferences]
-    public sealed partial class AudioTab : Control
-    {
-        [Dependency] private readonly IConfigurationManager _cfg = default!;
-        private readonly IAudioManager _audio;
-
-        public AudioTab()
-        {
-            RobustXamlLoader.Load(this);
-            IoCManager.InjectDependencies(this);
-
-            _audio = IoCManager.Resolve<IAudioManager>();
-            LobbyMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.LobbyMusicEnabled);
-            RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled);
-            EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled);
-            AdminSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.AdminSoundsEnabled);
-
-            ApplyButton.OnPressed += OnApplyButtonPressed;
-            ResetButton.OnPressed += OnResetButtonPressed;
-            MasterVolumeSlider.OnValueChanged += OnMasterVolumeSliderChanged;
-            MidiVolumeSlider.OnValueChanged += OnMidiVolumeSliderChanged;
-            AmbientMusicVolumeSlider.OnValueChanged += OnAmbientMusicVolumeSliderChanged;
-            AmbienceVolumeSlider.OnValueChanged += OnAmbienceVolumeSliderChanged;
-            AmbienceSoundsSlider.OnValueChanged += OnAmbienceSoundsSliderChanged;
-            LobbyVolumeSlider.OnValueChanged += OnLobbyVolumeSliderChanged;
-            InterfaceVolumeSlider.OnValueChanged += OnInterfaceVolumeSliderChanged;
-            LobbyMusicCheckBox.OnToggled += OnLobbyMusicCheckToggled;
-            RestartSoundsCheckBox.OnToggled += OnRestartSoundsCheckToggled;
-            EventMusicCheckBox.OnToggled += OnEventMusicCheckToggled;
-            AdminSoundsCheckBox.OnToggled += OnAdminSoundsCheckToggled;
-
-            AmbienceSoundsSlider.MinValue = _cfg.GetCVar(CCVars.MinMaxAmbientSourcesConfigured);
-            AmbienceSoundsSlider.MaxValue = _cfg.GetCVar(CCVars.MaxMaxAmbientSourcesConfigured);
-
-            Reset();
-        }
-
-        protected override void Dispose(bool disposing)
-        {
-            ApplyButton.OnPressed -= OnApplyButtonPressed;
-            ResetButton.OnPressed -= OnResetButtonPressed;
-            MasterVolumeSlider.OnValueChanged -= OnMasterVolumeSliderChanged;
-            MidiVolumeSlider.OnValueChanged -= OnMidiVolumeSliderChanged;
-            AmbientMusicVolumeSlider.OnValueChanged -= OnAmbientMusicVolumeSliderChanged;
-            AmbienceVolumeSlider.OnValueChanged -= OnAmbienceVolumeSliderChanged;
-            LobbyVolumeSlider.OnValueChanged -= OnLobbyVolumeSliderChanged;
-            InterfaceVolumeSlider.OnValueChanged -= OnInterfaceVolumeSliderChanged;
-            base.Dispose(disposing);
-        }
-
-        private void OnLobbyVolumeSliderChanged(Range obj)
-        {
-            UpdateChanges();
-        }
-
-        private void OnInterfaceVolumeSliderChanged(Range obj)
-        {
-            UpdateChanges();
-        }
-
-        private void OnAmbientMusicVolumeSliderChanged(Range obj)
-        {
-            UpdateChanges();
-        }
-
-        private void OnAmbienceVolumeSliderChanged(Range obj)
-        {
-            UpdateChanges();
-        }
-
-        private void OnAmbienceSoundsSliderChanged(Range obj)
-        {
-            UpdateChanges();
-        }
-
-        private void OnMasterVolumeSliderChanged(Range range)
-        {
-            _audio.SetMasterGain(MasterVolumeSlider.Value / 100f * ContentAudioSystem.MasterVolumeMultiplier);
-            UpdateChanges();
-        }
+namespace Content.Client.Options.UI.Tabs;
 
-        private void OnMidiVolumeSliderChanged(Range range)
-        {
-            UpdateChanges();
-        }
-
-        private void OnLobbyMusicCheckToggled(BaseButton.ButtonEventArgs args)
-        {
-            UpdateChanges();
-        }
-        private void OnRestartSoundsCheckToggled(BaseButton.ButtonEventArgs args)
-        {
-            UpdateChanges();
-        }
-        private void OnEventMusicCheckToggled(BaseButton.ButtonEventArgs args)
-        {
-            UpdateChanges();
-        }
-
-        private void OnAdminSoundsCheckToggled(BaseButton.ButtonEventArgs args)
-        {
-            UpdateChanges();
-        }
-
-        private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
-        {
-            _cfg.SetCVar(CVars.AudioMasterVolume, MasterVolumeSlider.Value / 100f * ContentAudioSystem.MasterVolumeMultiplier);
-            // Want the CVar updated values to have the multiplier applied
-            // For the UI we just display 0-100 still elsewhere
-            _cfg.SetCVar(CVars.MidiVolume, MidiVolumeSlider.Value / 100f * ContentAudioSystem.MidiVolumeMultiplier);
-            _cfg.SetCVar(CCVars.AmbienceVolume, AmbienceVolumeSlider.Value / 100f * ContentAudioSystem.AmbienceMultiplier);
-            _cfg.SetCVar(CCVars.AmbientMusicVolume, AmbientMusicVolumeSlider.Value / 100f * ContentAudioSystem.AmbientMusicMultiplier);
-            _cfg.SetCVar(CCVars.LobbyMusicVolume, LobbyVolumeSlider.Value / 100f * ContentAudioSystem.LobbyMultiplier);
-            _cfg.SetCVar(CCVars.InterfaceVolume, InterfaceVolumeSlider.Value / 100f * ContentAudioSystem.InterfaceMultiplier);
-
-            _cfg.SetCVar(CCVars.MaxAmbientSources, (int)AmbienceSoundsSlider.Value);
-
-            _cfg.SetCVar(CCVars.LobbyMusicEnabled, LobbyMusicCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.RestartSoundsEnabled, RestartSoundsCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.EventMusicEnabled, EventMusicCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.AdminSoundsEnabled, AdminSoundsCheckBox.Pressed);
-            _cfg.SaveToFile();
-            UpdateChanges();
-        }
-
-        private void OnResetButtonPressed(BaseButton.ButtonEventArgs args)
-        {
-            Reset();
-        }
-
-        private void Reset()
-        {
-            MasterVolumeSlider.Value = _cfg.GetCVar(CVars.AudioMasterVolume) * 100f / ContentAudioSystem.MasterVolumeMultiplier;
-            MidiVolumeSlider.Value = _cfg.GetCVar(CVars.MidiVolume) * 100f / ContentAudioSystem.MidiVolumeMultiplier;
-            AmbienceVolumeSlider.Value = _cfg.GetCVar(CCVars.AmbienceVolume) * 100f / ContentAudioSystem.AmbienceMultiplier;
-            AmbientMusicVolumeSlider.Value = _cfg.GetCVar(CCVars.AmbientMusicVolume) * 100f / ContentAudioSystem.AmbientMusicMultiplier;
-            LobbyVolumeSlider.Value = _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier;
-            InterfaceVolumeSlider.Value = _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier;
-
-            AmbienceSoundsSlider.Value = _cfg.GetCVar(CCVars.MaxAmbientSources);
-
-            LobbyMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.LobbyMusicEnabled);
-            RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled);
-            EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled);
-            AdminSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.AdminSoundsEnabled);
-            UpdateChanges();
-        }
+[GenerateTypedNameReferences]
+public sealed partial class AudioTab : Control
+{
+    [Dependency] private readonly IConfigurationManager _cfg = default!;
+    [Dependency] private readonly IAudioManager _audio = default!;
 
-        private void UpdateChanges()
-        {
-            // y'all need jesus.
-            var isMasterVolumeSame =
-                Math.Abs(MasterVolumeSlider.Value - _cfg.GetCVar(CVars.AudioMasterVolume) * 100f / ContentAudioSystem.MasterVolumeMultiplier) < 0.01f;
-            var isMidiVolumeSame =
-                Math.Abs(MidiVolumeSlider.Value - _cfg.GetCVar(CVars.MidiVolume) * 100f / ContentAudioSystem.MidiVolumeMultiplier) < 0.01f;
-            var isAmbientVolumeSame =
-                Math.Abs(AmbienceVolumeSlider.Value - _cfg.GetCVar(CCVars.AmbienceVolume) * 100f / ContentAudioSystem.AmbienceMultiplier) < 0.01f;
-            var isAmbientMusicVolumeSame =
-                Math.Abs(AmbientMusicVolumeSlider.Value - _cfg.GetCVar(CCVars.AmbientMusicVolume) * 100f / ContentAudioSystem.AmbientMusicMultiplier) < 0.01f;
-            var isLobbyVolumeSame =
-                Math.Abs(LobbyVolumeSlider.Value - _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier) < 0.01f;
-            var isInterfaceVolumeSame =
-                Math.Abs(InterfaceVolumeSlider.Value - _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier) < 0.01f;
+    public AudioTab()
+    {
+        RobustXamlLoader.Load(this);
+        IoCManager.InjectDependencies(this);
+
+        var masterVolume = Control.AddOptionPercentSlider(
+            CVars.AudioMasterVolume,
+            SliderVolumeMaster,
+            scale: ContentAudioSystem.MasterVolumeMultiplier);
+        masterVolume.ImmediateValueChanged += OnMasterVolumeSliderChanged;
+
+        Control.AddOptionPercentSlider(
+            CVars.MidiVolume,
+            SliderVolumeMidi,
+            scale: ContentAudioSystem.MidiVolumeMultiplier);
+
+        Control.AddOptionPercentSlider(
+            CCVars.AmbientMusicVolume,
+            SliderVolumeAmbientMusic,
+            scale: ContentAudioSystem.AmbientMusicMultiplier);
+
+        Control.AddOptionPercentSlider(
+            CCVars.AmbienceVolume,
+            SliderVolumeAmbience,
+            scale: ContentAudioSystem.AmbienceMultiplier);
+
+        Control.AddOptionPercentSlider(
+            CCVars.LobbyMusicVolume,
+            SliderVolumeLobby,
+            scale: ContentAudioSystem.LobbyMultiplier);
+
+        Control.AddOptionPercentSlider(
+            CCVars.InterfaceVolume,
+            SliderVolumeInterface,
+            scale: ContentAudioSystem.InterfaceMultiplier);
+
+        Control.AddOptionSlider(
+            CCVars.MaxAmbientSources,
+            SliderMaxAmbienceSounds,
+            _cfg.GetCVar(CCVars.MinMaxAmbientSourcesConfigured),
+            _cfg.GetCVar(CCVars.MaxMaxAmbientSourcesConfigured));
+
+        Control.AddOptionCheckBox(CCVars.LobbyMusicEnabled, LobbyMusicCheckBox);
+        Control.AddOptionCheckBox(CCVars.RestartSoundsEnabled, RestartSoundsCheckBox);
+        Control.AddOptionCheckBox(CCVars.EventMusicEnabled, EventMusicCheckBox);
+        Control.AddOptionCheckBox(CCVars.AdminSoundsEnabled, AdminSoundsCheckBox);
+
+        Control.Initialize();
+    }
 
-            var isAmbientSoundsSame = (int)AmbienceSoundsSlider.Value == _cfg.GetCVar(CCVars.MaxAmbientSources);
-            var isLobbySame = LobbyMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.LobbyMusicEnabled);
-            var isRestartSoundsSame = RestartSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.RestartSoundsEnabled);
-            var isEventSame = EventMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.EventMusicEnabled);
-            var isAdminSoundsSame = AdminSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.AdminSoundsEnabled);
-            var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame && isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
-                                   && isAdminSoundsSame && isLobbyVolumeSame && isInterfaceVolumeSame;
-            ApplyButton.Disabled = isEverythingSame;
-            ResetButton.Disabled = isEverythingSame;
-            MasterVolumeLabel.Text =
-                Loc.GetString("ui-options-volume-percent", ("volume", MasterVolumeSlider.Value / 100));
-            MidiVolumeLabel.Text =
-                Loc.GetString("ui-options-volume-percent", ("volume", MidiVolumeSlider.Value / 100));
-            AmbientMusicVolumeLabel.Text =
-                Loc.GetString("ui-options-volume-percent", ("volume", AmbientMusicVolumeSlider.Value / 100));
-            AmbienceVolumeLabel.Text =
-                Loc.GetString("ui-options-volume-percent", ("volume", AmbienceVolumeSlider.Value / 100));
-            LobbyVolumeLabel.Text =
-                Loc.GetString("ui-options-volume-percent", ("volume", LobbyVolumeSlider.Value / 100));
-            InterfaceVolumeLabel.Text =
-                Loc.GetString("ui-options-volume-percent", ("volume", InterfaceVolumeSlider.Value / 100));
-            AmbienceSoundsLabel.Text = ((int)AmbienceSoundsSlider.Value).ToString();
-        }
+    private void OnMasterVolumeSliderChanged(float value)
+    {
+        // TODO: I was thinking of giving OptionsTabControlRow a flag to "set CVar immediately", but I'm deferring that
+        // until there's a proper system for enforcing people don't close the window with pending changes.
+        _audio.SetMasterGain(value);
     }
 }
index ec1b9aa002f8b02175004ff888c23f7be9b69090..f1b9743cad385b542b6cfd7dd5cd2de522c2299f 100644 (file)
@@ -1,53 +1,38 @@
 <tabs:GraphicsTab xmlns="https://spacestation14.io"
-          xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
-          xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs">
+          xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
+          xmlns:ui="clr-namespace:Content.Client.Options.UI">
     <BoxContainer Orientation="Vertical">
-        <BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
-            <CheckBox Name="VSyncCheckBox" Text="{Loc 'ui-options-vsync'}" />
-            <CheckBox Name="FullscreenCheckBox" Text="{Loc 'ui-options-fullscreen'}" />
-            <BoxContainer Orientation="Horizontal">
-                <Label Text="{Loc 'ui-options-lighting-label'}" />
-                <Control MinSize="4 0" />
-                <OptionButton Name="LightingPresetOption" MinSize="100 0" />
-            </BoxContainer>
-            <BoxContainer Orientation="Horizontal">
-                <Label Text="{Loc 'ui-options-scale-label'}" />
-                <Control MinSize="4 0" />
-                <OptionButton Name="UIScaleOption" />
-            </BoxContainer>
-            <BoxContainer Orientation="Horizontal">
+        <ScrollContainer VerticalExpand="True">
+            <BoxContainer Orientation="Vertical" Margin="8 8 8 8">
+                <!-- Display -->
+                <Label Text="{Loc 'ui-options-display-label'}" StyleClasses="LabelKeyText"/>
+                <CheckBox Name="VSyncCheckBox" Text="{Loc 'ui-options-vsync'}" />
+                <CheckBox Name="FullscreenCheckBox" Text="{Loc 'ui-options-fullscreen'}" />
+
+                <!-- Quality -->
+                <Label Text="{Loc 'ui-options-quality-label'}" StyleClasses="LabelKeyText"/>
+                <ui:OptionDropDown Name="DropDownLightingQuality" Title="{Loc 'ui-options-lighting-label'}" />
+                <CheckBox Name="ViewportLowResCheckBox" Text="{Loc 'ui-options-vp-low-res'}" />
+                <CheckBox Name="ParallaxLowQualityCheckBox" Text="{Loc 'ui-options-parallax-low-quality'}" />
+
+                <!-- Interface -->
+                <Label Text="{Loc 'ui-options-interface-label'}" StyleClasses="LabelKeyText"/>
+                <ui:OptionDropDown Name="DropDownUIScale" Title="{Loc 'ui-options-scale-label'}" />
                 <CheckBox Name="ViewportStretchCheckBox" Text="{Loc 'ui-options-vp-stretch'}" />
-                <BoxContainer Name="ViewportScaleBox" Orientation="Horizontal">
-                    <Label Name="ViewportScaleText" Margin="8 0" />
-                    <Slider Name="ViewportScaleSlider"
-                            MinValue="1"
-                            MaxValue="5"
-                            Rounded="True"
-                            MinWidth="200" />
-                </BoxContainer>
-            </BoxContainer>
-            <BoxContainer Orientation="Horizontal">
-                <Label Name="ViewportWidthSliderDisplay" />
-                <Control MinSize="4 0" />
-                <Slider Name="ViewportWidthSlider"
-                        Rounded="True"
-                        MinWidth="200" />
+                <ui:OptionSlider Name="ViewportScaleSlider" Title="{Loc ui-options-vp-scale}" />
+                <ui:OptionSlider Name="ViewportWidthSlider" Title="{Loc ui-options-vp-width}" />
+                <CheckBox Name="IntegerScalingCheckBox"
+                          Text="{Loc 'ui-options-vp-integer-scaling'}"
+                          ToolTip="{Loc 'ui-options-vp-integer-scaling-tooltip'}" />
+                <CheckBox Name="ViewportVerticalFitCheckBox"
+                          Text="{Loc 'ui-options-vp-vertical-fit'}"
+                          ToolTip="{Loc 'ui-options-vp-vertical-fit-tooltip'}" />
+
+                <!-- Misc -->
+                <Label Text="{Loc 'ui-options-misc-label'}" StyleClasses="LabelKeyText"/>
+                <CheckBox Name="FpsCounterCheckBox" Text="{Loc 'ui-options-fps-counter'}" />
             </BoxContainer>
-            <CheckBox Name="IntegerScalingCheckBox"
-                      Text="{Loc 'ui-options-vp-integer-scaling'}"
-                      ToolTip="{Loc 'ui-options-vp-integer-scaling-tooltip'}" />
-            <CheckBox Name="ViewportVerticalFitCheckBox"
-                      Text="{Loc 'ui-options-vp-vertical-fit'}"
-                      ToolTip="{Loc 'ui-options-vp-vertical-fit-tooltip'}" />
-            <CheckBox Name="ViewportLowResCheckBox" Text="{Loc 'ui-options-vp-low-res'}" />
-            <CheckBox Name="ParallaxLowQualityCheckBox" Text="{Loc 'ui-options-parallax-low-quality'}" />
-            <CheckBox Name="FpsCounterCheckBox" Text="{Loc 'ui-options-fps-counter'}" />
-        </BoxContainer>
-        <controls:StripeBack HasBottomEdge="False" HasMargins="False">
-            <Button Name="ApplyButton"
-                    Text="{Loc 'ui-options-apply'}"
-                    TextAlign="Center"
-                    HorizontalAlignment="Right" />
-        </controls:StripeBack>
+        </ScrollContainer>
+        <ui:OptionsTabControlRow Name="Control" Access="Public" />
     </BoxContainer>
 </tabs:GraphicsTab>
index a22adf3e6329609a08bb6c3b32dee91b7d5b7127..f53a2edd95793bcee7365f18ec7216abe91e9e8d 100644 (file)
@@ -7,220 +7,141 @@ using Robust.Client.UserInterface.XAML;
 using Robust.Shared;
 using Robust.Shared.Configuration;
 
-namespace Content.Client.Options.UI.Tabs
+namespace Content.Client.Options.UI.Tabs;
+
+[GenerateTypedNameReferences]
+public sealed partial class GraphicsTab : Control
 {
-    [GenerateTypedNameReferences]
-    public sealed partial class GraphicsTab : Control
-    {
-        private static readonly float[] UIScaleOptions =
-        {
-            0f,
-            0.75f,
-            1f,
-            1.25f,
-            1.50f,
-            1.75f,
-            2f
-        };
-
-        [Dependency] private readonly IConfigurationManager _cfg = default!;
-
-        public GraphicsTab()
-        {
-            IoCManager.InjectDependencies(this);
-            RobustXamlLoader.Load(this);
-
-            VSyncCheckBox.OnToggled += OnCheckBoxToggled;
-            FullscreenCheckBox.OnToggled += OnCheckBoxToggled;
-
-            LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-very-low"));
-            LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-low"));
-            LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-medium"));
-            LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-high"));
-            LightingPresetOption.OnItemSelected += OnLightingQualityChanged;
-
-            UIScaleOption.AddItem(Loc.GetString("ui-options-scale-auto",
-                                                ("scale", UserInterfaceManager.DefaultUIScale)));
-            UIScaleOption.AddItem(Loc.GetString("ui-options-scale-75"));
-            UIScaleOption.AddItem(Loc.GetString("ui-options-scale-100"));
-            UIScaleOption.AddItem(Loc.GetString("ui-options-scale-125"));
-            UIScaleOption.AddItem(Loc.GetString("ui-options-scale-150"));
-            UIScaleOption.AddItem(Loc.GetString("ui-options-scale-175"));
-            UIScaleOption.AddItem(Loc.GetString("ui-options-scale-200"));
-            UIScaleOption.OnItemSelected += OnUIScaleChanged;
-
-            ViewportStretchCheckBox.OnToggled += _ =>
-            {
-                UpdateViewportScale();
-                UpdateApplyButton();
-            };
+    [Dependency] private readonly IConfigurationManager _cfg = default!;
 
-            ViewportScaleSlider.OnValueChanged += _ =>
-            {
-                UpdateApplyButton();
-                UpdateViewportScale();
-            };
+    public GraphicsTab()
+    {
+        IoCManager.InjectDependencies(this);
+        RobustXamlLoader.Load(this);
+
+        Control.AddOptionCheckBox(CVars.DisplayVSync, VSyncCheckBox);
+        Control.AddOption(new OptionFullscreen(Control, _cfg, FullscreenCheckBox));
+        Control.AddOption(new OptionLightingQuality(Control, _cfg, DropDownLightingQuality));
+
+        Control.AddOptionDropDown(
+            CVars.DisplayUIScale,
+            DropDownUIScale,
+            [
+                new OptionDropDownCVar<float>.ValueOption(
+                    0f,
+                    Loc.GetString("ui-options-scale-auto", ("scale", UserInterfaceManager.DefaultUIScale))),
+                new OptionDropDownCVar<float>.ValueOption(0.75f, Loc.GetString("ui-options-scale-75")),
+                new OptionDropDownCVar<float>.ValueOption(1.00f, Loc.GetString("ui-options-scale-100")),
+                new OptionDropDownCVar<float>.ValueOption(1.25f, Loc.GetString("ui-options-scale-125")),
+                new OptionDropDownCVar<float>.ValueOption(1.50f, Loc.GetString("ui-options-scale-150")),
+                new OptionDropDownCVar<float>.ValueOption(1.75f, Loc.GetString("ui-options-scale-175")),
+                new OptionDropDownCVar<float>.ValueOption(2.00f, Loc.GetString("ui-options-scale-200")),
+            ]);
+
+        var vpStretch = Control.AddOptionCheckBox(CCVars.ViewportStretch, ViewportStretchCheckBox);
+        var vpVertFit = Control.AddOptionCheckBox(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox);
+        Control.AddOptionSlider(
+            CCVars.ViewportFixedScaleFactor,
+            ViewportScaleSlider,
+            1,
+            5,
+            (_, value) => Loc.GetString("ui-options-vp-scale-value", ("scale", value)));
+
+        vpStretch.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
+        vpVertFit.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
+
+        Control.AddOptionSlider(
+            CCVars.ViewportWidth,
+            ViewportWidthSlider,
+            (int)ViewportWidthSlider.Slider.MinValue,
+            (int)ViewportWidthSlider.Slider.MaxValue);
+
+        Control.AddOption(new OptionIntegerScaling(Control, _cfg, IntegerScalingCheckBox));
+        Control.AddOptionCheckBox(CCVars.ViewportScaleRender, ViewportLowResCheckBox, invert: true);
+        Control.AddOptionCheckBox(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox);
+        Control.AddOptionCheckBox(CCVars.HudFpsCounterVisible, FpsCounterCheckBox);
+
+        Control.Initialize();
+
+        _cfg.OnValueChanged(CCVars.ViewportMinimumWidth, _ => UpdateViewportWidthRange());
+        _cfg.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportWidthRange());
+
+        UpdateViewportWidthRange();
+        UpdateViewportSettingsVisibility();
+    }
 
-            ViewportWidthSlider.OnValueChanged += _ =>
-            {
-                UpdateViewportWidthDisplay();
-                UpdateApplyButton();
-            };
+    private void UpdateViewportSettingsVisibility()
+    {
+        ViewportScaleSlider.Visible = !ViewportStretchCheckBox.Pressed;
+        IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
+        ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
+        ViewportWidthSlider.Visible = !ViewportStretchCheckBox.Pressed || !ViewportVerticalFitCheckBox.Pressed;
+    }
 
-            ViewportVerticalFitCheckBox.OnToggled += _ =>
-            {
-                UpdateViewportScale();
-                UpdateApplyButton();
-            };
+    private void UpdateViewportWidthRange()
+    {
+        var min = _cfg.GetCVar(CCVars.ViewportMinimumWidth);
+        var max = _cfg.GetCVar(CCVars.ViewportMaximumWidth);
 
-            IntegerScalingCheckBox.OnToggled += OnCheckBoxToggled;
-            ViewportLowResCheckBox.OnToggled += OnCheckBoxToggled;
-            ParallaxLowQualityCheckBox.OnToggled += OnCheckBoxToggled;
-            FpsCounterCheckBox.OnToggled += OnCheckBoxToggled;
-            ApplyButton.OnPressed += OnApplyButtonPressed;
-            VSyncCheckBox.Pressed = _cfg.GetCVar(CVars.DisplayVSync);
-            FullscreenCheckBox.Pressed = ConfigIsFullscreen;
-            LightingPresetOption.SelectId(GetConfigLightingQuality());
-            UIScaleOption.SelectId(GetConfigUIScalePreset(ConfigUIScale));
-            ViewportScaleSlider.Value = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
-            ViewportStretchCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportStretch);
-            IntegerScalingCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0;
-            ViewportVerticalFitCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportVerticalFit);
-            ViewportLowResCheckBox.Pressed = !_cfg.GetCVar(CCVars.ViewportScaleRender);
-            ParallaxLowQualityCheckBox.Pressed = _cfg.GetCVar(CCVars.ParallaxLowQuality);
-            FpsCounterCheckBox.Pressed = _cfg.GetCVar(CCVars.HudFpsCounterVisible);
-            ViewportWidthSlider.Value = _cfg.GetCVar(CCVars.ViewportWidth);
-
-            _cfg.OnValueChanged(CCVars.ViewportMinimumWidth, _ => UpdateViewportWidthRange());
-            _cfg.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportWidthRange());
-
-            UpdateViewportWidthRange();
-            UpdateViewportWidthDisplay();
-            UpdateViewportScale();
-            UpdateApplyButton();
-        }
+        ViewportWidthSlider.Slider.MinValue = min;
+        ViewportWidthSlider.Slider.MaxValue = max;
+    }
 
-        private void OnUIScaleChanged(OptionButton.ItemSelectedEventArgs args)
-        {
-            UIScaleOption.SelectId(args.Id);
-            UpdateApplyButton();
-        }
+    private sealed class OptionLightingQuality : BaseOption
+    {
+        private readonly IConfigurationManager _cfg;
+        private readonly OptionDropDown _dropDown;
 
-        private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
-        {
-            _cfg.SetCVar(CVars.DisplayVSync, VSyncCheckBox.Pressed);
-            SetConfigLightingQuality(LightingPresetOption.SelectedId);
-
-            _cfg.SetCVar(CVars.DisplayWindowMode,
-                         (int) (FullscreenCheckBox.Pressed ? WindowMode.Fullscreen : WindowMode.Windowed));
-            _cfg.SetCVar(CVars.DisplayUIScale, UIScaleOptions[UIScaleOption.SelectedId]);
-            _cfg.SetCVar(CCVars.ViewportStretch, ViewportStretchCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ViewportFixedScaleFactor, (int) ViewportScaleSlider.Value);
-            _cfg.SetCVar(CCVars.ViewportSnapToleranceMargin,
-                         IntegerScalingCheckBox.Pressed ? CCVars.ViewportSnapToleranceMargin.DefaultValue : 0);
-            _cfg.SetCVar(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ViewportScaleRender, !ViewportLowResCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.HudFpsCounterVisible, FpsCounterCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ViewportWidth, (int) ViewportWidthSlider.Value);
-
-            _cfg.SaveToFile();
-            UpdateApplyButton();
-        }
+        private const int QualityVeryLow = 0;
+        private const int QualityLow = 1;
+        private const int QualityMedium = 2;
+        private const int QualityHigh = 3;
 
-        private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
-        {
-            UpdateApplyButton();
-        }
+        private const int QualityDefault = QualityMedium;
 
-        private void OnLightingQualityChanged(OptionButton.ItemSelectedEventArgs args)
+        public OptionLightingQuality(OptionsTabControlRow controller, IConfigurationManager cfg, OptionDropDown dropDown) : base(controller)
         {
-            LightingPresetOption.SelectId(args.Id);
-            UpdateApplyButton();
+            _cfg = cfg;
+            _dropDown = dropDown;
+            var button = dropDown.Button;
+            button.AddItem(Loc.GetString("ui-options-lighting-very-low"), QualityVeryLow);
+            button.AddItem(Loc.GetString("ui-options-lighting-low"), QualityLow);
+            button.AddItem(Loc.GetString("ui-options-lighting-medium"), QualityMedium);
+            button.AddItem(Loc.GetString("ui-options-lighting-high"), QualityHigh);
+            button.OnItemSelected += OnOptionSelected;
         }
 
-        private void UpdateApplyButton()
+        private void OnOptionSelected(OptionButton.ItemSelectedEventArgs obj)
         {
-            var isVSyncSame = VSyncCheckBox.Pressed == _cfg.GetCVar(CVars.DisplayVSync);
-            var isFullscreenSame = FullscreenCheckBox.Pressed == ConfigIsFullscreen;
-            var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality();
-            var isUIScaleSame = MathHelper.CloseToPercent(UIScaleOptions[UIScaleOption.SelectedId], ConfigUIScale);
-            var isVPStretchSame = ViewportStretchCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportStretch);
-            var isVPScaleSame = (int) ViewportScaleSlider.Value == _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
-            var isIntegerScalingSame = IntegerScalingCheckBox.Pressed == (_cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0);
-            var isVPVerticalFitSame = ViewportVerticalFitCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportVerticalFit);
-            var isVPResSame = ViewportLowResCheckBox.Pressed == !_cfg.GetCVar(CCVars.ViewportScaleRender);
-            var isPLQSame = ParallaxLowQualityCheckBox.Pressed == _cfg.GetCVar(CCVars.ParallaxLowQuality);
-            var isFpsCounterVisibleSame = FpsCounterCheckBox.Pressed == _cfg.GetCVar(CCVars.HudFpsCounterVisible);
-            var isWidthSame = (int) ViewportWidthSlider.Value == _cfg.GetCVar(CCVars.ViewportWidth);
-
-            ApplyButton.Disabled = isVSyncSame &&
-                                   isFullscreenSame &&
-                                   isLightingQualitySame &&
-                                   isUIScaleSame &&
-                                   isVPStretchSame &&
-                                   isVPScaleSame &&
-                                   isIntegerScalingSame &&
-                                   isVPVerticalFitSame &&
-                                   isVPResSame &&
-                                   isPLQSame &&
-                                   isFpsCounterVisibleSame &&
-                                   isWidthSame;
+            _dropDown.Button.SelectId(obj.Id);
+            ValueChanged();
         }
 
-        private bool ConfigIsFullscreen =>
-            _cfg.GetCVar(CVars.DisplayWindowMode) == (int) WindowMode.Fullscreen;
-
-        public void UpdateProperties()
+        public override void LoadValue()
         {
-            FullscreenCheckBox.Pressed = ConfigIsFullscreen;
+            _dropDown.Button.SelectId(GetConfigLightingQuality());
         }
 
-
-        private float ConfigUIScale => _cfg.GetCVar(CVars.DisplayUIScale);
-
-        private int GetConfigLightingQuality()
+        public override void SaveValue()
         {
-            var val = _cfg.GetCVar(CVars.LightResolutionScale);
-            var soft = _cfg.GetCVar(CVars.LightSoftShadows);
-            if (val <= 0.125)
-            {
-                return 0;
-            }
-            else if ((val <= 0.5) && !soft)
-            {
-                return 1;
-            }
-            else if (val <= 0.5)
+            switch (_dropDown.Button.SelectedId)
             {
-                return 2;
-            }
-            else
-            {
-                return 3;
-            }
-        }
-
-        private void SetConfigLightingQuality(int value)
-        {
-            switch (value)
-            {
-                case 0:
+                case QualityVeryLow:
                     _cfg.SetCVar(CVars.LightResolutionScale, 0.125f);
                     _cfg.SetCVar(CVars.LightSoftShadows, false);
                     _cfg.SetCVar(CVars.LightBlur, false);
                     break;
-                case 1:
+                case QualityLow:
                     _cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
                     _cfg.SetCVar(CVars.LightSoftShadows, false);
                     _cfg.SetCVar(CVars.LightBlur, true);
                     break;
-                case 2:
+                default: // = QualityMedium
                     _cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
                     _cfg.SetCVar(CVars.LightSoftShadows, true);
                     _cfg.SetCVar(CVars.LightBlur, true);
                     break;
-                case 3:
+                case QualityHigh:
                     _cfg.SetCVar(CVars.LightResolutionScale, 1);
                     _cfg.SetCVar(CVars.LightSoftShadows, true);
                     _cfg.SetCVar(CVars.LightBlur, true);
@@ -228,40 +149,83 @@ namespace Content.Client.Options.UI.Tabs
             }
         }
 
-        private static int GetConfigUIScalePreset(float value)
+        public override void ResetToDefault()
         {
-            for (var i = 0; i < UIScaleOptions.Length; i++)
-            {
-                if (MathHelper.CloseToPercent(UIScaleOptions[i], value))
-                {
-                    return i;
-                }
-            }
+            _dropDown.Button.SelectId(QualityDefault);
+        }
+
+        public override bool IsModified()
+        {
+            return _dropDown.Button.SelectedId != GetConfigLightingQuality();
+        }
+
+        public override bool IsModifiedFromDefault()
+        {
+            return _dropDown.Button.SelectedId != QualityDefault;
+        }
+
+        private int GetConfigLightingQuality()
+        {
+            var val = _cfg.GetCVar(CVars.LightResolutionScale);
+            var soft = _cfg.GetCVar(CVars.LightSoftShadows);
+            if (val <= 0.125)
+                return QualityVeryLow;
+
+            if ((val <= 0.5) && !soft)
+                return QualityLow;
 
-            return 0;
+            if (val <= 0.5)
+                return QualityMedium;
+
+            return QualityHigh;
         }
+    }
+
+    private sealed class OptionFullscreen : BaseOptionCVar<int>
+    {
+        private readonly CheckBox _checkBox;
 
-        private void UpdateViewportScale()
+        protected override int Value
         {
-            ViewportScaleBox.Visible = !ViewportStretchCheckBox.Pressed;
-            IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
-            ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
-            ViewportWidthSlider.Visible = ViewportWidthSliderDisplay.Visible = !ViewportStretchCheckBox.Pressed || ViewportStretchCheckBox.Pressed && !ViewportVerticalFitCheckBox.Pressed;
-            ViewportScaleText.Text = Loc.GetString("ui-options-vp-scale", ("scale", ViewportScaleSlider.Value));
+            get => _checkBox.Pressed ? (int) WindowMode.Fullscreen : (int) WindowMode.Windowed;
+            set => _checkBox.Pressed = (value == (int) WindowMode.Fullscreen);
         }
 
-        private void UpdateViewportWidthRange()
+        public OptionFullscreen(
+            OptionsTabControlRow controller,
+            IConfigurationManager cfg,
+            CheckBox checkBox)
+            : base(controller, cfg, CVars.DisplayWindowMode)
         {
-            var min = _cfg.GetCVar(CCVars.ViewportMinimumWidth);
-            var max = _cfg.GetCVar(CCVars.ViewportMaximumWidth);
+            _checkBox = checkBox;
+            _checkBox.OnToggled += _ =>
+            {
+                ValueChanged();
+            };
+        }
+    }
 
-            ViewportWidthSlider.MinValue = min;
-            ViewportWidthSlider.MaxValue = max;
+    private sealed class OptionIntegerScaling : BaseOptionCVar<int>
+    {
+        private readonly CheckBox _checkBox;
+
+        protected override int Value
+        {
+            get => _checkBox.Pressed ? CCVars.ViewportSnapToleranceMargin.DefaultValue : 0;
+            set => _checkBox.Pressed = (value != 0);
         }
 
-        private void UpdateViewportWidthDisplay()
+        public OptionIntegerScaling(
+            OptionsTabControlRow controller,
+            IConfigurationManager cfg,
+            CheckBox checkBox)
+            : base(controller, cfg, CCVars.ViewportSnapToleranceMargin)
         {
-            ViewportWidthSliderDisplay.Text = Loc.GetString("ui-options-vp-width", ("width", (int) ViewportWidthSlider.Value));
+            _checkBox = checkBox;
+            _checkBox.OnToggled += _ =>
+            {
+                ValueChanged();
+            };
         }
     }
 }
index 0c6ec3804245da3cb3b21f96a74bf6951425554d..c1733e209dbe7f49d6825e00f4bd5721e0524c3b 100644 (file)
@@ -1,76 +1,34 @@
 <tabs:MiscTab xmlns="https://spacestation14.io"
-                  xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
-                  xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
-                  xmlns:xNamespace="http://schemas.microsoft.com/winfx/2006/xaml"
-                  xmlns:s="clr-namespace:Content.Client.Stylesheets">
+              xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
+              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+              xmlns:ui="clr-namespace:Content.Client.Options.UI">
     <BoxContainer Orientation="Vertical">
         <ScrollContainer VerticalExpand="True" HorizontalExpand="True">
             <BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
                 <Label Text="{Loc 'ui-options-general-ui-style'}"
-                       FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
                        StyleClasses="LabelKeyText"/>
-                <BoxContainer Orientation="Horizontal">
-                    <Label Text="{Loc 'ui-options-hud-theme'}" />
-                    <Control MinSize="4 0" />
-                    <OptionButton Name="HudThemeOption" />
-                </BoxContainer>
-                <BoxContainer Orientation="Horizontal">
-                    <Label Text="{Loc 'ui-options-hud-layout'}" />
-                    <Control MinSize="4 0" />
-                    <OptionButton Name="HudLayoutOption" />
-                </BoxContainer>
-                <Label Text="{Loc 'ui-options-general-accessibility'}"
-                       FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
-                       StyleClasses="LabelKeyText"/>
-                <CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
-                <CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
-                <CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" />
-                <BoxContainer Orientation="Horizontal">
-                    <Label Text="{Loc 'ui-options-chat-window-opacity'}" Margin="8 0" />
-                    <Slider Name="ChatWindowOpacitySlider"
-                            MinValue="0"
-                            MaxValue="1"
-                            MinWidth="200" />
-                    <Label Name="ChatWindowOpacityLabel" Margin="8 0" />
-                </BoxContainer>
-                <BoxContainer Orientation="Horizontal">
-                    <Label Text="{Loc 'ui-options-screen-shake-intensity'}" Margin="8 0" />
-                    <Slider Name="ScreenShakeIntensitySlider"
-                            MinValue="0"
-                            MaxValue="100"
-                            Rounded="True"
-                            MinWidth="200" />
-                    <Label Name="ScreenShakeIntensityLabel" Margin="8 0" />
-                </BoxContainer>
+                <ui:OptionDropDown Name="DropDownHudTheme" Title="{Loc 'ui-options-hud-theme'}" />
+                <ui:OptionDropDown Name="DropDownHudLayout" Title="{Loc 'ui-options-hud-layout'}" />
                 <Label Text="{Loc 'ui-options-general-discord'}"
-                       FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
                        StyleClasses="LabelKeyText"/>
                 <CheckBox Name="DiscordRich" Text="{Loc 'ui-options-discordrich'}" />
                 <Label Text="{Loc 'ui-options-general-speech'}"
-                       FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
                        StyleClasses="LabelKeyText"/>
                 <CheckBox Name="ShowOocPatronColor" Text="{Loc 'ui-options-show-ooc-patron-color'}" />
                 <CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
                 <CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
                 <CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
                 <Label Text="{Loc 'ui-options-general-cursor'}"
-                       FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
                        StyleClasses="LabelKeyText"/>
                 <CheckBox Name="ShowHeldItemCheckBox" Text="{Loc 'ui-options-show-held-item'}" />
                 <CheckBox Name="ShowCombatModeIndicatorsCheckBox" Text="{Loc 'ui-options-show-combat-mode-indicators'}" />
                 <Label Text="{Loc 'ui-options-general-storage'}"
-                       FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
                        StyleClasses="LabelKeyText"/>
                 <CheckBox Name="OpaqueStorageWindowCheckBox" Text="{Loc 'ui-options-opaque-storage-window'}" />
                 <CheckBox Name="StaticStorageUI" Text="{Loc 'ui-options-static-storage-ui'}" />
                 <!-- <CheckBox Name="ToggleWalk" Text="{Loc 'ui-options-hotkey-toggle-walk'}" /> -->
             </BoxContainer>
         </ScrollContainer>
-        <controls:StripeBack HasBottomEdge="False" HasMargins="False">
-            <Button Name="ApplyButton"
-                    Text="{Loc 'ui-options-apply'}"
-                    TextAlign="Center"
-                    HorizontalAlignment="Right" />
-        </controls:StripeBack>
+        <ui:OptionsTabControlRow Name="Control" Access="Public" />
     </BoxContainer>
 </tabs:MiscTab>
index 13e3fd05f550ffda0a1af5b6013cb90f154a1265..2aad4e1d0b6274f04a74bd51b9e9d74ea0087ae4 100644 (file)
@@ -5,201 +5,54 @@ using Content.Shared.HUD;
 using Robust.Client.AutoGenerated;
 using Robust.Client.Player;
 using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.XAML;
 using Robust.Shared;
-using Robust.Shared.Configuration;
-using Robust.Shared.Network;
-using Robust.Shared.Player;
 using Robust.Shared.Prototypes;
-using Range = Robust.Client.UserInterface.Controls.Range;
 
-namespace Content.Client.Options.UI.Tabs
-{
-    [GenerateTypedNameReferences]
-    public sealed partial class MiscTab : Control
-    {
-        [Dependency] private readonly IPlayerManager _playerManager = default!;
-        [Dependency] private readonly IConfigurationManager _cfg = default!;
-        [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-
-        private readonly Dictionary<string, int> _hudThemeIdToIndex = new();
-
-        public MiscTab()
-        {
-            RobustXamlLoader.Load(this);
-            IoCManager.InjectDependencies(this);
-
-            var themes = _prototypeManager.EnumeratePrototypes<HudThemePrototype>().ToList();
-            themes.Sort();
-            foreach (var gear in themes)
-            {
-                HudThemeOption.AddItem(Loc.GetString(gear.Name));
-                _hudThemeIdToIndex.Add(gear.ID, HudThemeOption.GetItemId(HudThemeOption.ItemCount - 1));
-            }
-
-            var hudLayout = _cfg.GetCVar(CCVars.UILayout);
-            var id = 0;
-            foreach (var layout in Enum.GetValues(typeof(ScreenType)))
-            {
-                var name = layout.ToString()!;
-                HudLayoutOption.AddItem(name, id);
-                if (name == hudLayout)
-                {
-                    HudLayoutOption.SelectId(id);
-                }
-                HudLayoutOption.SetItemMetadata(id, name);
-
-                id++;
-            }
-
-            HudLayoutOption.OnItemSelected += args =>
-            {
-                HudLayoutOption.SelectId(args.Id);
-                UpdateApplyButton();
-            };
-
-            // Channel can be null in replays so.
-            // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
-            ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
-
-            HudThemeOption.OnItemSelected += OnHudThemeChanged;
-            DiscordRich.OnToggled += OnCheckBoxToggled;
-            ShowOocPatronColor.OnToggled += OnCheckBoxToggled;
-            ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
-            ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
-            ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
-            OpaqueStorageWindowCheckBox.OnToggled += OnCheckBoxToggled;
-            FancySpeechBubblesCheckBox.OnToggled += OnCheckBoxToggled;
-            FancyNameBackgroundsCheckBox.OnToggled += OnCheckBoxToggled;
-            EnableColorNameCheckBox.OnToggled += OnCheckBoxToggled;
-            ColorblindFriendlyCheckBox.OnToggled += OnCheckBoxToggled;
-            ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
-            ChatWindowOpacitySlider.OnValueChanged += OnChatWindowOpacitySliderChanged;
-            ScreenShakeIntensitySlider.OnValueChanged += OnScreenShakeIntensitySliderChanged;
-            // ToggleWalk.OnToggled += OnCheckBoxToggled;
-            StaticStorageUI.OnToggled += OnCheckBoxToggled;
-
-            HudThemeOption.SelectId(_hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
-            DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled);
-            ShowOocPatronColor.Pressed = _cfg.GetCVar(CCVars.ShowOocPatronColor);
-            ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
-            ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
-            ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
-            OpaqueStorageWindowCheckBox.Pressed = _cfg.GetCVar(CCVars.OpaqueStorageWindow);
-            FancySpeechBubblesCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
-            FancyNameBackgroundsCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatFancyNameBackground);
-            EnableColorNameCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableColorName);
-            ColorblindFriendlyCheckBox.Pressed = _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
-            ReducedMotionCheckBox.Pressed = _cfg.GetCVar(CCVars.ReducedMotion);
-            ChatWindowOpacitySlider.Value = _cfg.GetCVar(CCVars.ChatWindowOpacity);
-            ScreenShakeIntensitySlider.Value = _cfg.GetCVar(CCVars.ScreenShakeIntensity) * 100f;
-            // ToggleWalk.Pressed = _cfg.GetCVar(CCVars.ToggleWalk);
-            StaticStorageUI.Pressed = _cfg.GetCVar(CCVars.StaticStorageUI);
-
-
-            ApplyButton.OnPressed += OnApplyButtonPressed;
-            UpdateApplyButton();
-        }
+namespace Content.Client.Options.UI.Tabs;
 
-        private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
-        {
-            UpdateApplyButton();
-        }
+[GenerateTypedNameReferences]
+public sealed partial class MiscTab : Control
+{
+    [Dependency] private readonly IPlayerManager _playerManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
 
-        private void OnHudThemeChanged(OptionButton.ItemSelectedEventArgs args)
-        {
-            HudThemeOption.SelectId(args.Id);
-            UpdateApplyButton();
-        }
+    public MiscTab()
+    {
+        RobustXamlLoader.Load(this);
+        IoCManager.InjectDependencies(this);
 
-        private void OnChatWindowOpacitySliderChanged(Range range)
+        var themes = _prototypeManager.EnumeratePrototypes<HudThemePrototype>().ToList();
+        themes.Sort();
+        var themeEntries = new List<OptionDropDownCVar<string>.ValueOption>();
+        foreach (var gear in themes)
         {
-            ChatWindowOpacityLabel.Text = Loc.GetString("ui-options-chat-window-opacity-percent",
-                ("opacity", range.Value));
-            UpdateApplyButton();
+            themeEntries.Add(new OptionDropDownCVar<string>.ValueOption(gear.ID, Loc.GetString(gear.Name)));
         }
 
-        private void OnScreenShakeIntensitySliderChanged(Range obj)
+        var layoutEntries = new List<OptionDropDownCVar<string>.ValueOption>();
+        foreach (var layout in Enum.GetValues(typeof(ScreenType)))
         {
-            ScreenShakeIntensityLabel.Text = Loc.GetString("ui-options-screen-shake-percent", ("intensity", ScreenShakeIntensitySlider.Value / 100f));
-            UpdateApplyButton();
+            layoutEntries.Add(new OptionDropDownCVar<string>.ValueOption(layout.ToString()!, layout.ToString()!));
         }
 
-        private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
-        {
-            foreach (var theme in _prototypeManager.EnumeratePrototypes<HudThemePrototype>())
-            {
-                if (_hudThemeIdToIndex[theme.ID] != HudThemeOption.SelectedId)
-                    continue;
-                _cfg.SetCVar(CVars.InterfaceTheme, theme.ID);
-                break;
-            }
-
-            _cfg.SetCVar(CVars.DiscordEnabled, DiscordRich.Pressed);
-            _cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ShowOocPatronColor, ShowOocPatronColor.Pressed);
-            _cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ChatEnableColorName, EnableColorNameCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
-            _cfg.SetCVar(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider.Value);
-            _cfg.SetCVar(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider.Value / 100f);
-            // _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
-            _cfg.SetCVar(CCVars.StaticStorageUI, StaticStorageUI.Pressed);
-
-            if (HudLayoutOption.SelectedMetadata is string opt)
-            {
-                _cfg.SetCVar(CCVars.UILayout, opt);
-            }
+        // Channel can be null in replays so.
+        // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
+        ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
 
-            _cfg.SaveToFile();
-            UpdateApplyButton();
-        }
+        Control.AddOptionDropDown(CVars.InterfaceTheme, DropDownHudTheme, themeEntries);
+        Control.AddOptionDropDown(CCVars.UILayout, DropDownHudLayout, layoutEntries);
 
-        private void UpdateApplyButton()
-        {
-            var isHudThemeSame = HudThemeOption.SelectedId == _hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0);
-            var isLayoutSame = HudLayoutOption.SelectedMetadata is string opt && opt == _cfg.GetCVar(CCVars.UILayout);
-            var isDiscordSame = DiscordRich.Pressed == _cfg.GetCVar(CVars.DiscordEnabled);
-            var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
-            var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
-            var isOpaqueStorageWindow = OpaqueStorageWindowCheckBox.Pressed == _cfg.GetCVar(CCVars.OpaqueStorageWindow);
-            var isOocPatronColorShowSame = ShowOocPatronColor.Pressed == _cfg.GetCVar(CCVars.ShowOocPatronColor);
-            var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
-            var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
-            var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
-            var isEnableColorNameSame = EnableColorNameCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableColorName);
-            var isColorblindFriendly = ColorblindFriendlyCheckBox.Pressed == _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
-            var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
-            var isChatWindowOpacitySame = Math.Abs(ChatWindowOpacitySlider.Value - _cfg.GetCVar(CCVars.ChatWindowOpacity)) < 0.01f;
-            var isScreenShakeIntensitySame = Math.Abs(ScreenShakeIntensitySlider.Value / 100f - _cfg.GetCVar(CCVars.ScreenShakeIntensity)) < 0.01f;
-            // var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
-            var isStaticStorageUISame = StaticStorageUI.Pressed == _cfg.GetCVar(CCVars.StaticStorageUI);
-
-            ApplyButton.Disabled = isHudThemeSame &&
-                                   isLayoutSame &&
-                                   isDiscordSame &&
-                                   isShowHeldItemSame &&
-                                   isCombatModeIndicatorsSame &&
-                                   isOpaqueStorageWindow &&
-                                   isOocPatronColorShowSame &&
-                                   isLoocShowSame &&
-                                   isFancyChatSame &&
-                                   isFancyBackgroundSame &&
-                                   isEnableColorNameSame &&
-                                   isColorblindFriendly &&
-                                   isReducedMotionSame &&
-                                   isChatWindowOpacitySame &&
-                                   isScreenShakeIntensitySame &&
-                                   // isToggleWalkSame &&
-                                   isStaticStorageUISame;
-        }
+        Control.AddOptionCheckBox(CVars.DiscordEnabled, DiscordRich);
+        Control.AddOptionCheckBox(CCVars.ShowOocPatronColor, ShowOocPatronColor);
+        Control.AddOptionCheckBox(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox);
+        Control.AddOptionCheckBox(CCVars.HudHeldItemShow, ShowHeldItemCheckBox);
+        Control.AddOptionCheckBox(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox);
+        Control.AddOptionCheckBox(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox);
+        Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
+        Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
+        Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
 
+        Control.Initialize();
     }
-
 }
index d07f9127a470d2caee171f876fb5ef737db7c97d..39a72260e2eda589ed44c30daa25e8ca749325c8 100644 (file)
@@ -1,15 +1,18 @@
 ## General stuff
 
 ui-options-title = Game Options
+ui-options-tab-accessibility = Accessibility
 ui-options-tab-graphics = Graphics
 ui-options-tab-controls = Controls
 ui-options-tab-audio = Audio
 ui-options-tab-network = Network
 ui-options-tab-misc = General
 
-ui-options-apply = Apply
-ui-options-reset-all = Reset All
-ui-options-default = Default
+ui-options-apply = Save & apply
+ui-options-reset-all = Reset changed
+ui-options-default = Reset to defaults
+
+ui-options-value-percent = { TOSTRING($value, "P0") }
 
 # Misc/General menu
 
@@ -35,10 +38,15 @@ ui-options-restart-sounds = Round Restart Sounds
 ui-options-event-music = Event Music
 ui-options-admin-sounds = Play Admin Sounds
 ui-options-volume-label = Volume
-ui-options-volume-percent = { TOSTRING($volume, "P0") }
 
 ## Graphics menu
 
+ui-options-display-label = Display
+ui-options-quality-label = Quality
+ui-options-misc-label = Misc
+ui-options-interface-label = Interface
+
+
 ui-options-show-held-item = Show held item next to cursor
 ui-options-show-combat-mode-indicators = Show combat mode indicators with cursor
 ui-options-opaque-storage-window = Opaque storage window
@@ -46,13 +54,6 @@ ui-options-show-ooc-patron-color = Show OOC Patreon color
 ui-options-show-looc-on-head = Show LOOC chat above characters head
 ui-options-fancy-speech = Show names in speech bubbles
 ui-options-fancy-name-background = Add background to speech bubble names
-ui-options-enable-color-name = Add colors to character names
-ui-options-colorblind-friendly = Colorblind friendly mode
-ui-options-reduced-motion = Reduce motion of visual effects
-ui-options-chat-window-opacity = Chat window opacity
-ui-options-chat-window-opacity-percent = { TOSTRING($opacity, "P0") }
-ui-options-screen-shake-intensity = Screen shake intensity
-ui-options-screen-shake-percent = { TOSTRING($intensity, "P0") }
 ui-options-vsync = VSync
 ui-options-fullscreen = Fullscreen
 ui-options-lighting-label = Lighting Quality:
@@ -77,7 +78,8 @@ ui-options-hud-theme-retro = Retro
 ui-options-hud-theme-minimalist = Minimalist
 ui-options-hud-theme-ashen = Ashen
 ui-options-vp-stretch = Stretch viewport to fit game window
-ui-options-vp-scale = Fixed viewport scale: x{ $scale }
+ui-options-vp-scale = Fixed viewport scale:
+ui-options-vp-scale-value = x{ $scale }
 ui-options-vp-integer-scaling = Prefer integer scaling (might cause black bars/clipping)
 ui-options-vp-integer-scaling-tooltip = If this option is enabled, the viewport will be scaled using an integer value
                                         at specific resolutions. While this results in crisp textures, it also often
@@ -90,7 +92,7 @@ ui-options-vp-vertical-fit-tooltip = When enabled, the main viewport will ignore
 ui-options-vp-low-res = Low-resolution viewport
 ui-options-parallax-low-quality = Low-quality Parallax (background)
 ui-options-fps-counter = Show FPS counter
-ui-options-vp-width = Viewport width: { $width }
+ui-options-vp-width = Viewport width:
 ui-options-hud-layout = HUD layout:
 
 ## Controls menu
@@ -267,3 +269,11 @@ ui-options-net-pvs-leave-tooltip = This limits the rate at which the client will
 ## Toggle window console command
 cmd-options-desc = Opens options menu, optionally with a specific tab selected.
 cmd-options-help = Usage: options [tab]
+
+## Accessibility menu
+
+ui-options-enable-color-name = Add colors to character names
+ui-options-colorblind-friendly = Colorblind friendly mode
+ui-options-reduced-motion = Reduce motion of visual effects
+ui-options-chat-window-opacity = Chat window opacity
+ui-options-screen-shake-intensity = Screen shake intensity