Rewrite the options menu (#28389)

* 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>
This commit is contained in:
Pieter-Jan Briers
2024-06-22 06:11:14 +02:00
committed by GitHub
parent e0a6604d06
commit 07fe1a6b5a
17 changed files with 1141 additions and 801 deletions

View File

@@ -0,0 +1,6 @@
<Control xmlns="https://spacestation14.io">
<BoxContainer Orientation="Horizontal">
<Label Name="NameLabel" MinWidth="400" />
<OptionButton Name="Button" Access="Public" />
</BoxContainer>
</Control>

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -7,5 +7,6 @@
<tabs:GraphicsTab Name="GraphicsTab" /> <tabs:GraphicsTab Name="GraphicsTab" />
<tabs:KeyRebindTab Name="KeyRebindTab" /> <tabs:KeyRebindTab Name="KeyRebindTab" />
<tabs:AudioTab Name="AudioTab" /> <tabs:AudioTab Name="AudioTab" />
<tabs:AccessibilityTab Name="AccessibilityTab" />
</TabContainer> </TabContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -1,9 +1,6 @@
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.IoC;
using Content.Client.Options.UI.Tabs;
namespace Content.Client.Options.UI 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(1, Loc.GetString("ui-options-tab-graphics"));
Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls")); Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls"));
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio")); Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
UpdateTabs(); UpdateTabs();
} }
public void UpdateTabs() public void UpdateTabs()
{ {
GraphicsTab.UpdateProperties(); GraphicsTab.Control.ReloadValues();
MiscTab.Control.ReloadValues();
AccessibilityTab.Control.ReloadValues();
AudioTab.Control.ReloadValues();
} }
} }
} }

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -1,128 +1,26 @@
<Control xmlns="https://spacestation14.io" <Control xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:Content.Client.Stylesheets" xmlns:ui="clr-namespace:Content.Client.Options.UI">
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True"> <BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
<Label Text="{Loc 'ui-options-volume-label'}" <Label Text="{Loc 'ui-options-volume-label'}"
FontColorOverride="{x:Static s:StyleNano.NanoGold}"
StyleClasses="LabelKeyText"/> StyleClasses="LabelKeyText"/>
<BoxContainer Orientation="Vertical" Margin="0 3 0 0"> <BoxContainer Orientation="Vertical" Margin="0 3 0 0">
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0"> <ui:OptionSlider Name="SliderVolumeMaster" Title="{Loc 'ui-options-master-volume'}"
<Label Text="{Loc 'ui-options-master-volume'}" HorizontalExpand="True" /> Margin="0 0 0 8" />
<Control MinSize="8 0" /> <ui:OptionSlider Name="SliderVolumeMidi" Title="{Loc 'ui-options-midi-volume'}" />
<Slider Name="MasterVolumeSlider" <ui:OptionSlider Name="SliderVolumeAmbientMusic" Title="{Loc 'ui-options-ambient-music-volume'}" />
MinValue="0" <ui:OptionSlider Name="SliderVolumeAmbience" Title="{Loc 'ui-options-ambience-volume'}" />
MaxValue="100" <ui:OptionSlider Name="SliderVolumeLobby" Title="{Loc 'ui-options-lobby-volume'}" />
HorizontalExpand="True" <ui:OptionSlider Name="SliderVolumeInterface" Title="{Loc 'ui-options-interface-volume'}" />
MinSize="80 0" <ui:OptionSlider Name="SliderMaxAmbienceSounds" Title="{Loc 'ui-options-ambience-max-sounds'}"
Rounded="True" /> Margin="0 0 0 8" />
<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" />
<CheckBox Name="LobbyMusicCheckBox" Text="{Loc 'ui-options-lobby-music'}" /> <CheckBox Name="LobbyMusicCheckBox" Text="{Loc 'ui-options-lobby-music'}" />
<CheckBox Name="RestartSoundsCheckBox" Text="{Loc 'ui-options-restart-sounds'}" /> <CheckBox Name="RestartSoundsCheckBox" Text="{Loc 'ui-options-restart-sounds'}" />
<CheckBox Name="EventMusicCheckBox" Text="{Loc 'ui-options-event-music'}" /> <CheckBox Name="EventMusicCheckBox" Text="{Loc 'ui-options-event-music'}" />
<CheckBox Name="AdminSoundsCheckBox" Text="{Loc 'ui-options-admin-sounds'}" /> <CheckBox Name="AdminSoundsCheckBox" Text="{Loc 'ui-options-admin-sounds'}" />
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
<controls:StripeBack HasBottomEdge="False" HasMargins="False"> <ui:OptionsTabControlRow Name="Control" Access="Public" />
<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>
</BoxContainer> </BoxContainer>
</Control> </Control>

