feat(ui): mark store-only option rows dimmed (user-directed, gate 2)
User directive (gate 2, verbatim): "mark all options that are not implemented now, so I can clearly see what is not implemented." Store-only rows keep full interactivity (still persist/send) but render their caption in a shared dimmed grey (UiRenderContext.StoreOnlyCaptionColor, matching the existing UiMenu.TextColorGhosted convention) instead of white/DAT color. No invented marker text anywhere -- the dim IS the marker. Config tab (ConfigOptionsPageController, 21 of 27 rows dimmed): Sound Features menu, Interface Sound trio, Play Sound Only When Active (AP-199); Screen Brightness, Automatic Degrades, Graphics Performance, Degrade Distance, the four Rendering Quality menus, Building Detail Textures, Multi-Pass Alpha (AP-198); Camera Stiffness, Camera Adjustment Speed, Align To Slope, Mouse Look Sensitivity, Invert Mouselook Y Axis, Use Mouse Turning (TS-74); Chat Font Face/Size (AP-200). NOT dimmed: Sound/Ambient trios, Resolution, Full Screen (LIVE), VSync and Field of View (NEXT-LAUNCH -- still implemented, just deferred to next process start, per the controller's own doc). Character tab (CharacterOptionsPageController, 35 of 50 rows dimmed): every Group A (wire+store only) and Group D (deferred) row, plus the Group B rows the OP4 gate script's own step 16 confirms are unbound (ShowTooltips, SideBySideVitals, SpellDuration, AdvancedCombatUI, StayInChatMode, DisableMostWeatherEffects, PersistentAtDay, FilterLanguage, MainPackPreferred). NOT dimmed (15 rows): the six ListenTo*Chat ids (TurbineChatMembershipGate), DisableDistanceFog/ DisplayTimeStamps/ToggleRun (bound at GameWindow.cs), the Group-C re-point (ViewCombatTarget/VividTargetingIndicator/CoordinatesOnRadar/ AutoTarget/AutoRepeatAttack), and DragItemOnPlayerOpensSecureTrade (TS-48). Cross-checked against actual shipped consumers via source grep, not just the research doc's Group table, since OP4 only wired a subset of the doc's aspirational Group B. Configure Keyboard (KeyboardConfigController): a row whose RetailActionIdentityTable lookup fails (MappedAction null -- AP-203's Emote/CharacterSettings set) dims its synthesized caption; the key buttons stay fully bindable/persisted/conflict-checked. Chat tab (ChatOptionsPageController): audited, zero store-only rows -- every filter block and both opacity sliders already have a live consumer (ChatWindowState / RetailWindowOpacityController). Ambiguity flagged, not guessed: the character-options-map.md research doc lists AcceptLootPermits in BOTH Group A and Group C; its only code site (LiveSessionRuntimeFactory.cs, the /consent command) is a second setter for the same server bit, not a behavioral reader, so it is classified Group A / dimmed here. Register: AD-78 documents the convention (retail dims nothing; this is a deliberate acdream-only divergence that retires as consumers land). New per-surface conformance tests pin the exact dimmed/live set against a literal expected list, so wiring a future consumer without also flipping its row's literal fails the build: CharacterOptionsPageControllerTests.StoreOnlyRows_MatchTheDerivationTableExactly + Bind_AppliesDimmedCaptionColor_ForStoreOnlyRows_AndWhiteForLiveRows, ConfigOptionsPageControllerTests.CaptionDimming_MatchesTheStoreOnlySetExactly, KeyboardConfigControllerTests.UnmappedRows_DimTheirCaption_MappedRowsStayWhite. Build green; full Release suite 13,086 passed / 4 skipped / 0 failed (baseline 13,082/4/0 -- delta is exactly the four new tests above). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8bd7e3b88d
commit
3441a71833
10 changed files with 563 additions and 93 deletions
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.UI.Abstractions.Panels.Settings;
|
||||
|
|
@ -858,4 +859,108 @@ public sealed class ConfigOptionsPageControllerTests
|
|||
Assert.True(bound);
|
||||
Assert.Equal(30, controller.ConfigPage.Rows.Count);
|
||||
}
|
||||
|
||||
// ── AD-78 caption dimming (user-directed, 2026-08-11, gate 2) ───────────
|
||||
|
||||
private enum RowKind { Toggle, TrioToggle, Slider, Menu }
|
||||
|
||||
/// <summary>
|
||||
/// One literal expectation per option-row-bearing viewport item, in the
|
||||
/// SAME order <see cref="ConfigOptionsPageController.Bind"/> builds them
|
||||
/// (39 stacked items: 6 headers + 6 separators + 27 option rows — see
|
||||
/// <see cref="Bind_ListBoxStacks39Items_SixHeaders_SixSeparators_27OptionRows"/>
|
||||
/// for the index arithmetic this table shares). <b>Wiring a future
|
||||
/// consumer for any row here means flipping BOTH this literal's
|
||||
/// <c>StoreOnly</c> AND the matching <c>ConfigOptionsPageController</c>
|
||||
/// <c>BindXxxSection</c> call site's own <c>storeOnly:</c> argument
|
||||
/// consciously — leaving either one stale fails this test.</b>
|
||||
/// </summary>
|
||||
private static readonly (int ViewportIndex, RowKind Kind, bool StoreOnly, string Label)[]
|
||||
DimmingExpectations =
|
||||
{
|
||||
(1, RowKind.Menu, true, "Sound Features"), // AP-199
|
||||
(2, RowKind.TrioToggle, false, "Disable Sound Effects"), // LIVE
|
||||
(3, RowKind.TrioToggle, false, "Disable Ambient Sound"), // LIVE
|
||||
(4, RowKind.TrioToggle, true, "Disable Interface Sound"), // AP-174/AP-199
|
||||
(5, RowKind.Toggle, true, "Play Sound Only When Active"), // AP-199
|
||||
(8, RowKind.Slider, true, "Camera Stiffness"), // TS-74
|
||||
(9, RowKind.Slider, true, "Camera Adjustment Speed"), // TS-74
|
||||
(10, RowKind.Slider, false, "Field Of View"), // NEXT-LAUNCH
|
||||
(11, RowKind.Toggle, true, "Align To Slope"), // TS-74
|
||||
(14, RowKind.Menu, false, "Resolution"), // LIVE
|
||||
(15, RowKind.Toggle, false, "Full Screen"), // LIVE
|
||||
(16, RowKind.Toggle, false, "Sync To Refresh"), // NEXT-LAUNCH
|
||||
(17, RowKind.Slider, true, "Screen Brightness"), // review S2
|
||||
(18, RowKind.Toggle, true, "Automatic Degrades"), // AP-198
|
||||
(19, RowKind.Slider, true, "Graphics Performance"), // AP-198
|
||||
(20, RowKind.Slider, true, "Degrade Distance"), // AP-198
|
||||
(23, RowKind.Menu, true, "Landscape Texture Detail"), // AP-198
|
||||
(24, RowKind.Menu, true, "Environment Texture Detail"), // AP-198
|
||||
(25, RowKind.Menu, true, "Texture Filtering"), // AP-198
|
||||
(26, RowKind.Menu, true, "Landscape Draw Distance"), // AP-198
|
||||
(27, RowKind.Toggle, true, "Building Detail Textures"), // AP-198
|
||||
(28, RowKind.Toggle, true, "Multi-Pass Alpha"), // AP-198
|
||||
(31, RowKind.Slider, true, "Mouse Look Sensitivity"), // TS-74
|
||||
(32, RowKind.Toggle, true, "Invert Mouselook Y Axis"), // TS-74
|
||||
(33, RowKind.Toggle, true, "Use Mouse Turning"), // TS-74
|
||||
(36, RowKind.Menu, true, "Chat Font Face"), // AP-200
|
||||
(37, RowKind.Menu, true, "Chat Font Size"), // AP-200
|
||||
};
|
||||
|
||||
private static Vector4? FindTextLineColor(UiElement root, uint elementId)
|
||||
{
|
||||
if (UiElement.FindDescendant(root, elementId) is not UiText text)
|
||||
return null;
|
||||
IReadOnlyList<UiText.Line> lines = text.LinesProvider();
|
||||
return lines.Count == 0 ? null : lines[0].Color;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CaptionDimming_MatchesTheStoreOnlySetExactly()
|
||||
{
|
||||
// A non-null constant resolver is required here (unlike most of this
|
||||
// file's BindReal() default): SetLabelText's dim color is baked into
|
||||
// the UiText.Line closure only when a label actually resolves, so a
|
||||
// null resolver would leave every slider/menu label's LinesProvider
|
||||
// at its empty default and hide the very thing this test checks.
|
||||
(OptionsPanelController controller, _, bool bound) = BindReal(resolveString: (_, _) => "x");
|
||||
Assert.True(bound);
|
||||
|
||||
var configSlot = UiElement.FindDescendant(controller.TabPanel, ConfigPageSlotId)!;
|
||||
var listBox = Assert.IsType<UiTemplateListBox>(
|
||||
UiElement.FindDescendant(configSlot, ConfigOptionsPageController.ListBoxElementId));
|
||||
UiElement viewport = Assert.Single(listBox.Children);
|
||||
IReadOnlyList<UiElement> items = viewport.Children.ToList();
|
||||
Assert.Equal(39, items.Count);
|
||||
|
||||
const uint ToggleCheckboxElementId = 0x10000219u;
|
||||
const uint SliderLabelElementId = 0x1000021Bu;
|
||||
const uint MenuLabelElementId = 0x10000223u;
|
||||
|
||||
foreach ((int index, RowKind kind, bool storeOnly, string label) in DimmingExpectations)
|
||||
{
|
||||
Vector4 expected = storeOnly
|
||||
? UiRenderContext.StoreOnlyCaptionColor
|
||||
: Vector4.One;
|
||||
Vector4? actual = kind switch
|
||||
{
|
||||
RowKind.Toggle =>
|
||||
(UiElement.FindDescendant(items[index], ToggleCheckboxElementId) as UiButton)?.LabelColor,
|
||||
RowKind.TrioToggle =>
|
||||
Assert.IsType<UiOptionToggleSlider>(items[index]).Toggle?.LabelColor,
|
||||
RowKind.Slider => FindTextLineColor(items[index], SliderLabelElementId),
|
||||
RowKind.Menu => FindTextLineColor(items[index], MenuLabelElementId),
|
||||
_ => throw new InvalidOperationException($"unhandled row kind {kind}"),
|
||||
};
|
||||
Assert.True(
|
||||
actual.HasValue,
|
||||
$"{label} (viewport index {index}, kind {kind}): could not locate the "
|
||||
+ "caption widget/line to check its color.");
|
||||
Assert.True(
|
||||
expected == actual.Value,
|
||||
$"{label} (viewport index {index}): expected "
|
||||
+ $"{(storeOnly ? "DIMMED" : "LIVE")} caption color {expected} but the "
|
||||
+ $"built widget rendered {actual.Value}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue