From 19c831211b437dbf5e9045cdad56ab14eb458d33 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 7 Sep 2026 08:15:23 +0200 Subject: [PATCH] =?UTF-8?q?feat(vtank):=20slice=207=20fix=20=E2=80=94=20=20selects=20plain=20vs=20retail=20art?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires the new UiMenu.RetailButtonArt switch (previous commit) into plugin markup: (also the default when the attribute is absent) builds RetailButtonArt=false so a plugin's dropdown gets the flat VTank-matching box; style="retail" opts a panel back into the gold pushbutton face. Any other value throws FormatException at Build naming the element, matching the existing validation convention (ValidateIconKind). Mutation check: temporarily stubbed ValidateMenuStyle to always return true (as if the switch didn't exist) — 3 of the 4 new MarkupDocumentTests.Menu_* tests failed exactly as expected (Menu_NoStyleAttribute_DefaultsToPlain_RetailButtonArtFalse, Menu_StylePlain_Explicit_RetailButtonArtFalse, Menu_UnknownStyle_ThrowsFormatException_NamingTheElement); the style="retail" test passed trivially either way, as expected for that case. Restored before committing. Co-Authored-By: Claude Fable 5.1 --- src/AcDream.App/UI/MarkupDocument.cs | 21 ++++++++ .../UI/MarkupDocumentTests.cs | 54 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/AcDream.App/UI/MarkupDocument.cs b/src/AcDream.App/UI/MarkupDocument.cs index c152a043a..3d14d983a 100644 --- a/src/AcDream.App/UI/MarkupDocument.cs +++ b/src/AcDream.App/UI/MarkupDocument.cs @@ -461,6 +461,7 @@ public static class MarkupDocument Func menuSelected = BindString( (string?)el.Attribute("selected"), binding); + bool menuRetailButtonArt = ValidateMenuStyle((string?)el.Attribute("style")); var menu = new UiMenu { Left = F(el, "x"), @@ -480,6 +481,7 @@ public static class MarkupDocument PopupBgSprite = 0x0600124Cu, ItemNormalSprite = 0x0600124Eu, ItemHighlightSprite = 0x0600124Du, + RetailButtonArt = menuRetailButtonArt, ButtonLabelProvider = () => menuSelected() ?? string.Empty, OnSelect = payload => { @@ -633,6 +635,25 @@ public static class MarkupDocument $"{context} must be did, spell, or item (got \"{other}\")"), }; + /// + /// Owner live-client report 2026-09-07 ("Those BIG gold/yellow buttons HAS + /// to go. That is not how vtank looks."): validates <menu + /// style="..."> and returns the + /// value it selects. Default (attribute absent, or explicit + /// style="plain") is the flat VTank/Decal HudCombo box + /// (false) — retail's gold pushbutton art is now an explicit + /// style="retail" opt-in for a plugin panel that genuinely wants + /// it. Any other value is a Build-time author error, same rule as + /// . + /// + private static bool ValidateMenuStyle(string? style) => style switch + { + null or "plain" => false, + "retail" => true, + var other => throw new FormatException( + $" must be plain or retail"), + }; + /// /// Builds the zero-argument icon resolver the <icon> element /// uses: dispatch by iconkind (default "did") to the diff --git a/tests/AcDream.App.Tests/UI/MarkupDocumentTests.cs b/tests/AcDream.App.Tests/UI/MarkupDocumentTests.cs index bb3cb9b11..1a1f864fd 100644 --- a/tests/AcDream.App.Tests/UI/MarkupDocumentTests.cs +++ b/tests/AcDream.App.Tests/UI/MarkupDocumentTests.cs @@ -314,4 +314,58 @@ public class MarkupDocumentTests Assert.Equal(0.78f, binding.AttackPower); Assert.Equal(0.78f, slider.ScalarPositionSource!()); } + + // ── S7 fix ("BIG gold/yellow buttons has to go" — owner live-client + // report 2026-09-07): selects UiMenu.RetailButtonArt. + + private sealed class MenuStyleBinding + { + public IReadOnlyList Choices => ["First", "Second"]; + public string Selected { get; } = "First"; + } + + private static string MenuXml(string? styleAttribute) => + "" + + $"" + + ""; + + [Fact] + public void Menu_NoStyleAttribute_DefaultsToPlain_RetailButtonArtFalse() + { + var panel = MarkupDocument.Build(MenuXml(styleAttribute: ""), new MenuStyleBinding(), _ => (1u, 32, 32)); + var menu = Assert.IsType(panel.Children[0]); + + Assert.False(menu.RetailButtonArt); + } + + [Fact] + public void Menu_StylePlain_Explicit_RetailButtonArtFalse() + { + var panel = MarkupDocument.Build( + MenuXml(" style=\"plain\""), new MenuStyleBinding(), _ => (1u, 32, 32)); + var menu = Assert.IsType(panel.Children[0]); + + Assert.False(menu.RetailButtonArt); + } + + [Fact] + public void Menu_StyleRetail_OptsIntoTheGoldButtonArt() + { + var panel = MarkupDocument.Build( + MenuXml(" style=\"retail\""), new MenuStyleBinding(), _ => (1u, 32, 32)); + var menu = Assert.IsType(panel.Children[0]); + + Assert.True(menu.RetailButtonArt); + } + + [Fact] + public void Menu_UnknownStyle_ThrowsFormatException_NamingTheElement() + { + var ex = Assert.Throws( + () => MarkupDocument.Build( + MenuXml(" style=\"chrome\""), new MenuStyleBinding(), _ => (1u, 32, 32))); + + Assert.Contains("menu", ex.Message); + Assert.Contains("chrome", ex.Message); + } }