View File

@@ -3,200 +3,72 @@ using Content.Shared.CCVar;
using Robust.Client.Audio; using Robust.Client.Audio;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared; using Robust.Shared;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Range = Robust.Client.UserInterface.Controls.Range;
namespace Content.Client.Options.UI.Tabs namespace Content.Client.Options.UI.Tabs;
{
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class AudioTab : Control public sealed partial class AudioTab : Control
{ {
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly IAudioManager _audio; [Dependency] private readonly IAudioManager _audio = default!;
public AudioTab() public AudioTab()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_audio = IoCManager.Resolve<IAudioManager>(); var masterVolume = Control.AddOptionPercentSlider(
LobbyMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.LobbyMusicEnabled); CVars.AudioMasterVolume,
RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled); SliderVolumeMaster,
EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled); scale: ContentAudioSystem.MasterVolumeMultiplier);
AdminSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.AdminSoundsEnabled); masterVolume.ImmediateValueChanged += OnMasterVolumeSliderChanged;
ApplyButton.OnPressed += OnApplyButtonPressed; Control.AddOptionPercentSlider(
ResetButton.OnPressed += OnResetButtonPressed; CVars.MidiVolume,
MasterVolumeSlider.OnValueChanged += OnMasterVolumeSliderChanged; SliderVolumeMidi,
MidiVolumeSlider.OnValueChanged += OnMidiVolumeSliderChanged; scale: ContentAudioSystem.MidiVolumeMultiplier);
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); Control.AddOptionPercentSlider(
AmbienceSoundsSlider.MaxValue = _cfg.GetCVar(CCVars.MaxMaxAmbientSourcesConfigured); CCVars.AmbientMusicVolume,
SliderVolumeAmbientMusic,
scale: ContentAudioSystem.AmbientMusicMultiplier);
Reset(); 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();
} }
protected override void Dispose(bool disposing) private void OnMasterVolumeSliderChanged(float value)
{ {
ApplyButton.OnPressed -= OnApplyButtonPressed; // TODO: I was thinking of giving OptionsTabControlRow a flag to "set CVar immediately", but I'm deferring that
ResetButton.OnPressed -= OnResetButtonPressed; // until there's a proper system for enforcing people don't close the window with pending changes.
MasterVolumeSlider.OnValueChanged -= OnMasterVolumeSliderChanged; _audio.SetMasterGain(value);
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();
}
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();
}
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;
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();
}
} }
} }

View File

