1
0

OptionsTabControlRow.xaml.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. using System.Linq;
  2. using Content.Client.Stylesheets;
  3. using Robust.Client.AutoGenerated;
  4. using Robust.Client.UserInterface;
  5. using Robust.Client.UserInterface.Controls;
  6. using Robust.Client.UserInterface.XAML;
  7. using Robust.Shared.Collections;
  8. using Robust.Shared.Configuration;
  9. namespace Content.Client.Options.UI;
  10. /// <summary>
  11. /// Control used on all tabs of the in-game options menu,
  12. /// contains the "save" and "reset" buttons and controls the entire logic.
  13. /// </summary>
  14. /// <remarks>
  15. /// <para>
  16. /// Basic operation is simple: options tabs put this control at the bottom of the tab,
  17. /// they bind UI controls to it with calls such as <see cref="AddOptionCheckBox"/>,
  18. /// then they call <see cref="Initialize"/>. The rest is all handled by the control.
  19. /// </para>
  20. /// <para>
  21. /// Individual options are implementations of <see cref="BaseOption"/>. See the type for details.
  22. /// Common implementations for building on top of CVars are already exist,
  23. /// but tabs can define their own if they need to.
  24. /// </para>
  25. /// <para>
  26. /// Generally, options are added via helper methods such as <see cref="AddOptionCheckBox"/>,
  27. /// however it is totally possible to directly instantiate the backing types
  28. /// and add them via <see cref="AddOption{T}"/>.
  29. /// </para>
  30. /// <para>
  31. /// The options system is general purpose enough that <see cref="OptionsTabControlRow"/> does not, itself,
  32. /// know what a CVar is. It does automatically save CVars to config when save is pressed, but otherwise CVar interaction
  33. /// is handled by <see cref="BaseOption"/> implementations.
  34. /// </para>
  35. /// <para>
  36. /// Behaviorally, the row has 3 control buttons: save, reset changed, and reset to default.
  37. /// "Save" writes the configuration changes and saves the configuration.
  38. /// "Reset changed" discards changes made in the menu and re-loads the saved settings.
  39. /// "Reset to default" resets the settings on the menu to be the default, out-of-the-box values.
  40. /// Note that "Reset to default" does not save immediately, the user must still press save manually.
  41. /// </para>
  42. /// <para>
  43. /// The disabled state of the 3 buttons is updated dynamically based on the values of the options.
  44. /// </para>
  45. /// </remarks>
  46. [GenerateTypedNameReferences]
  47. public sealed partial class OptionsTabControlRow : Control
  48. {
  49. [Dependency] private readonly ILocalizationManager _loc = default!;
  50. [Dependency] private readonly IConfigurationManager _cfg = default!;
  51. private ValueList<BaseOption> _options;
  52. public OptionsTabControlRow()
  53. {
  54. RobustXamlLoader.Load(this);
  55. IoCManager.InjectDependencies(this);
  56. ResetButton.StyleClasses.Add(StyleBase.ButtonOpenRight);
  57. ApplyButton.OnPressed += ApplyButtonPressed;
  58. ResetButton.OnPressed += ResetButtonPressed;
  59. DefaultButton.OnPressed += DefaultButtonPressed;
  60. }
  61. /// <summary>
  62. /// Add a new option to be tracked by the control.
  63. /// </summary>
  64. /// <param name="option">The option object that manages this object's logic</param>
  65. /// <typeparam name="T">
  66. /// The type of option being passed in. Necessary to allow the return type to match the parameter type
  67. /// for easy chaining.
  68. /// </typeparam>
  69. /// <returns>The same <paramref name="option"/> as passed in, for easy chaining.</returns>
  70. public T AddOption<T>(T option) where T : BaseOption
  71. {
  72. _options.Add(option);
  73. return option;
  74. }
  75. /// <summary>
  76. /// Add a checkbox option backed by a simple boolean CVar.
  77. /// </summary>
  78. /// <param name="cVar">The CVar represented by the checkbox.</param>
  79. /// <param name="checkBox">The UI control for the option.</param>
  80. /// <param name="invert">
  81. /// If true, the checkbox is inverted relative to the CVar: if the CVar is true, the checkbox will be unchecked.
  82. /// </param>
  83. /// <returns>The option instance backing the added option.</returns>
  84. /// <seealso cref="OptionCheckboxCVar"/>
  85. public OptionCheckboxCVar AddOptionCheckBox(CVarDef<bool> cVar, CheckBox checkBox, bool invert = false)
  86. {
  87. return AddOption(new OptionCheckboxCVar(this, _cfg, cVar, checkBox, invert));
  88. }
  89. /// <summary>
  90. /// Add a slider option, displayed in percent, backed by a simple float CVar.
  91. /// </summary>
  92. /// <param name="cVar">The CVar represented by the slider.</param>
  93. /// <param name="slider">The UI control for the option.</param>
  94. /// <param name="min">The minimum value the slider should allow. The default value represents "0%"</param>
  95. /// <param name="max">The maximum value the slider should allow. The default value represents "100%"</param>
  96. /// <param name="scale">
  97. /// Scale with which to multiply slider values when mapped to the backing CVar.
  98. /// For example, if a scale of 2 is set, a slider at 75% writes a value of 1.5 to the CVar.
  99. /// </param>
  100. /// <returns>The option instance backing the added option.</returns>
  101. /// <remarks>
  102. /// <para>
  103. /// Note that percentage values are represented as ratios in code, i.e. a value of 100% is "1".
  104. /// </para>
  105. /// </remarks>
  106. public OptionSliderFloatCVar AddOptionPercentSlider(
  107. CVarDef<float> cVar,
  108. OptionSlider slider,
  109. float min = 0,
  110. float max = 1,
  111. float scale = 1)
  112. {
  113. return AddOption(new OptionSliderFloatCVar(this, _cfg, cVar, slider, min, max, scale, FormatPercent));
  114. }
  115. /// <summary>
  116. /// Add a slider option, backed by a simple integer CVar.
  117. /// </summary>
  118. /// <param name="cVar">The CVar represented by the slider.</param>
  119. /// <param name="slider">The UI control for the option.</param>
  120. /// <param name="min">The minimum value the slider should allow.</param>
  121. /// <param name="max">The maximum value the slider should allow.</param>
  122. /// <param name="format">
  123. /// An optional delegate used to format the textual value display of the slider.
  124. /// If not provided, the default behavior is to directly format the integer value as text.
  125. /// </param>
  126. /// <returns>The option instance backing the added option.</returns>
  127. public OptionSliderIntCVar AddOptionSlider(
  128. CVarDef<int> cVar,
  129. OptionSlider slider,
  130. int min,
  131. int max,
  132. Func<OptionSliderIntCVar, int, string>? format = null)
  133. {
  134. return AddOption(new OptionSliderIntCVar(this, _cfg, cVar, slider, min, max, format ?? FormatInt));
  135. }
  136. /// <summary>
  137. /// Add a drop-down option, backed by a CVar.
  138. /// </summary>
  139. /// <param name="cVar">The CVar represented by the drop-down.</param>
  140. /// <param name="dropDown">The UI control for the option.</param>
  141. /// <param name="options">
  142. /// The set of options that will be shown in the drop-down. Items are ordered as provided.
  143. /// </param>
  144. /// <typeparam name="T">The type of the CVar being controlled.</typeparam>
  145. /// <returns>The option instance backing the added option.</returns>
  146. public OptionDropDownCVar<T> AddOptionDropDown<T>(
  147. CVarDef<T> cVar,
  148. OptionDropDown dropDown,
  149. IReadOnlyCollection<OptionDropDownCVar<T>.ValueOption> options)
  150. where T : notnull
  151. {
  152. return AddOption(new OptionDropDownCVar<T>(this, _cfg, cVar, dropDown, options));
  153. }
  154. /// <summary>
  155. /// Initializes the control row. This should be called after all options have been added.
  156. /// </summary>
  157. public void Initialize()
  158. {
  159. foreach (var option in _options)
  160. {
  161. option.LoadValue();
  162. }
  163. UpdateButtonState();
  164. }
  165. /// <summary>
  166. /// Re-loads options in the settings from backing values.
  167. /// Should be called when the options window is opened to make sure all values are up-to-date.
  168. /// </summary>
  169. public void ReloadValues()
  170. {
  171. Initialize();
  172. }
  173. /// <summary>
  174. /// Called by <see cref="BaseOption"/> to signal that an option's value changed through user interaction.
  175. /// </summary>
  176. /// <remarks>
  177. /// <see cref="BaseOption"/> implementations should not call this function directly,
  178. /// instead they should call <see cref="BaseOption.ValueChanged"/>.
  179. /// </remarks>
  180. public void ValueChanged()
  181. {
  182. UpdateButtonState();
  183. }
  184. private void UpdateButtonState()
  185. {
  186. var anyModified = _options.Any(option => option.IsModified());
  187. var anyModifiedFromDefault = _options.Any(option => option.IsModifiedFromDefault());
  188. DefaultButton.Disabled = !anyModifiedFromDefault;
  189. ApplyButton.Disabled = !anyModified;
  190. ResetButton.Disabled = !anyModified;
  191. }
  192. private void ApplyButtonPressed(BaseButton.ButtonEventArgs obj)
  193. {
  194. foreach (var option in _options)
  195. {
  196. if (option.IsModified())
  197. option.SaveValue();
  198. }
  199. _cfg.SaveToFile();
  200. UpdateButtonState();
  201. }
  202. private void ResetButtonPressed(BaseButton.ButtonEventArgs obj)
  203. {
  204. foreach (var option in _options)
  205. {
  206. option.LoadValue();
  207. }
  208. UpdateButtonState();
  209. }
  210. private void DefaultButtonPressed(BaseButton.ButtonEventArgs obj)
  211. {
  212. foreach (var option in _options)
  213. {
  214. option.ResetToDefault();
  215. }
  216. UpdateButtonState();
  217. }
  218. private string FormatPercent(OptionSliderFloatCVar slider, float value)
  219. {
  220. return _loc.GetString("ui-options-value-percent", ("value", value));
  221. }
  222. private static string FormatInt(OptionSliderIntCVar slider, int value)
  223. {
  224. return value.ToString();
  225. }
  226. }
  227. /// <summary>
  228. /// Base class of a single "option" for <see cref="OptionsTabControlRow"/>.
  229. /// </summary>
  230. /// <remarks>
  231. /// <para>
  232. /// Implementations of this class handle loading values from backing storage or defaults,
  233. /// handling UI controls, and saving. The main <see cref="OptionsTabControlRow"/> does not know what a CVar is.
  234. /// </para>
  235. /// <para>
  236. /// <see cref="BaseOptionCVar{TValue}"/> is a derived class that makes it easier to work with options
  237. /// backed by a single CVar.
  238. /// </para>
  239. /// </remarks>
  240. /// <param name="controller">The control row that owns this option.</param>
  241. /// <seealso cref="OptionsTabControlRow"/>
  242. public abstract class BaseOption(OptionsTabControlRow controller)
  243. {
  244. /// <summary>
  245. /// Should be called by derived implementations to indicate that their value changed, due to user interaction.
  246. /// </summary>
  247. protected virtual void ValueChanged()
  248. {
  249. controller.ValueChanged();
  250. }
  251. /// <summary>
  252. /// Loads the value represented by this option from its backing store, into the UI state.
  253. /// </summary>
  254. public abstract void LoadValue();
  255. /// <summary>
  256. /// Saves the value in the UI state to the backing store.
  257. /// </summary>
  258. public abstract void SaveValue();
  259. /// <summary>
  260. /// Resets the UI state to that of the factory-default value. This should not write to the backing store.
  261. /// </summary>
  262. public abstract void ResetToDefault();
  263. /// <summary>
  264. /// Called to check if this option's UI value is different from the backing store value.
  265. /// </summary>
  266. /// <returns>If true, the UI value is different and was modified by the user.</returns>
  267. public abstract bool IsModified();
  268. /// <summary>
  269. /// Called to check if this option's UI value is different from the backing store's default value.
  270. /// </summary>
  271. /// <returns>If true, the UI value is different.</returns>
  272. public abstract bool IsModifiedFromDefault();
  273. }
  274. /// <summary>
  275. /// Derived class of <see cref="BaseOption"/> intended for making mappings to simple CVars easier.
  276. /// </summary>
  277. /// <typeparam name="TValue">The type of the CVar.</typeparam>
  278. /// <seealso cref="OptionsTabControlRow"/>
  279. public abstract class BaseOptionCVar<TValue> : BaseOption
  280. where TValue : notnull
  281. {
  282. /// <summary>
  283. /// Raised immediately when the UI value of this option is changed by the user, even before saving.
  284. /// </summary>
  285. /// <remarks>
  286. /// <para>
  287. /// This can be used to update parts of the options UI based on the state of a checkbox.
  288. /// </para>
  289. /// </remarks>
  290. public event Action<TValue>? ImmediateValueChanged;
  291. private readonly IConfigurationManager _cfg;
  292. private readonly CVarDef<TValue> _cVar;
  293. /// <summary>
  294. /// Sets and gets the actual CVar value to/from the frontend UI state or control.
  295. /// </summary>
  296. /// <remarks>
  297. /// <para>
  298. /// In the simplest case, this function should set a UI control's state to represent the CVar,
  299. /// and inversely conver the UI control's state to the CVar value. For simple controls like a checkbox or slider,
  300. /// this just means passing through their value property.
  301. /// </para>
  302. /// </remarks>
  303. protected abstract TValue Value { get; set; }
  304. protected BaseOptionCVar(
  305. OptionsTabControlRow controller,
  306. IConfigurationManager cfg,
  307. CVarDef<TValue> cVar)
  308. : base(controller)
  309. {
  310. _cfg = cfg;
  311. _cVar = cVar;
  312. }
  313. public override void LoadValue()
  314. {
  315. Value = _cfg.GetCVar(_cVar);
  316. }
  317. public override void SaveValue()
  318. {
  319. _cfg.SetCVar(_cVar, Value);
  320. }
  321. public override void ResetToDefault()
  322. {
  323. Value = _cVar.DefaultValue;
  324. }
  325. public override bool IsModified()
  326. {
  327. return !IsValueEqual(Value, _cfg.GetCVar(_cVar));
  328. }
  329. public override bool IsModifiedFromDefault()
  330. {
  331. return !IsValueEqual(Value, _cVar.DefaultValue);
  332. }
  333. protected virtual bool IsValueEqual(TValue a, TValue b)
  334. {
  335. // Use different logic for floats so there's some error margin.
  336. // This check is handled cleanly at compile-time by the JIT.
  337. if (typeof(TValue) == typeof(float))
  338. return MathHelper.CloseToPercent((float) (object) a, (float) (object) b);
  339. return EqualityComparer<TValue>.Default.Equals(a, b);
  340. }
  341. protected override void ValueChanged()
  342. {
  343. base.ValueChanged();
  344. ImmediateValueChanged?.Invoke(Value);
  345. }
  346. }
  347. /// <summary>
  348. /// Implementation of a CVar option that simply corresponds with a <see cref="CheckBox"/>.
  349. /// </summary>
  350. /// <remarks>
  351. /// <para>
  352. /// Generally, you should just call <c>AddOption</c> methods on <see cref="OptionsTabControlRow"/>
  353. /// instead of instantiating this type directly.
  354. /// </para>
  355. /// </remarks>
  356. /// <seealso cref="OptionsTabControlRow"/>
  357. public sealed class OptionCheckboxCVar : BaseOptionCVar<bool>
  358. {
  359. private readonly CheckBox _checkBox;
  360. private readonly bool _invert;
  361. protected override bool Value
  362. {
  363. get => _checkBox.Pressed ^ _invert;
  364. set => _checkBox.Pressed = value ^ _invert;
  365. }
  366. /// <summary>
  367. /// Creates a new instance of this type.
  368. /// </summary>
  369. /// <param name="controller">The control row that owns this option.</param>
  370. /// <param name="cfg">The configuration manager to get and set values from.</param>
  371. /// <param name="cVar">The CVar that is being controlled by this option.</param>
  372. /// <param name="checkBox">The UI control for the option.</param>
  373. /// <param name="invert">
  374. /// If true, the checkbox is inverted relative to the CVar: if the CVar is true, the checkbox will be unchecked.
  375. /// </param>
  376. /// <remarks>
  377. /// <para>
  378. /// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
  379. /// such as <see cref="OptionsTabControlRow.AddOptionCheckBox"/> instead of instantiating this type directly.
  380. /// </para>
  381. /// </remarks>
  382. public OptionCheckboxCVar(
  383. OptionsTabControlRow controller,
  384. IConfigurationManager cfg,
  385. CVarDef<bool> cVar,
  386. CheckBox checkBox,
  387. bool invert)
  388. : base(controller, cfg, cVar)
  389. {
  390. _checkBox = checkBox;
  391. _invert = invert;
  392. checkBox.OnToggled += _ =>
  393. {
  394. ValueChanged();
  395. };
  396. }
  397. }
  398. /// <summary>
  399. /// Implementation of a CVar option that simply corresponds with a floating-point <see cref="OptionSlider"/>.
  400. /// </summary>
  401. /// <seealso cref="OptionsTabControlRow"/>
  402. public sealed class OptionSliderFloatCVar : BaseOptionCVar<float>
  403. {
  404. /// <summary>
  405. /// Scale with which to multiply slider values when mapped to the backing CVar.
  406. /// </summary>
  407. /// <remarks>
  408. /// For example, if a scale of 2 is set, a slider at 75% writes a value of 1.5 to the CVar.
  409. /// </remarks>
  410. public float Scale { get; }
  411. private readonly OptionSlider _slider;
  412. private readonly Func<OptionSliderFloatCVar, float, string> _format;
  413. protected override float Value
  414. {
  415. get => _slider.Slider.Value * Scale;
  416. set
  417. {
  418. _slider.Slider.Value = value / Scale;
  419. UpdateLabelValue();
  420. }
  421. }
  422. /// <summary>
  423. /// Creates a new instance of this type.
  424. /// </summary>
  425. /// <remarks>
  426. /// <para>
  427. /// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
  428. /// such as <see cref="OptionsTabControlRow.AddOptionPercentSlider"/> instead of instantiating this type directly.
  429. /// </para>
  430. /// </remarks>
  431. /// <param name="controller">The control row that owns this option.</param>
  432. /// <param name="cfg">The configuration manager to get and set values from.</param>
  433. /// <param name="cVar">The CVar that is being controlled by this option.</param>
  434. /// <param name="slider">The UI control for the option.</param>
  435. /// <param name="minValue">The minimum value the slider should allow.</param>
  436. /// <param name="maxValue">The maximum value the slider should allow.</param>
  437. /// <param name="scale">
  438. /// Scale with which to multiply slider values when mapped to the backing CVar. See <see cref="Scale"/>.
  439. /// </param>
  440. /// <param name="format">Function that will be called to format the value display next to the slider.</param>
  441. public OptionSliderFloatCVar(
  442. OptionsTabControlRow controller,
  443. IConfigurationManager cfg,
  444. CVarDef<float> cVar,
  445. OptionSlider slider,
  446. float minValue,
  447. float maxValue,
  448. float scale,
  449. Func<OptionSliderFloatCVar, float, string> format) : base(controller, cfg, cVar)
  450. {
  451. Scale = scale;
  452. _slider = slider;
  453. _format = format;
  454. slider.Slider.MinValue = minValue;
  455. slider.Slider.MaxValue = maxValue;
  456. slider.Slider.OnValueChanged += _ =>
  457. {
  458. ValueChanged();
  459. UpdateLabelValue();
  460. };
  461. }
  462. private void UpdateLabelValue()
  463. {
  464. _slider.ValueLabel.Text = _format(this, _slider.Slider.Value);
  465. }
  466. }
  467. /// <summary>
  468. /// Implementation of a CVar option that simply corresponds with an integer <see cref="OptionSlider"/>.
  469. /// </summary>
  470. /// <seealso cref="OptionsTabControlRow"/>
  471. public sealed class OptionSliderIntCVar : BaseOptionCVar<int>
  472. {
  473. private readonly OptionSlider _slider;
  474. private readonly Func<OptionSliderIntCVar, int, string> _format;
  475. protected override int Value
  476. {
  477. get => (int) _slider.Slider.Value;
  478. set
  479. {
  480. _slider.Slider.Value = value;
  481. UpdateLabelValue();
  482. }
  483. }
  484. /// <summary>
  485. /// Creates a new instance of this type.
  486. /// </summary>
  487. /// <remarks>
  488. /// <para>
  489. /// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
  490. /// such as <see cref="OptionsTabControlRow.AddOptionPercentSlider"/> instead of instantiating this type directly.
  491. /// </para>
  492. /// </remarks>
  493. /// <param name="controller">The control row that owns this option.</param>
  494. /// <param name="cfg">The configuration manager to get and set values from.</param>
  495. /// <param name="cVar">The CVar that is being controlled by this option.</param>
  496. /// <param name="slider">The UI control for the option.</param>
  497. /// <param name="minValue">The minimum value the slider should allow.</param>
  498. /// <param name="maxValue">The maximum value the slider should allow.</param>
  499. /// <param name="format">Function that will be called to format the value display next to the slider.</param>
  500. public OptionSliderIntCVar(
  501. OptionsTabControlRow controller,
  502. IConfigurationManager cfg,
  503. CVarDef<int> cVar,
  504. OptionSlider slider,
  505. int minValue,
  506. int maxValue,
  507. Func<OptionSliderIntCVar, int, string> format) : base(controller, cfg, cVar)
  508. {
  509. _slider = slider;
  510. _format = format;
  511. slider.Slider.MinValue = minValue;
  512. slider.Slider.MaxValue = maxValue;
  513. slider.Slider.Rounded = true;
  514. slider.Slider.OnValueChanged += _ =>
  515. {
  516. ValueChanged();
  517. UpdateLabelValue();
  518. };
  519. }
  520. private void UpdateLabelValue()
  521. {
  522. _slider.ValueLabel.Text = _format(this, (int) _slider.Slider.Value);
  523. }
  524. }
  525. /// <summary>
  526. /// Implementation of a CVar option via a drop-down.
  527. /// </summary>
  528. /// <seealso cref="OptionsTabControlRow"/>
  529. public sealed class OptionDropDownCVar<T> : BaseOptionCVar<T> where T : notnull
  530. {
  531. private readonly OptionDropDown _dropDown;
  532. private readonly ItemEntry[] _entries;
  533. protected override T Value
  534. {
  535. get => (T) _dropDown.Button.SelectedMetadata!;
  536. set => _dropDown.Button.SelectId(FindValueId(value));
  537. }
  538. /// <summary>
  539. /// Creates a new instance of this type.
  540. /// </summary>
  541. /// <remarks>
  542. /// <para>
  543. /// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
  544. /// such as <see cref="OptionsTabControlRow.AddOptionDropDown{T}"/> instead of instantiating this type directly.
  545. /// </para>
  546. /// </remarks>
  547. /// <param name="controller">The control row that owns this option.</param>
  548. /// <param name="cfg">The configuration manager to get and set values from.</param>
  549. /// <param name="cVar">The CVar that is being controlled by this option.</param>
  550. /// <param name="dropDown">The UI control for the option.</param>
  551. /// <param name="options">The list of options shown to the user.</param>
  552. public OptionDropDownCVar(
  553. OptionsTabControlRow controller,
  554. IConfigurationManager cfg,
  555. CVarDef<T> cVar,
  556. OptionDropDown dropDown,
  557. IReadOnlyCollection<ValueOption> options) : base(controller, cfg, cVar)
  558. {
  559. if (options.Count == 0)
  560. throw new ArgumentException("Need at least one option!");
  561. _dropDown = dropDown;
  562. _entries = new ItemEntry[options.Count];
  563. var button = dropDown.Button;
  564. var i = 0;
  565. foreach (var option in options)
  566. {
  567. _entries[i] = new ItemEntry
  568. {
  569. Key = option.Key,
  570. };
  571. button.AddItem(option.Label, i);
  572. button.SetItemMetadata(button.GetIdx(i), option.Key);
  573. i += 1;
  574. }
  575. dropDown.Button.OnItemSelected += args =>
  576. {
  577. dropDown.Button.SelectId(args.Id);
  578. ValueChanged();
  579. };
  580. }
  581. private int FindValueId(T value)
  582. {
  583. for (var i = 0; i < _entries.Length; i++)
  584. {
  585. if (IsValueEqual(_entries[i].Key, value))
  586. return i;
  587. }
  588. // This will just default select the first entry or whatever.
  589. return 0;
  590. }
  591. /// <summary>
  592. /// A single option for a drop-down.
  593. /// </summary>
  594. /// <param name="key">The value that this option has. This is what will be written to the CVar if selected.</param>
  595. /// <param name="label">The visual text shown to the user for the option.</param>
  596. /// <seealso cref="OptionDropDownCVar{T}"/>
  597. /// <seealso cref="OptionsTabControlRow.AddOptionDropDown{T}"/>
  598. public sealed class ValueOption(T key, string label)
  599. {
  600. /// <summary>
  601. /// The value that this option has. This is what will be written to the CVar if selected.
  602. /// </summary>
  603. public readonly T Key = key;
  604. /// <summary>
  605. /// The visual text shown to the user for the option.
  606. /// </summary>
  607. public readonly string Label = label;
  608. }
  609. private struct ItemEntry
  610. {
  611. public T Key;
  612. }
  613. }