* 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>
--- /dev/null
+<Control xmlns="https://spacestation14.io">
+ <BoxContainer Orientation="Horizontal">
+ <Label Name="NameLabel" MinWidth="400" />
+ <OptionButton Name="Button" Access="Public" />
+ </BoxContainer>
+</Control>
--- /dev/null
+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;
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+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;
+ }
+}
<tabs:GraphicsTab Name="GraphicsTab" />
<tabs:KeyRebindTab Name="KeyRebindTab" />
<tabs:AudioTab Name="AudioTab" />
+ <tabs:AccessibilityTab Name="AccessibilityTab" />
</TabContainer>
</DefaultWindow>
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
{
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();
}
}
}
--- /dev/null
+<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>
--- /dev/null
+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;
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+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();
+ }
+}
+
<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>
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);
}
}
<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>
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);
}
}
- 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();
+ };
}
}
}
<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>
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();
}
-
}
## 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
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
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:
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
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
## 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