@@ -1,53 +1,38 @@
<tabs:GraphicsTab xmlns="https://spacestation14.io" <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">
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True"> <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="VSyncCheckBox" Text="{Loc 'ui-options-vsync'}" />
<CheckBox Name="FullscreenCheckBox" Text="{Loc 'ui-options-fullscreen'}" /> <CheckBox Name="FullscreenCheckBox" Text="{Loc 'ui-options-fullscreen'}" />
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'ui-options-lighting-label'}" /> <!-- Quality -->
<Control MinSize="4 0" /> <Label Text="{Loc 'ui-options-quality-label'}" StyleClasses="LabelKeyText"/>
<OptionButton Name="LightingPresetOption" MinSize="100 0" /> <ui:OptionDropDown Name="DropDownLightingQuality" Title="{Loc 'ui-options-lighting-label'}" />
</BoxContainer> <CheckBox Name="ViewportLowResCheckBox" Text="{Loc 'ui-options-vp-low-res'}" />
<BoxContainer Orientation="Horizontal"> <CheckBox Name="ParallaxLowQualityCheckBox" Text="{Loc 'ui-options-parallax-low-quality'}" />
<Label Text="{Loc 'ui-options-scale-label'}" />
<Control MinSize="4 0" /> <!-- Interface -->
<OptionButton Name="UIScaleOption" /> <Label Text="{Loc 'ui-options-interface-label'}" StyleClasses="LabelKeyText"/>
</BoxContainer> <ui:OptionDropDown Name="DropDownUIScale" Title="{Loc 'ui-options-scale-label'}" />
<BoxContainer Orientation="Horizontal">
<CheckBox Name="ViewportStretchCheckBox" Text="{Loc 'ui-options-vp-stretch'}" /> <CheckBox Name="ViewportStretchCheckBox" Text="{Loc 'ui-options-vp-stretch'}" />
<BoxContainer Name="ViewportScaleBox" Orientation="Horizontal"> <ui:OptionSlider Name="ViewportScaleSlider" Title="{Loc ui-options-vp-scale}" />
<Label Name="ViewportScaleText" Margin="8 0" /> <ui:OptionSlider Name="ViewportWidthSlider" Title="{Loc ui-options-vp-width}" />
<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" />
</BoxContainer>
<CheckBox Name="IntegerScalingCheckBox" <CheckBox Name="IntegerScalingCheckBox"
Text="{Loc 'ui-options-vp-integer-scaling'}" Text="{Loc 'ui-options-vp-integer-scaling'}"
ToolTip="{Loc 'ui-options-vp-integer-scaling-tooltip'}" /> ToolTip="{Loc 'ui-options-vp-integer-scaling-tooltip'}" />
<CheckBox Name="ViewportVerticalFitCheckBox" <CheckBox Name="ViewportVerticalFitCheckBox"
Text="{Loc 'ui-options-vp-vertical-fit'}" Text="{Loc 'ui-options-vp-vertical-fit'}"
ToolTip="{Loc 'ui-options-vp-vertical-fit-tooltip'}" /> 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'}" /> <!-- Misc -->
<Label Text="{Loc 'ui-options-misc-label'}" StyleClasses="LabelKeyText"/>
<CheckBox Name="FpsCounterCheckBox" Text="{Loc 'ui-options-fps-counter'}" /> <CheckBox Name="FpsCounterCheckBox" Text="{Loc 'ui-options-fps-counter'}" />
</BoxContainer> </BoxContainer>
<controls:StripeBack HasBottomEdge="False" HasMargins="False"> </ScrollContainer>
<Button Name="ApplyButton" <ui:OptionsTabControlRow Name="Control" Access="Public" />
Text="{Loc 'ui-options-apply'}"
TextAlign="Center"
HorizontalAlignment="Right" />
</controls:StripeBack>
</BoxContainer> </BoxContainer>
</tabs:GraphicsTab> </tabs:GraphicsTab>

View File

