feat(ui): Campaign OP slice OP6 — the Config tab
Binds the retail Options panel's Config tab (LayoutDesc 0x21000029, 27 authored rows across 6 sections) through OP2's template mechanism and OP3's per-page OptionPage model, matching the Character/Chat tab controllers' established pattern. The row table is transcribed directly from two decompiled sources — gmConfigUI::InitOptions @0x0049E400 (row order, widget shape, defaults) and gmClient::InitUIPreferences @0x004035b0 (the complete UIPreferences::AttachPreference registration: every label/tooltip key, every slider's real-unit range, every menu's enum choices) — which resolves the research docs' own "U4" unverified slider-caption pairing: retail ships ZERO range captions on this tab (every SetSliderLabel call passes literal string id 0). Consumer disposition: LIVE — Sound/Ambient volume-trio sliders and their toggle halves (AudioSettings.SfxDisabled/AmbientDisabled now gate the already-live engine write; RuntimeSettingsController.SaveAudio newly pushes into OpenAlAudioEngine on every change, not just at startup), Resolution/Full Screen (immediate window resize on save). NEXT-LAUNCH (pre-existing precedent): Sync To Refresh, Field of View. STORE-ONLY (register rows AP-198/199/200, TS-74 extended): Sound Features/Interface trio/Play-Only-When-Active, the nine Graphics/Rendering-Quality rows (Vulkan has no per-feature render knobs), Camera/Input's six rows and Use Mouse Turning (no persistent mouse-turning camera mode), Chat Font Face/Size (distinct new fields from the existing live ChatSettings.FontSize). AudioSettings/DisplaySettings/CameraTurningSettings/ChatSettings each gain new fields for their slice of the 27 rows, backed by SettingsStore round-trips. A real bug caught by testing: the scrollbar scope lookup used the standalone-layout root id (0x100001FF), which does not survive base-merge into the host-mounted tree — fixed to scope from the tab host's own page-slot id (0x10000213), matching Chat's established pattern for the same shared-scrollbar-id hazard (0x10000201, authored by both the Chat and Config ListBoxes). 30 new tests (27 authored rows register as 30 IOptionRow instances — the three toggle+slider trios each register two). Full Release suite: 13,107 passed / 4 skipped / 0 failed (was 13,083/4/0 — net +24, the one existing RuntimeSettingsControllerTests case updated for SaveAudio's new live-apply call, not a regression). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
527a3a4056
commit
f5ac1742ba
16 changed files with 2188 additions and 30 deletions
|
|
@ -279,6 +279,159 @@ public sealed class FloatOptionRow : IOptionRow
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign OP slice OP6: a <c>UIOption_Menu</c> leaf's current/saved/default
|
||||
/// triple over an <see cref="int"/> — the Config tab's menu rows (Sound
|
||||
/// Features, Resolution, the four texture-detail-family selectors, Chat Font
|
||||
/// Face/Size). Same shape as <see cref="BoolOptionRow"/>/<see cref="FloatOptionRow"/>
|
||||
/// (research doc §3.3's leaf semantics apply identically — every
|
||||
/// <c>UIOption</c> subclass shares the same base current/saved/default verbs).
|
||||
/// <see cref="SetCurrentValue"/> is what picking a popup row runs — applies
|
||||
/// live immediately, does not touch <see cref="Saved"/>.
|
||||
/// </summary>
|
||||
public sealed class IntOptionRow : IOptionRow
|
||||
{
|
||||
private readonly Action<int>? _apply;
|
||||
private readonly Func<int>? _read;
|
||||
private readonly Action<int>? _refresh;
|
||||
private Action? _notifyPageOptionChanged;
|
||||
private int _current;
|
||||
private int _saved;
|
||||
private int _default;
|
||||
|
||||
public IntOptionRow(
|
||||
int initial,
|
||||
int defaultValue,
|
||||
Action<int>? apply = null,
|
||||
Func<int>? read = null,
|
||||
Action<int>? refresh = null)
|
||||
{
|
||||
_current = initial;
|
||||
_saved = initial;
|
||||
_default = defaultValue;
|
||||
_apply = apply;
|
||||
_read = read;
|
||||
_refresh = refresh;
|
||||
}
|
||||
|
||||
/// <summary>The live value — the menu's currently-selected payload.</summary>
|
||||
public int Current => _current;
|
||||
|
||||
/// <summary>The committed baseline Reset reverts to.</summary>
|
||||
public int Saved => _saved;
|
||||
|
||||
/// <summary>The value Defaults restores.</summary>
|
||||
public int DefaultValue => _default;
|
||||
|
||||
public bool Changed => _saved != _current;
|
||||
|
||||
public void SetDefaultValue(int value) => _default = value;
|
||||
|
||||
public void SetCurrentValue(int value)
|
||||
{
|
||||
_current = value;
|
||||
_apply?.Invoke(value);
|
||||
_notifyPageOptionChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void AttachPageNotify(Action notify) => _notifyPageOptionChanged = notify;
|
||||
|
||||
public void SaveCurrentValue()
|
||||
{
|
||||
if (_read is not null)
|
||||
{
|
||||
_current = _read();
|
||||
_refresh?.Invoke(_current);
|
||||
}
|
||||
_saved = _current;
|
||||
}
|
||||
|
||||
public void RestoreSavedValue()
|
||||
{
|
||||
_current = _saved;
|
||||
_apply?.Invoke(_current);
|
||||
}
|
||||
|
||||
public void RestoreDefaultValue()
|
||||
{
|
||||
_current = _default;
|
||||
_apply?.Invoke(_current);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign OP slice OP6: a <c>UIOption_Menu</c> leaf's current/saved/default
|
||||
/// triple over a <see cref="string"/> payload — used ONLY for the Config
|
||||
/// tab's Resolution row, whose retail <c>UIOption_Menu::SetUserPreference</c>
|
||||
/// path (built via <c>AddMenuOption(..., arg3=0)</c>, the tab's own
|
||||
/// documented outlier) carries the resolution string itself rather than an
|
||||
/// enum-choice index. Same current/saved/default shape as
|
||||
/// <see cref="IntOptionRow"/>/<see cref="BoolOptionRow"/>.
|
||||
/// </summary>
|
||||
public sealed class StringOptionRow : IOptionRow
|
||||
{
|
||||
private readonly Action<string>? _apply;
|
||||
private readonly Func<string>? _read;
|
||||
private readonly Action<string>? _refresh;
|
||||
private Action? _notifyPageOptionChanged;
|
||||
private string _current;
|
||||
private string _saved;
|
||||
private string _default;
|
||||
|
||||
public StringOptionRow(
|
||||
string initial,
|
||||
string defaultValue,
|
||||
Action<string>? apply = null,
|
||||
Func<string>? read = null,
|
||||
Action<string>? refresh = null)
|
||||
{
|
||||
_current = initial;
|
||||
_saved = initial;
|
||||
_default = defaultValue;
|
||||
_apply = apply;
|
||||
_read = read;
|
||||
_refresh = refresh;
|
||||
}
|
||||
|
||||
public string Current => _current;
|
||||
public string Saved => _saved;
|
||||
public string DefaultValue => _default;
|
||||
public bool Changed => _saved != _current;
|
||||
|
||||
public void SetDefaultValue(string value) => _default = value;
|
||||
|
||||
public void SetCurrentValue(string value)
|
||||
{
|
||||
_current = value;
|
||||
_apply?.Invoke(value);
|
||||
_notifyPageOptionChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void AttachPageNotify(Action notify) => _notifyPageOptionChanged = notify;
|
||||
|
||||
public void SaveCurrentValue()
|
||||
{
|
||||
if (_read is not null)
|
||||
{
|
||||
_current = _read();
|
||||
_refresh?.Invoke(_current);
|
||||
}
|
||||
_saved = _current;
|
||||
}
|
||||
|
||||
public void RestoreSavedValue()
|
||||
{
|
||||
_current = _saved;
|
||||
_apply?.Invoke(_current);
|
||||
}
|
||||
|
||||
public void RestoreDefaultValue()
|
||||
{
|
||||
_current = _default;
|
||||
_apply?.Invoke(_current);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign OP slice OP5: one of the Chat tab's five per-window text-filter
|
||||
/// <c>UIOption_CheckboxBitfield64</c> blocks — the current/saved/default triple over
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue