feat(vtank): slice 7 fix — <menu style> selects plain vs retail art

Wires the new UiMenu.RetailButtonArt switch (previous commit) into
plugin markup: <menu style="plain"> (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
<icon iconkind> 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 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 08:15:23 +02:00
parent cc11e077a4
commit 19c831211b
2 changed files with 75 additions and 0 deletions

View file

@ -461,6 +461,7 @@ public static class MarkupDocument
Func<string?> 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}\")"),
};
/// <summary>
/// Owner live-client report 2026-09-07 ("Those BIG gold/yellow buttons HAS
/// to go. That is not how vtank looks."): validates <c>&lt;menu
/// style="..."&gt;</c> and returns the <see cref="UiMenu.RetailButtonArt"/>
/// value it selects. Default (attribute absent, or explicit
/// <c>style="plain"</c>) is the flat VTank/Decal <c>HudCombo</c> box
/// (<c>false</c>) — retail's gold pushbutton art is now an explicit
/// <c>style="retail"</c> opt-in for a plugin panel that genuinely wants
/// it. Any other value is a Build-time author error, same rule as
/// <see cref="ValidateIconKind"/>.
/// </summary>
private static bool ValidateMenuStyle(string? style) => style switch
{
null or "plain" => false,
"retail" => true,
var other => throw new FormatException(
$"<menu style=\"{other}\"> must be plain or retail"),
};
/// <summary>
/// Builds the zero-argument icon resolver the <c>&lt;icon&gt;</c> element
/// uses: dispatch by <c>iconkind</c> (default <c>"did"</c>) to the

View file

@ -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): <menu style="..."> selects UiMenu.RetailButtonArt.
private sealed class MenuStyleBinding
{
public IReadOnlyList<string> Choices => ["First", "Second"];
public string Selected { get; } = "First";
}
private static string MenuXml(string? styleAttribute) =>
"<panel x=\"0\" y=\"0\" w=\"240\" h=\"60\">" +
$"<menu x=\"4\" y=\"4\" w=\"120\" h=\"20\" items=\"{{Choices}}\" selected=\"{{Selected}}\"{styleAttribute}/>" +
"</panel>";
[Fact]
public void Menu_NoStyleAttribute_DefaultsToPlain_RetailButtonArtFalse()
{
var panel = MarkupDocument.Build(MenuXml(styleAttribute: ""), new MenuStyleBinding(), _ => (1u, 32, 32));
var menu = Assert.IsType<UiMenu>(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<UiMenu>(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<UiMenu>(panel.Children[0]);
Assert.True(menu.RetailButtonArt);
}
[Fact]
public void Menu_UnknownStyle_ThrowsFormatException_NamingTheElement()
{
var ex = Assert.Throws<FormatException>(
() => MarkupDocument.Build(
MenuXml(" style=\"chrome\""), new MenuStyleBinding(), _ => (1u, 32, 32)));
Assert.Contains("menu", ex.Message);
Assert.Contains("chrome", ex.Message);
}
}