@@ -7,22 +7,11 @@ using Robust.Client.UserInterface.XAML;
using Robust.Shared; using Robust.Shared;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
namespace Content.Client.Options.UI.Tabs namespace Content.Client.Options.UI.Tabs;
{
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class GraphicsTab : Control 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!; [Dependency] private readonly IConfigurationManager _cfg = default!;
public GraphicsTab() public GraphicsTab()
@@ -30,224 +19,63 @@ namespace Content.Client.Options.UI.Tabs
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
VSyncCheckBox.OnToggled += OnCheckBoxToggled; Control.AddOptionCheckBox(CVars.DisplayVSync, VSyncCheckBox);
FullscreenCheckBox.OnToggled += OnCheckBoxToggled; Control.AddOption(new OptionFullscreen(Control, _cfg, FullscreenCheckBox));
Control.AddOption(new OptionLightingQuality(Control, _cfg, DropDownLightingQuality));
LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-very-low")); Control.AddOptionDropDown(
LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-low")); CVars.DisplayUIScale,
LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-medium")); DropDownUIScale,
LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-high")); [
LightingPresetOption.OnItemSelected += OnLightingQualityChanged; 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")),
]);
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-auto", var vpStretch = Control.AddOptionCheckBox(CCVars.ViewportStretch, ViewportStretchCheckBox);
("scale", UserInterfaceManager.DefaultUIScale))); var vpVertFit = Control.AddOptionCheckBox(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox);
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-75")); Control.AddOptionSlider(
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-100")); CCVars.ViewportFixedScaleFactor,
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-125")); ViewportScaleSlider,
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-150")); 1,
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-175")); 5,
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-200")); (_, value) => Loc.GetString("ui-options-vp-scale-value", ("scale", value)));
UIScaleOption.OnItemSelected += OnUIScaleChanged;
ViewportStretchCheckBox.OnToggled += _ => vpStretch.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
{ vpVertFit.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
UpdateViewportScale();
UpdateApplyButton();
};
ViewportScaleSlider.OnValueChanged += _ => Control.AddOptionSlider(
{ CCVars.ViewportWidth,
UpdateApplyButton(); ViewportWidthSlider,
UpdateViewportScale(); (int)ViewportWidthSlider.Slider.MinValue,
}; (int)ViewportWidthSlider.Slider.MaxValue);
ViewportWidthSlider.OnValueChanged += _ => Control.AddOption(new OptionIntegerScaling(Control, _cfg, IntegerScalingCheckBox));
{ Control.AddOptionCheckBox(CCVars.ViewportScaleRender, ViewportLowResCheckBox, invert: true);
UpdateViewportWidthDisplay(); Control.AddOptionCheckBox(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox);
UpdateApplyButton(); Control.AddOptionCheckBox(CCVars.HudFpsCounterVisible, FpsCounterCheckBox);
};
ViewportVerticalFitCheckBox.OnToggled += _ => Control.Initialize();
{
UpdateViewportScale();
UpdateApplyButton();
};
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.ViewportMinimumWidth, _ => UpdateViewportWidthRange());
_cfg.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportWidthRange()); _cfg.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportWidthRange());
UpdateViewportWidthRange(); UpdateViewportWidthRange();
UpdateViewportWidthDisplay(); UpdateViewportSettingsVisibility();
UpdateViewportScale();
UpdateApplyButton();
} }
private void OnUIScaleChanged(OptionButton.ItemSelectedEventArgs args) private void UpdateViewportSettingsVisibility()
{ {
UIScaleOption.SelectId(args.Id); ViewportScaleSlider.Visible = !ViewportStretchCheckBox.Pressed;
UpdateApplyButton();
}
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 void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
{
UpdateApplyButton();
}
private void OnLightingQualityChanged(OptionButton.ItemSelectedEventArgs args)
{
LightingPresetOption.SelectId(args.Id);
UpdateApplyButton();
}
private void UpdateApplyButton()
{
var isVSyncSame = VSyncCheckBox.Pressed == _cfg.GetCVar(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;
}
private bool ConfigIsFullscreen =>
_cfg.GetCVar(CVars.DisplayWindowMode) == (int) WindowMode.Fullscreen;
public void UpdateProperties()
{
FullscreenCheckBox.Pressed = ConfigIsFullscreen;
}
private float ConfigUIScale => _cfg.GetCVar(CVars.DisplayUIScale);
private int GetConfigLightingQuality()
{
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)
{
return 2;
}
else
{
return 3;
}
}
private void SetConfigLightingQuality(int value)
{
switch (value)
{
case 0:
_cfg.SetCVar(CVars.LightResolutionScale, 0.125f);
_cfg.SetCVar(CVars.LightSoftShadows, false);
_cfg.SetCVar(CVars.LightBlur, false);
break;
case 1:
_cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
_cfg.SetCVar(CVars.LightSoftShadows, false);
_cfg.SetCVar(CVars.LightBlur, true);
break;
case 2:
_cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
_cfg.SetCVar(CVars.LightSoftShadows, true);
_cfg.SetCVar(CVars.LightBlur, true);
break;
case 3:
_cfg.SetCVar(CVars.LightResolutionScale, 1);
_cfg.SetCVar(CVars.LightSoftShadows, true);
_cfg.SetCVar(CVars.LightBlur, true);
break;
}
}
private static int GetConfigUIScalePreset(float value)
{
for (var i = 0; i < UIScaleOptions.Length; i++)
{
if (MathHelper.CloseToPercent(UIScaleOptions[i], value))
{
return i;
}
}
return 0;
}
private void UpdateViewportScale()
{
ViewportScaleBox.Visible = !ViewportStretchCheckBox.Pressed;
IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed; IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed; ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
ViewportWidthSlider.Visible = ViewportWidthSliderDisplay.Visible = !ViewportStretchCheckBox.Pressed || ViewportStretchCheckBox.Pressed && !ViewportVerticalFitCheckBox.Pressed; ViewportWidthSlider.Visible = !ViewportStretchCheckBox.Pressed || !ViewportVerticalFitCheckBox.Pressed;
ViewportScaleText.Text = Loc.GetString("ui-options-vp-scale", ("scale", ViewportScaleSlider.Value));
} }
private void UpdateViewportWidthRange() private void UpdateViewportWidthRange()
@@ -255,13 +83,149 @@ namespace Content.Client.Options.UI.Tabs
var min = _cfg.GetCVar(CCVars.ViewportMinimumWidth); var min = _cfg.GetCVar(CCVars.ViewportMinimumWidth);
var max = _cfg.GetCVar(CCVars.ViewportMaximumWidth); var max = _cfg.GetCVar(CCVars.ViewportMaximumWidth);
ViewportWidthSlider.MinValue = min; ViewportWidthSlider.Slider.MinValue = min;
ViewportWidthSlider.MaxValue = max; ViewportWidthSlider.Slider.MaxValue = max;
} }
private void UpdateViewportWidthDisplay() private sealed class OptionLightingQuality : BaseOption
{ {
ViewportWidthSliderDisplay.Text = Loc.GetString("ui-options-vp-width", ("width", (int) ViewportWidthSlider.Value)); private readonly IConfigurationManager _cfg;
private readonly OptionDropDown _dropDown;
private const int QualityVeryLow = 0;
private const int QualityLow = 1;
private const int QualityMedium = 2;
private const int QualityHigh = 3;
private const int QualityDefault = QualityMedium;
public OptionLightingQuality(OptionsTabControlRow controller, IConfigurationManager cfg, OptionDropDown dropDown) : base(controller)
{
_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 OnOptionSelected(OptionButton.ItemSelectedEventArgs obj)
{
_dropDown.Button.SelectId(obj.Id);
ValueChanged();
}
public override void LoadValue()
{
_dropDown.Button.SelectId(GetConfigLightingQuality());
}
public override void SaveValue()
{
switch (_dropDown.Button.SelectedId)
{
case QualityVeryLow:
_cfg.SetCVar(CVars.LightResolutionScale, 0.125f);
_cfg.SetCVar(CVars.LightSoftShadows, false);
_cfg.SetCVar(CVars.LightBlur, false);
break;
case QualityLow:
_cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
_cfg.SetCVar(CVars.LightSoftShadows, false);
_cfg.SetCVar(CVars.LightBlur, true);
break;
default: // = QualityMedium
_cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
_cfg.SetCVar(CVars.LightSoftShadows, true);
_cfg.SetCVar(CVars.LightBlur, true);
break;
case QualityHigh:
_cfg.SetCVar(CVars.LightResolutionScale, 1);
_cfg.SetCVar(CVars.LightSoftShadows, true);
_cfg.SetCVar(CVars.LightBlur, true);
break;
}
}
public override void ResetToDefault()
{
_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;
if (val <= 0.5)
return QualityMedium;
return QualityHigh;
}
}
private sealed class OptionFullscreen : BaseOptionCVar<int>
{
private readonly CheckBox _checkBox;
protected override int Value
{
get => _checkBox.Pressed ? (int) WindowMode.Fullscreen : (int) WindowMode.Windowed;
set => _checkBox.Pressed = (value == (int) WindowMode.Fullscreen);
}
public OptionFullscreen(
OptionsTabControlRow controller,
IConfigurationManager cfg,
CheckBox checkBox)
: base(controller, cfg, CVars.DisplayWindowMode)
{
_checkBox = checkBox;
_checkBox.OnToggled += _ =>
{
ValueChanged();
};
}
}
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);
}
public OptionIntegerScaling(
OptionsTabControlRow controller,
IConfigurationManager cfg,
CheckBox checkBox)
: base(controller, cfg, CCVars.ViewportSnapToleranceMargin)
{
_checkBox = checkBox;
_checkBox.OnToggled += _ =>
{
ValueChanged();
};
} }
} }
} }

View File

@@ -1,76 +1,34 @@
<tabs:MiscTab xmlns="https://spacestation14.io" <tabs:MiscTab 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:xNamespace="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:Content.Client.Stylesheets"> xmlns:ui="clr-namespace:Content.Client.Options.UI">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<ScrollContainer VerticalExpand="True" HorizontalExpand="True"> <ScrollContainer VerticalExpand="True" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True"> <BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
<Label Text="{Loc 'ui-options-general-ui-style'}" <Label Text="{Loc 'ui-options-general-ui-style'}"
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
StyleClasses="LabelKeyText"/> StyleClasses="LabelKeyText"/>
<BoxContainer Orientation="Horizontal"> <ui:OptionDropDown Name="DropDownHudTheme" Title="{Loc 'ui-options-hud-theme'}" />
<Label Text="{Loc 'ui-options-hud-theme'}" /> <ui:OptionDropDown Name="DropDownHudLayout" Title="{Loc 'ui-options-hud-layout'}" />
<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>
<Label Text="{Loc 'ui-options-general-discord'}" <Label Text="{Loc 'ui-options-general-discord'}"
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
StyleClasses="LabelKeyText"/> StyleClasses="LabelKeyText"/>
<CheckBox Name="DiscordRich" Text="{Loc 'ui-options-discordrich'}" /> <CheckBox Name="DiscordRich" Text="{Loc 'ui-options-discordrich'}" />
<Label Text="{Loc 'ui-options-general-speech'}" <Label Text="{Loc 'ui-options-general-speech'}"
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
StyleClasses="LabelKeyText"/> StyleClasses="LabelKeyText"/>
<CheckBox Name="ShowOocPatronColor" Text="{Loc 'ui-options-show-ooc-patron-color'}" /> <CheckBox Name="ShowOocPatronColor" Text="{Loc 'ui-options-show-ooc-patron-color'}" />
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" /> <CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" /> <CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" /> <CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
<Label Text="{Loc 'ui-options-general-cursor'}" <Label Text="{Loc 'ui-options-general-cursor'}"
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
StyleClasses="LabelKeyText"/> StyleClasses="LabelKeyText"/>
<CheckBox Name="ShowHeldItemCheckBox" Text="{Loc 'ui-options-show-held-item'}" /> <CheckBox Name="ShowHeldItemCheckBox" Text="{Loc 'ui-options-show-held-item'}" />
<CheckBox Name="ShowCombatModeIndicatorsCheckBox" Text="{Loc 'ui-options-show-combat-mode-indicators'}" /> <CheckBox Name="ShowCombatModeIndicatorsCheckBox" Text="{Loc 'ui-options-show-combat-mode-indicators'}" />
<Label Text="{Loc 'ui-options-general-storage'}" <Label Text="{Loc 'ui-options-general-storage'}"
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
StyleClasses="LabelKeyText"/> StyleClasses="LabelKeyText"/>
<CheckBox Name="OpaqueStorageWindowCheckBox" Text="{Loc 'ui-options-opaque-storage-window'}" /> <CheckBox Name="OpaqueStorageWindowCheckBox" Text="{Loc 'ui-options-opaque-storage-window'}" />
<CheckBox Name="StaticStorageUI" Text="{Loc 'ui-options-static-storage-ui'}" /> <CheckBox Name="StaticStorageUI" Text="{Loc 'ui-options-static-storage-ui'}" />
<!-- <CheckBox Name="ToggleWalk" Text="{Loc 'ui-options-hotkey-toggle-walk'}" /> --> <!-- <CheckBox Name="ToggleWalk" Text="{Loc 'ui-options-hotkey-toggle-walk'}" /> -->
</BoxContainer> </BoxContainer>
</ScrollContainer> </ScrollContainer>
<controls:StripeBack HasBottomEdge="False" HasMargins="False"> <ui:OptionsTabControlRow Name="Control" Access="Public" />
<Button Name="ApplyButton"
Text="{Loc 'ui-options-apply'}"
TextAlign="Center"
HorizontalAlignment="Right" />
</controls:StripeBack>
</BoxContainer> </BoxContainer>
</tabs:MiscTab> </tabs:MiscTab>

View File

@@ -5,26 +5,18 @@ using Content.Shared.HUD;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared; using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Range = Robust.Client.UserInterface.Controls.Range;
namespace Content.Client.Options.UI.Tabs namespace Content.Client.Options.UI.Tabs;
{
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class MiscTab : Control public sealed partial class MiscTab : Control
{ {
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly Dictionary<string, int> _hudThemeIdToIndex = new();
public MiscTab() public MiscTab()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -32,174 +24,35 @@ namespace Content.Client.Options.UI.Tabs
var themes = _prototypeManager.EnumeratePrototypes<HudThemePrototype>().ToList(); var themes = _prototypeManager.EnumeratePrototypes<HudThemePrototype>().ToList();
themes.Sort(); themes.Sort();
var themeEntries = new List<OptionDropDownCVar<string>.ValueOption>();
foreach (var gear in themes) foreach (var gear in themes)
{ {
HudThemeOption.AddItem(Loc.GetString(gear.Name)); themeEntries.Add(new OptionDropDownCVar<string>.ValueOption(gear.ID, Loc.GetString(gear.Name)));
_hudThemeIdToIndex.Add(gear.ID, HudThemeOption.GetItemId(HudThemeOption.ItemCount - 1));
} }
var hudLayout = _cfg.GetCVar(CCVars.UILayout); var layoutEntries = new List<OptionDropDownCVar<string>.ValueOption>();
var id = 0;
foreach (var layout in Enum.GetValues(typeof(ScreenType))) foreach (var layout in Enum.GetValues(typeof(ScreenType)))
{ {
var name = layout.ToString()!; layoutEntries.Add(new OptionDropDownCVar<string>.ValueOption(layout.ToString()!, 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. // Channel can be null in replays so.
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { }; ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
HudThemeOption.OnItemSelected += OnHudThemeChanged; Control.AddOptionDropDown(CVars.InterfaceTheme, DropDownHudTheme, themeEntries);
DiscordRich.OnToggled += OnCheckBoxToggled; Control.AddOptionDropDown(CCVars.UILayout, DropDownHudLayout, layoutEntries);
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)); Control.AddOptionCheckBox(CVars.DiscordEnabled, DiscordRich);
DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled); Control.AddOptionCheckBox(CCVars.ShowOocPatronColor, ShowOocPatronColor);
ShowOocPatronColor.Pressed = _cfg.GetCVar(CCVars.ShowOocPatronColor); Control.AddOptionCheckBox(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox);
ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow); Control.AddOptionCheckBox(CCVars.HudHeldItemShow, ShowHeldItemCheckBox);
ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow); Control.AddOptionCheckBox(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox);
ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow); Control.AddOptionCheckBox(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox);
OpaqueStorageWindowCheckBox.Pressed = _cfg.GetCVar(CCVars.OpaqueStorageWindow); Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
FancySpeechBubblesCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableFancyBubbles); Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
FancyNameBackgroundsCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatFancyNameBackground); Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
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);
Control.Initialize();
ApplyButton.OnPressed += OnApplyButtonPressed;
UpdateApplyButton();
} }
private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
{
UpdateApplyButton();
}
private void OnHudThemeChanged(OptionButton.ItemSelectedEventArgs args)
{
HudThemeOption.SelectId(args.Id);
UpdateApplyButton();
}
private void OnChatWindowOpacitySliderChanged(Range range)
{
ChatWindowOpacityLabel.Text = Loc.GetString("ui-options-chat-window-opacity-percent",
("opacity", range.Value));
UpdateApplyButton();
}
private void OnScreenShakeIntensitySliderChanged(Range obj)
{
ScreenShakeIntensityLabel.Text = Loc.GetString("ui-options-screen-shake-percent", ("intensity", ScreenShakeIntensitySlider.Value / 100f));
UpdateApplyButton();
}
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);
}
_cfg.SaveToFile();
UpdateApplyButton();
}
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;
}
}
} }

View File

@@ -1,15 +1,18 @@
## General stuff ## General stuff
ui-options-title = Game Options ui-options-title = Game Options
ui-options-tab-accessibility = Accessibility
ui-options-tab-graphics = Graphics ui-options-tab-graphics = Graphics
ui-options-tab-controls = Controls ui-options-tab-controls = Controls
ui-options-tab-audio = Audio ui-options-tab-audio = Audio
ui-options-tab-network = Network ui-options-tab-network = Network
ui-options-tab-misc = General ui-options-tab-misc = General
ui-options-apply = Apply ui-options-apply = Save & apply
ui-options-reset-all = Reset All ui-options-reset-all = Reset changed
ui-options-default = Default ui-options-default = Reset to defaults
ui-options-value-percent = { TOSTRING($value, "P0") }
# Misc/General menu # Misc/General menu
@@ -35,10 +38,15 @@ ui-options-restart-sounds = Round Restart Sounds
ui-options-event-music = Event Music ui-options-event-music = Event Music
ui-options-admin-sounds = Play Admin Sounds ui-options-admin-sounds = Play Admin Sounds
ui-options-volume-label = Volume ui-options-volume-label = Volume
ui-options-volume-percent = { TOSTRING($volume, "P0") }
## Graphics menu ## 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-held-item = Show held item next to cursor
ui-options-show-combat-mode-indicators = Show combat mode indicators with cursor ui-options-show-combat-mode-indicators = Show combat mode indicators with cursor
ui-options-opaque-storage-window = Opaque storage window 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-show-looc-on-head = Show LOOC chat above characters head
ui-options-fancy-speech = Show names in speech bubbles ui-options-fancy-speech = Show names in speech bubbles
ui-options-fancy-name-background = Add background to speech bubble names 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-vsync = VSync
ui-options-fullscreen = Fullscreen ui-options-fullscreen = Fullscreen
ui-options-lighting-label = Lighting Quality: 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-minimalist = Minimalist
ui-options-hud-theme-ashen = Ashen ui-options-hud-theme-ashen = Ashen
ui-options-vp-stretch = Stretch viewport to fit game window 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 = 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 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 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-vp-low-res = Low-resolution viewport
ui-options-parallax-low-quality = Low-quality Parallax (background) ui-options-parallax-low-quality = Low-quality Parallax (background)
ui-options-fps-counter = Show FPS counter 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: ui-options-hud-layout = HUD layout:
## Controls menu ## 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 ## Toggle window console command
cmd-options-desc = Opens options menu, optionally with a specific tab selected. cmd-options-desc = Opens options menu, optionally with a specific tab selected.
cmd-options-help = Usage: options [tab] 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