fix(vtank): slice 7 round F item 6 — Buffs gap, Loot Editor Back, Route count column
Three small resemblance-re-check nits. Buffs: the extras strip (toggles/Difficulty/Rebuff/Buff button, no VTank counterpart) stayed at its pre-Round-D x=332 while Round D item 4's own +128 right-anchor shift moved the Blacklisted Buff Families list from x=524 to x=652 — an 8px left margin against a 138px right margin. Discovered a hard geometric ceiling while fixing this: Extra Buff Spells never moves (anchor="left top bottom", pinned x=4..324) but Blacklisted Buff Families tracks the growing right edge, sitting at x=524 at the panel's own tested FLOOR (856x236) and x=652 at the 984-wide enlarged default — a 128px swing. A single fixed-position strip can only be centered against ONE of those widths; centering against the 984 default (tried x=397 first) put the strip's right edge 55px inside where the list sits at the floor — a real overlap the existing ResolvedMainPanelHasNoOverlapOrOutOfBoundsChildAtThisSize pin (856x236 case) caught immediately. x=333 is the widest safe position (9px margins against the floor's 324..524 gap) — 1px from where fix round B item 2 already had it. The residual 984-default asymmetry (~9/137, barely changed from ~8/138) is an accepted, unavoidable- without-a-redesign limitation, the same "dead margin, polish later" trade already accepted for six other tabs at this size. Coverage moved from y=213 (anchored to the group's bottom edge, a real 55px empty hole once the panel grew past its original 194-tall size) to sit directly under the Buff button row (y=176, no anchor). Loot Editor: dropped the leftover standalone "Back" button — the window's own OS-level title bar already closes it, matching every other plugin popup (Advanced Options included) since round D. CloseLootEditor stays real, still called by ToggleLootEditorVisible. Route: the waypoint grid's count column (declared width="30") was the grid's LAST column, so docs/plugin-ui-markup.md's own "Width semantics" rule made that declared width never validated/used — it silently absorbed whatever remainder was left (33px normally, but only 17px once the scrollbar's 16px reservation ate into the list's own 370px width, since 337+30 already exceeded 370-16=354). A trailing filler column alone wasn't sufficient — proved by a targeted mutation below — since even non-last, a truly fixed 30px column still gets clamped by the same 354px ceiling. The real text column narrows 337->324 (324+30=354, exactly matching the scrollbar-reduced width) so the count column gets its full 30px whether or not the scrollbar shows. Every new/changed pin shown to fail against a targeted mutation first: LootEditorHasNoLeftoverBackButton failed with the button re-inserted, then passed once removed. RouteShapedGrid_CountColumnStaysThirtyPxWhen TheListScrolls (a synthetic list matching Route's real 370/324/30/* shape, 9 rows forcing the scrollbar, reflecting into UiMarkupList's own _cachedLayout) failed both against the original 2-column 337/30 shape (17px, reproducing the reported squeeze) AND against filler-only-no- narrowing (also 17px, proving the filler alone isn't the fix), then passed at exactly 30px once both changes landed together. BuffsExtrasStripIsCenteredAtThePanelsFloorAndCoverageSitsUnderTheButtonRow and its App-side resolved-tree counterpart BuffsExtrasStripStaysCenteredAndNonOverlappingAtTheFloor pin the floor-centered position and the Coverage gap. MossTank suite 725 -> 726 (one new pin); App markup/plugin filter 260 -> 262 (two new pins). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
bae34aaa8b
commit
8376105936
6 changed files with 312 additions and 32 deletions
|
|
@ -1040,6 +1040,18 @@ internal sealed partial class MossTankPanel
|
||||||
// PluginCore.cs:3576-3599 — any cell click deletes the waypoint).
|
// PluginCore.cs:3576-3599 — any cell click deletes the waypoint).
|
||||||
public IReadOnlyList<string> RouteWaypointTextColumn => _routeRows;
|
public IReadOnlyList<string> RouteWaypointTextColumn => _routeRows;
|
||||||
public IReadOnlyList<string> RouteWaypointCountColumn => _routeWaypointCounts;
|
public IReadOnlyList<string> RouteWaypointCountColumn => _routeWaypointCounts;
|
||||||
|
// Round F item 6 (resemblance re-check: "Route's count column
|
||||||
|
// squeezes to 17 when the list scrolls"): RouteWaypointCountColumn
|
||||||
|
// was the grid's LAST column, so docs/plugin-ui-markup.md's own
|
||||||
|
// "Width semantics" rule made its declared width="30" NEVER validated
|
||||||
|
// or used — it silently absorbed whatever remainder was left instead
|
||||||
|
// (33px normally, but only 17px once the scrollbar's own 16px
|
||||||
|
// reservation ate into that remainder). A trailing filler column
|
||||||
|
// (empty items) now sits after it so the count column is no longer
|
||||||
|
// last and keeps its own real, always-honored 30px width — the same
|
||||||
|
// fix as the Monsters grid's MonsterFillerColumn.
|
||||||
|
public IReadOnlyList<string> RouteWaypointFillerColumn => Array.Empty<string>();
|
||||||
|
public Action<int> RouteWaypointFillerClick => static _ => { };
|
||||||
public Action<int> DeleteRouteWaypointAt => DeleteRouteWaypointAtCore;
|
public Action<int> DeleteRouteWaypointAt => DeleteRouteWaypointAtCore;
|
||||||
public Action SelectNearestRouteWaypoint => SelectNearestRouteWaypointCore;
|
public Action SelectNearestRouteWaypoint => SelectNearestRouteWaypointCore;
|
||||||
// VTank's own cmbNavType captions read Circular/Linear/Follow/Once
|
// VTank's own cmbNavType captions read Circular/Linear/Follow/Once
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,20 @@
|
||||||
panel, registered as a THIRD AddPanel call from MossTankPlugin.cs with
|
panel, registered as a THIRD AddPanel call from MossTankPlugin.cs with
|
||||||
ShowInSidePanel=false (VTank's popup has no independent shelf entry). This
|
ShowInSidePanel=false (VTank's popup has no independent shelf entry). This
|
||||||
panel's own visible="{LootEditorVisible}" binding is the SAME flag the
|
panel's own visible="{LootEditorVisible}" binding is the SAME flag the
|
||||||
checkbox flips and this popup's own "Back" button clears
|
checkbox flips and CloseLootEditor clears
|
||||||
(CloseLootEditor => _lootEditorVisible = false) — closing the popup
|
(CloseLootEditor => _lootEditorVisible = false) — closing the popup
|
||||||
unchecks the box for free (see mosstank-advanced.xml's comment for why no
|
unchecks the box for free (see mosstank-advanced.xml's comment for why no
|
||||||
new host show/hide API was needed).
|
new host show/hide API was needed).
|
||||||
|
|
||||||
|
Round F item 6 (resemblance re-check: "Loot Editor: drop the leftover
|
||||||
|
'Back' button — the window closes from its title bar like the
|
||||||
|
others"): the standalone "Back" button bound directly to
|
||||||
|
CloseLootEditor is gone — the window's own OS-level title bar already
|
||||||
|
closes it, the same shape every other plugin popup (Advanced Options
|
||||||
|
included) has used with no Back button of its own since round D.
|
||||||
|
CloseLootEditor stays real: ToggleLootEditorVisible (the Profiles-tab
|
||||||
|
"Show Editor" checkbox's own close path) still calls it directly.
|
||||||
|
|
||||||
Fix round A also replaced the "↑"/"↓" text buttons (MoveLootRuleUp/Down)
|
Fix round A also replaced the "↑"/"↓" text buttons (MoveLootRuleUp/Down)
|
||||||
with the same DAT move-up/move-down icon glyphs the Monsters grid already
|
with the same DAT move-up/move-down icon glyphs the Monsters grid already
|
||||||
uses (0x060028FC / 0x060028FD) — the retail default UI font (0x40000000)
|
uses (0x060028FC / 0x060028FD) — the retail default UI font (0x40000000)
|
||||||
|
|
@ -40,8 +49,6 @@
|
||||||
-->
|
-->
|
||||||
<panel x="660" y="405" w="268" h="300" title="MossTank Loot Editor"
|
<panel x="660" y="405" w="268" h="300" title="MossTank Loot Editor"
|
||||||
visible="{LootEditorVisible}" resize="none">
|
visible="{LootEditorVisible}" resize="none">
|
||||||
<button x="198" y="2" w="64" h="18" text="Back"
|
|
||||||
onclick="{CloseLootEditor}" />
|
|
||||||
<field x="4" y="22" w="260" h="18" text="{LootProfileNameDraft}"
|
<field x="4" y="22" w="260" h="18" text="{LootProfileNameDraft}"
|
||||||
onchange="{SetLootProfileNameDraft}"
|
onchange="{SetLootProfileNameDraft}"
|
||||||
onsubmit="{CreateNamedLootProfile}" maxlength="64"
|
onsubmit="{CreateNamedLootProfile}" maxlength="64"
|
||||||
|
|
|
||||||
|
|
@ -582,13 +582,42 @@
|
||||||
catches. Both lists are restored to VTank's full 320x116 at the far
|
catches. Both lists are restored to VTank's full 320x116 at the far
|
||||||
left (x=4) and far right (x=524, 848-4-320) with their own
|
left (x=4) and far right (x=524, 848-4-320) with their own
|
||||||
120-wide "Add..." row below each list; the whole MossTank-extras
|
120-wide "Add..." row below each list; the whole MossTank-extras
|
||||||
block moves into the EMPTY MIDDLE strip between the two lists
|
block moves into the EMPTY MIDDLE strip between the two lists,
|
||||||
(x=332..516, the 184px VTank's own layout never uses), stacked
|
stacked compactly: 7 toggles at a 16px pitch, then Difficulty +/-,
|
||||||
compactly: 7 toggles at a 16px pitch, then Difficulty +/-, then
|
then Rebuff +/-, then the Buff button + status, then Coverage
|
||||||
Rebuff +/-, then the Buff button + status, then Coverage at y=178
|
right under it — a proportion-preserving compromise fitting a
|
||||||
(194-16, item 3's bottom-fit rule) — a proportion-preserving
|
real box, not a pixel-for-pixel VVS port (owner's bar: "looks
|
||||||
compromise fitting a real 184x194 box, not a pixel-for-pixel VVS
|
basically the same", never VVS pixels).
|
||||||
port (owner's bar: "looks basically the same", never VVS pixels). -->
|
|
||||||
|
Round F item 6 (resemblance re-check: "Buffs' 136 px middle gap
|
||||||
|
and the orphaned Coverage label"). Coverage moved from y=213
|
||||||
|
(anchored to the group's bottom edge, a real 55px empty hole
|
||||||
|
above it once the panel grew past its original 194-tall size) to
|
||||||
|
sit directly under the Buff button row instead — no anchor, since
|
||||||
|
nothing below it needs the space that anchor used to protect.
|
||||||
|
|
||||||
|
The middle-gap half of this item turned out to have a hard
|
||||||
|
geometric ceiling: Extra Buff Spells never moves (anchor="left
|
||||||
|
top bottom", pinned at x=4..324) but Blacklisted Buff Families
|
||||||
|
tracks the growing right edge (anchor="right"), sitting at
|
||||||
|
x=524 at the panel's own tested floor (856x236, PanelIsResizable
|
||||||
|
FlooredAtThePreRoundDAuthoredSize) and x=652 at the 984-wide
|
||||||
|
enlarged default — a 128px swing. The extras strip (182px wide)
|
||||||
|
is a single FIXED position, so it can only be centered relative
|
||||||
|
to ONE of those two widths; centering it against the 984 default
|
||||||
|
(x=397) put its right edge at 579, 55px INSIDE where the same
|
||||||
|
list sits at the floor (524) — a real overlap
|
||||||
|
ResolvedMainPanelHasNoOverlapOrOutOfBoundsChildAtThisSize's own
|
||||||
|
856x236 case catches. x=333 is the strip's floor-centered
|
||||||
|
position (9px margin on both sides against the floor's 324..524
|
||||||
|
gap, the widest safe position a fixed strip can take) — the same
|
||||||
|
position fix round B item 2 effectively already used (x=332, 1px
|
||||||
|
off from exactly centered) before Round D's enlargement existed.
|
||||||
|
The residual asymmetric gap at the 984 default (previously ~8/138,
|
||||||
|
now ~9/137) is therefore an accepted, unavoidable-without-a-
|
||||||
|
redesign limitation — the same "dead margin, polish later" trade
|
||||||
|
the resemblance re-check already accepted for six other tabs at
|
||||||
|
this same enlarged size. -->
|
||||||
<group x="8" y="42" w="976" h="229" visible="{BuffsVisible}" anchor="left top right bottom">
|
<group x="8" y="42" w="976" h="229" visible="{BuffsVisible}" anchor="left top right bottom">
|
||||||
<!-- Fix round B item 14 (owner honesty rule): BuffPlan.Build does not
|
<!-- Fix round B item 14 (owner honesty rule): BuffPlan.Build does not
|
||||||
read this set at all yet — a real, tracked wiring gap (see
|
read this set at all yet — a real, tracked wiring gap (see
|
||||||
|
|
@ -616,39 +645,39 @@
|
||||||
onchange="{DeleteBlacklistedBuffFamilyAt}" tooltip="Click a row to remove it." />
|
onchange="{DeleteBlacklistedBuffFamilyAt}" tooltip="Click a row to remove it." />
|
||||||
<button x="652" y="173" w="120" h="18" text="Add..." onclick="{ShowBlacklistedBuffPicker}" anchor="right bottom" />
|
<button x="652" y="173" w="120" h="18" text="Add..." onclick="{ShowBlacklistedBuffPicker}" anchor="right bottom" />
|
||||||
|
|
||||||
<toggle x="332" y="0" w="180" h="16" text="Trained skills only"
|
<toggle x="333" y="0" w="180" h="16" text="Trained skills only"
|
||||||
checked="{TrainedOnlyEnabled}" onclick="{ToggleTrainedOnly}" />
|
checked="{TrainedOnlyEnabled}" onclick="{ToggleTrainedOnly}" />
|
||||||
<toggle x="332" y="16" w="180" h="16" text="Attributes"
|
<toggle x="333" y="16" w="180" h="16" text="Attributes"
|
||||||
checked="{AttributesEnabled}" onclick="{ToggleAttributes}" />
|
checked="{AttributesEnabled}" onclick="{ToggleAttributes}" />
|
||||||
<toggle x="332" y="32" w="180" h="16" text="Protections"
|
<toggle x="333" y="32" w="180" h="16" text="Protections"
|
||||||
checked="{ProtectionsEnabled}" onclick="{ToggleProtections}" />
|
checked="{ProtectionsEnabled}" onclick="{ToggleProtections}" />
|
||||||
<toggle x="332" y="48" w="180" h="16" text="Weapon auras"
|
<toggle x="333" y="48" w="180" h="16" text="Weapon auras"
|
||||||
checked="{AurasEnabled}" onclick="{ToggleAuras}" />
|
checked="{AurasEnabled}" onclick="{ToggleAuras}" />
|
||||||
<toggle x="332" y="64" w="180" h="16" text="Armor banes"
|
<toggle x="333" y="64" w="180" h="16" text="Armor banes"
|
||||||
checked="{BanesEnabled}" onclick="{ToggleBanes}" />
|
checked="{BanesEnabled}" onclick="{ToggleBanes}" />
|
||||||
<toggle x="332" y="80" w="180" h="16" text="Regeneration"
|
<toggle x="333" y="80" w="180" h="16" text="Regeneration"
|
||||||
checked="{RegenerationEnabled}" onclick="{ToggleRegeneration}" />
|
checked="{RegenerationEnabled}" onclick="{ToggleRegeneration}" />
|
||||||
<toggle x="332" y="96" w="180" h="16" text="Other self-spells"
|
<toggle x="333" y="96" w="180" h="16" text="Other self-spells"
|
||||||
checked="{OtherEnabled}" onclick="{ToggleOther}" />
|
checked="{OtherEnabled}" onclick="{ToggleOther}" />
|
||||||
|
|
||||||
<label x="332" y="116" w="140" text="{DifficultyText}" color="#FFE8DEC3" />
|
<label x="333" y="116" w="140" text="{DifficultyText}" color="#FFE8DEC3" />
|
||||||
<button x="472" y="112" w="20" h="18" text="-" onclick="{DifficultyDown}"
|
<button x="473" y="112" w="20" h="18" text="-" onclick="{DifficultyDown}"
|
||||||
tooltip="Decrease the skill-over-difficulty margin." />
|
tooltip="Decrease the skill-over-difficulty margin." />
|
||||||
<button x="494" y="112" w="20" h="18" text="+" onclick="{DifficultyUp}"
|
<button x="495" y="112" w="20" h="18" text="+" onclick="{DifficultyUp}"
|
||||||
tooltip="Increase the skill-over-difficulty margin." />
|
tooltip="Increase the skill-over-difficulty margin." />
|
||||||
<label x="332" y="136" w="140" text="{RebuffText}" color="#FFE8DEC3" />
|
<label x="333" y="136" w="140" text="{RebuffText}" color="#FFE8DEC3" />
|
||||||
<button x="472" y="132" w="20" h="18" text="-" onclick="{RebuffDown}"
|
<button x="473" y="132" w="20" h="18" text="-" onclick="{RebuffDown}"
|
||||||
tooltip="Rebuff later." />
|
tooltip="Rebuff later." />
|
||||||
<button x="494" y="132" w="20" h="18" text="+" onclick="{RebuffUp}"
|
<button x="495" y="132" w="20" h="18" text="+" onclick="{RebuffUp}"
|
||||||
tooltip="Rebuff earlier." />
|
tooltip="Rebuff earlier." />
|
||||||
|
|
||||||
<button x="332" y="154" w="110" h="18" text="{BuffButtonText}" onclick="{Buff}" />
|
<button x="333" y="154" w="110" h="18" text="{BuffButtonText}" onclick="{Buff}" />
|
||||||
<label x="446" y="158" w="70" text="{BuffStatus}" color="#FFC7B98F" />
|
<label x="447" y="158" w="70" text="{BuffStatus}" color="#FFC7B98F" />
|
||||||
<!-- Round D item 4: the "bottom-fit rule" the original comment names
|
<!-- Round F item 6: sits right under the button row above (154+18=172,
|
||||||
(194-16) now targets the enlarged group's own bottom edge
|
+4px) instead of anchored to the group's bottom edge — the
|
||||||
(229-16), anchor="bottom" so a further manual resize keeps it
|
anchor used to leave a growing empty hole (55px at the enlarged
|
||||||
pinned there. -->
|
984-wide default) between this label and the row it belongs to. -->
|
||||||
<label x="332" y="213" w="184" h="16" text="{Coverage}" color="#FF9B9072" anchor="bottom" />
|
<label x="333" y="176" w="184" h="16" text="{Coverage}" color="#FF9B9072" />
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<!-- Route: VTank's own 2-across button grid, row order, and bottom
|
<!-- Route: VTank's own 2-across button grid, row order, and bottom
|
||||||
|
|
@ -657,7 +686,13 @@
|
||||||
verbatim, +4px left margin for our own convention). The waypoint
|
verbatim, +4px left margin for our own convention). The waypoint
|
||||||
list is VTank's own clWP/clWPc 2-column grid (Campaign VT S7.5;
|
list is VTank's own clWP/clWPc 2-column grid (Campaign VT S7.5;
|
||||||
PluginCore.cs:3576-3599 — any cell click deletes the waypoint).
|
PluginCore.cs:3576-3599 — any cell click deletes the waypoint).
|
||||||
PITCH: text 330+7=337, count (last column) auto.
|
PITCH: text was 330+7=337, count 23+7=30. Round F item 6 narrowed
|
||||||
|
text to 324 (337+30=367 already exceeded this list's own
|
||||||
|
w=370 minus the scrollbar's 16px reservation — 354 — so count
|
||||||
|
was clamped to 17px the moment the list actually needed to
|
||||||
|
scroll) and appended a third, trailing filler column so count is
|
||||||
|
no longer the grid's LAST column and keeps its declared 30px
|
||||||
|
honored instead of silently absorbed/starved.
|
||||||
|
|
||||||
MossTank-only extras that have no VTank Route-tab counterpart moved
|
MossTank-only extras that have no VTank Route-tab counterpart moved
|
||||||
to the Advanced Options popup's own "MossTank Extras" section:
|
to the Advanced Options popup's own "MossTank Extras" section:
|
||||||
|
|
@ -686,8 +721,19 @@
|
||||||
<list x="4" y="16" w="370" h="151" rowheight="17" anchor="left top bottom"
|
<list x="4" y="16" w="370" h="151" rowheight="17" anchor="left top bottom"
|
||||||
selected="{SelectedRouteWaypointIndex}" onchange="{SelectRouteWaypoint}"
|
selected="{SelectedRouteWaypointIndex}" onchange="{SelectRouteWaypoint}"
|
||||||
tooltip="Click a waypoint to remove it.">
|
tooltip="Click a waypoint to remove it.">
|
||||||
<column type="text" width="337" items="{RouteWaypointTextColumn}" onclick="{DeleteRouteWaypointAt}" />
|
<column type="text" width="324" items="{RouteWaypointTextColumn}" onclick="{DeleteRouteWaypointAt}" />
|
||||||
<column type="text" width="30" items="{RouteWaypointCountColumn}" onclick="{DeleteRouteWaypointAt}" />
|
<column type="text" width="30" items="{RouteWaypointCountColumn}" onclick="{DeleteRouteWaypointAt}" />
|
||||||
|
<!-- Round F item 6: a trailing filler so the count column above is
|
||||||
|
no longer the grid's LAST column and keeps its own real 30px
|
||||||
|
width instead of silently absorbing whatever's left. The text
|
||||||
|
column is also narrowed 337->324: 324+30=354 exactly matches
|
||||||
|
this list's own w=370 minus the scrollbar's 16px reservation,
|
||||||
|
so the count column gets its full 30px whether or not the
|
||||||
|
scrollbar is showing — previously 337+30=367 already exceeded
|
||||||
|
354, so the count column was clamped to 17px the moment the
|
||||||
|
list actually needed to scroll, no matter which column was
|
||||||
|
"last". -->
|
||||||
|
<column type="text" width="*" items="{RouteWaypointFillerColumn}" onclick="{RouteWaypointFillerClick}" />
|
||||||
</list>
|
</list>
|
||||||
|
|
||||||
<menu x="4" y="171" w="80" h="16" items="{RouteModeNames}" anchor="bottom"
|
<menu x="4" y="171" w="80" h="16" items="{RouteModeNames}" anchor="bottom"
|
||||||
|
|
|
||||||
|
|
@ -1460,6 +1460,58 @@ public sealed class MarkupListColumnsTests
|
||||||
Assert.Equal(new[] { 0 }, binding.ActionClicks);
|
Assert.Equal(new[] { 0 }, binding.ActionClicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class RouteShapedBinding
|
||||||
|
{
|
||||||
|
public IReadOnlyList<string> Names { get; } =
|
||||||
|
Enumerable.Range(0, 9).Select(static i => $"WP{i}").ToArray();
|
||||||
|
public IReadOnlyList<string> Counts { get; } =
|
||||||
|
Enumerable.Range(0, 9).Select(static i => i.ToString()).ToArray();
|
||||||
|
public IReadOnlyList<string> Filler { get; } = Array.Empty<string>();
|
||||||
|
public int Selected { get; set; } = -1;
|
||||||
|
public Action<int> Click => static _ => { };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Round F item 6 (resemblance re-check: "Route's count column
|
||||||
|
/// squeezes to 17 when the list scrolls"). Mirrors the Route tab's
|
||||||
|
/// EXACT real shape (mosstank.xml: a 370px-wide list, columns
|
||||||
|
/// 324/30/*) with enough rows (9, past the 151/17≈8 visible-row
|
||||||
|
/// ceiling) to force the scrollbar — the count column's declared
|
||||||
|
/// 30px was previously only honored while the scrollbar was hidden;
|
||||||
|
/// with it showing, 337(the OLD text width)+30 already exceeded
|
||||||
|
/// 370-16=354, clamping the count column to 17px. Reflects into
|
||||||
|
/// UiMarkupList's own cached column layout (the same one
|
||||||
|
/// DrawColumns/OnEventColumns use) rather than measuring glyphs —
|
||||||
|
/// this is a pure geometry question, no font involved.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void RouteShapedGrid_CountColumnStaysThirtyPxWhenTheListScrolls()
|
||||||
|
{
|
||||||
|
var binding = new RouteShapedBinding();
|
||||||
|
const string xml =
|
||||||
|
"<panel x=\"0\" y=\"0\" w=\"400\" h=\"200\">" +
|
||||||
|
"<list x=\"0\" y=\"0\" w=\"370\" h=\"151\" rowheight=\"17\" " +
|
||||||
|
"selected=\"{Selected}\">" +
|
||||||
|
" <column type=\"text\" width=\"324\" items=\"{Names}\" onclick=\"{Click}\"/>" +
|
||||||
|
" <column type=\"text\" width=\"30\" items=\"{Counts}\" onclick=\"{Click}\"/>" +
|
||||||
|
" <column type=\"text\" width=\"*\" items=\"{Filler}\"/>" +
|
||||||
|
"</list></panel>";
|
||||||
|
|
||||||
|
var panel = MarkupDocument.Build(xml, binding, Sprite);
|
||||||
|
var list = Assert.IsType<UiMarkupList>(panel.Children[0]);
|
||||||
|
|
||||||
|
var (renderer, ctx) = MakeContext(800f, 400f);
|
||||||
|
list.DrawSelfAndChildren(ctx);
|
||||||
|
|
||||||
|
System.Reflection.FieldInfo layoutField = typeof(UiMarkupList).GetField(
|
||||||
|
"_cachedLayout",
|
||||||
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
|
||||||
|
var layout = ((float x, float w)[])layoutField.GetValue(list)!;
|
||||||
|
|
||||||
|
Assert.Equal(3, layout.Length);
|
||||||
|
Assert.Equal(30f, layout[1].w, 3);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class MonsterShapedBinding
|
private sealed class MonsterShapedBinding
|
||||||
{
|
{
|
||||||
public IReadOnlyList<bool> Checks { get; } = new[] { true, false };
|
public IReadOnlyList<bool> Checks { get; } = new[] { true, false };
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,73 @@ public sealed class MossTankMarkupBuildOverRealFilesTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Round F item 6 (resemblance re-check: "Buffs' 136 px middle gap").
|
||||||
|
/// <see cref="ResolvedMainPanelHasNoOverlapOrOutOfBoundsChildAtThisSize"/>
|
||||||
|
/// already proves the extras strip never overlaps the Blacklisted
|
||||||
|
/// Buff Families list at the floor or the enlarged size (that generic
|
||||||
|
/// sibling-overlap pass is exactly what caught the original x=397
|
||||||
|
/// attempt at true 984-default centering colliding with the list's
|
||||||
|
/// own floor position, x=524). This is the narrower, POSITIVE claim
|
||||||
|
/// that overlap pass alone doesn't make: the strip is actually
|
||||||
|
/// CENTERED at the panel's floor (856x236) — the one width at which
|
||||||
|
/// centering and non-overlap can both hold, since Extra Buff Spells
|
||||||
|
/// never moves but Blacklisted Buff Families tracks the growing
|
||||||
|
/// right edge.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void BuffsExtrasStripStaysCenteredAndNonOverlappingAtTheFloor()
|
||||||
|
{
|
||||||
|
string xml = File.ReadAllText(
|
||||||
|
Path.Combine(MossTankMarkupDirectory, "mosstank.xml"));
|
||||||
|
var panel = new MossTankPanel(new StubHost());
|
||||||
|
|
||||||
|
UiNineSlicePanel built = MarkupDocument.Build(xml, panel, static id => (id, 32, 32));
|
||||||
|
|
||||||
|
UiPanel[] tabGroups = built.Children
|
||||||
|
.Where(static child => child.GetType() == typeof(UiPanel))
|
||||||
|
.Cast<UiPanel>()
|
||||||
|
.ToArray();
|
||||||
|
Assert.Equal(9, tabGroups.Length);
|
||||||
|
foreach (UiPanel group in tabGroups)
|
||||||
|
group.Visible = false;
|
||||||
|
UiPanel buffsGroup = tabGroups[6];
|
||||||
|
buffsGroup.Visible = true;
|
||||||
|
|
||||||
|
var device = new RecordingGpuDevice();
|
||||||
|
var renderer = new TextRenderer(device, new NullGpuFrameSource(), "unused");
|
||||||
|
renderer.Begin(new Vector2(1400f, 900f));
|
||||||
|
var ctx = new UiRenderContext(renderer, new Vector2(1400f, 900f));
|
||||||
|
|
||||||
|
// First draw at the authored default captures anchor baselines;
|
||||||
|
// only then resize down to the panel's own tested floor.
|
||||||
|
built.DrawSelfAndChildren(ctx);
|
||||||
|
built.Width = 856f;
|
||||||
|
built.Height = 236f;
|
||||||
|
built.DrawSelfAndChildren(ctx);
|
||||||
|
|
||||||
|
UiMarkupList[] lists = buffsGroup.Children.OfType<UiMarkupList>().ToArray();
|
||||||
|
Assert.Equal(2, lists.Length);
|
||||||
|
UiMarkupList leftList = lists.Single(static l => l.Left < 300f);
|
||||||
|
UiMarkupList rightList = lists.Single(static l => l.Left >= 300f);
|
||||||
|
|
||||||
|
UiMarkupToggle trainedToggle = Assert.Single(
|
||||||
|
buffsGroup.Children.OfType<UiMarkupToggle>(), static t => t.Text == "Trained skills only");
|
||||||
|
UiSimpleButton rebuffUpButton = Assert.Single(
|
||||||
|
buffsGroup.Children.OfType<UiSimpleButton>(), static b => b.Text == "+" && b.Top > 120f);
|
||||||
|
|
||||||
|
float leftMargin = trainedToggle.Left - (leftList.Left + leftList.Width);
|
||||||
|
float rightMargin = rightList.Left - (rebuffUpButton.Left + rebuffUpButton.Width);
|
||||||
|
Assert.True(
|
||||||
|
rightMargin >= 0f,
|
||||||
|
$"Extras strip (right edge {rebuffUpButton.Left + rebuffUpButton.Width}) "
|
||||||
|
+ $"overlaps the right list (left edge {rightList.Left}) at the floor.");
|
||||||
|
Assert.True(
|
||||||
|
MathF.Abs(leftMargin - rightMargin) <= 4f,
|
||||||
|
$"Extras strip not centered at the floor: left margin "
|
||||||
|
+ $"{leftMargin}px, right margin {rightMargin}px.");
|
||||||
|
}
|
||||||
|
|
||||||
private static void AssertResolvedWithinParent(UiElement parent)
|
private static void AssertResolvedWithinParent(UiElement parent)
|
||||||
{
|
{
|
||||||
foreach (UiElement child in parent.Children)
|
foreach (UiElement child in parent.Children)
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,102 @@ public sealed class MossTankMarkupContractTests
|
||||||
Assert.DoesNotContain("{ProfileNotice}", labelBindings);
|
Assert.DoesNotContain("{ProfileNotice}", labelBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Round F item 6 (resemblance re-check nits: "Buffs' 136 px middle
|
||||||
|
/// gap and the orphaned Coverage label"). Coverage sits right under
|
||||||
|
/// the Buff button row instead of anchored to the group's bottom
|
||||||
|
/// edge, which used to leave a 55px empty hole above it as the panel
|
||||||
|
/// grew.
|
||||||
|
///
|
||||||
|
/// The middle-gap half has a hard geometric ceiling documented at
|
||||||
|
/// mosstank.xml's own Buffs comment: Extra Buff Spells never moves
|
||||||
|
/// (x=4..324) but Blacklisted Buff Families tracks the growing right
|
||||||
|
/// edge (anchor="right"), sitting at x=524 at the panel's own tested
|
||||||
|
/// FLOOR (856x236) and x=652 (its authored declaration below) at the
|
||||||
|
/// 984-wide enlarged default — a 128px swing this campaign's own
|
||||||
|
/// Round D item 4 introduced. A single FIXED-position strip can only
|
||||||
|
/// be centered relative to ONE of those two widths; centering
|
||||||
|
/// against the 984 default would put the strip's right edge INSIDE
|
||||||
|
/// where the list sits at the floor — a real overlap
|
||||||
|
/// ResolvedMainPanelHasNoOverlapOrOutOfBoundsChildAtThisSize's own
|
||||||
|
/// 856x236 case would catch. This pins centering against the FLOOR
|
||||||
|
/// value instead (declared x minus the 128px Round D growth delta —
|
||||||
|
/// 984-856) — the widest safe position a fixed strip can take,
|
||||||
|
/// verified against the real-file resolved tree at both sizes by
|
||||||
|
/// <c>MossTankMarkupBuildOverRealFilesTests.BuffsExtrasStripStaysCenteredAndNonOverlappingAtTheFloor</c>
|
||||||
|
/// in the App test project.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void BuffsExtrasStripIsCenteredAtThePanelsFloorAndCoverageSitsUnderTheButtonRow()
|
||||||
|
{
|
||||||
|
XDocument document = XDocument.Load(
|
||||||
|
Path.Combine(AppContext.BaseDirectory, "mosstank.xml"));
|
||||||
|
XElement root = Assert.IsType<XElement>(document.Root);
|
||||||
|
XElement buffsGroup = root.Elements("group")
|
||||||
|
.Single(static g => (string?)g.Attribute("visible") == "{BuffsVisible}");
|
||||||
|
|
||||||
|
XElement[] lists = buffsGroup.Elements("list").ToArray();
|
||||||
|
Assert.Equal(2, lists.Length);
|
||||||
|
float leftListRightEdge = Number(lists[0], "x") + Number(lists[0], "w");
|
||||||
|
// Round D item 4's own +128 growth: the declared x below is the
|
||||||
|
// 984-wide authored default; subtracting it back out gives the
|
||||||
|
// resolved position at the panel's tested floor (856x236).
|
||||||
|
const float roundDGrowthDelta = 984f - 856f;
|
||||||
|
float rightListLeftEdgeAtFloor = Number(lists[1], "x") - roundDGrowthDelta;
|
||||||
|
|
||||||
|
XElement trainedToggle = buffsGroup.Elements("toggle")
|
||||||
|
.Single(static t => (string?)t.Attribute("onclick") == "{ToggleTrainedOnly}");
|
||||||
|
XElement rebuffUpButton = buffsGroup.Elements("button")
|
||||||
|
.Single(static b => (string?)b.Attribute("onclick") == "{RebuffUp}");
|
||||||
|
float extrasLeft = Number(trainedToggle, "x");
|
||||||
|
float extrasRight = Number(rebuffUpButton, "x") + Number(rebuffUpButton, "w");
|
||||||
|
|
||||||
|
float leftMargin = extrasLeft - leftListRightEdge;
|
||||||
|
float rightMarginAtFloor = rightListLeftEdgeAtFloor - extrasRight;
|
||||||
|
Assert.True(
|
||||||
|
rightMarginAtFloor >= 0f,
|
||||||
|
$"Extras strip (right edge {extrasRight}) overlaps the "
|
||||||
|
+ $"Blacklisted Buff Families list at the panel's floor "
|
||||||
|
+ $"(left edge {rightListLeftEdgeAtFloor}).");
|
||||||
|
Assert.True(
|
||||||
|
MathF.Abs(leftMargin - rightMarginAtFloor) <= 4f,
|
||||||
|
$"Extras strip not centered at the panel's floor: left margin "
|
||||||
|
+ $"{leftMargin}px, right margin {rightMarginAtFloor}px.");
|
||||||
|
|
||||||
|
XElement buffButton = buffsGroup.Elements("button")
|
||||||
|
.Single(static b => (string?)b.Attribute("onclick") == "{Buff}");
|
||||||
|
XElement coverage = buffsGroup.Elements("label")
|
||||||
|
.Single(static l => (string?)l.Attribute("text") == "{Coverage}");
|
||||||
|
float rowBottom = Number(buffButton, "y") + Number(buffButton, "h");
|
||||||
|
float coverageGap = Number(coverage, "y") - rowBottom;
|
||||||
|
Assert.True(
|
||||||
|
coverageGap is >= 0f and <= 12f,
|
||||||
|
$"Coverage sits {coverageGap}px below the Buff button row — "
|
||||||
|
+ "expected <=12px, not the old 55px hole from anchoring it to "
|
||||||
|
+ "the group's bottom edge instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Round F item 6 (resemblance re-check: "Loot Editor: drop the
|
||||||
|
/// leftover 'Back' button — the window closes from its title bar
|
||||||
|
/// like the others"). The standalone button is gone;
|
||||||
|
/// CloseLootEditor stays a real, bound property (still called by
|
||||||
|
/// ToggleLootEditorVisible, the Profiles-tab checkbox's own close
|
||||||
|
/// path).
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void LootEditorHasNoLeftoverBackButton()
|
||||||
|
{
|
||||||
|
XDocument document = XDocument.Load(
|
||||||
|
Path.Combine(AppContext.BaseDirectory, "mosstank-loot-editor.xml"));
|
||||||
|
XElement root = Assert.IsType<XElement>(document.Root);
|
||||||
|
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
root.Elements("button"),
|
||||||
|
static b => (string?)b.Attribute("text") == "Back");
|
||||||
|
Assert.NotNull(typeof(MossTankPanel).GetProperty("CloseLootEditor"));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AuthoredShellFitsTheMinimumCanvasAndEverySizedChildFitsItsParent()
|
public void AuthoredShellFitsTheMinimumCanvasAndEverySizedChildFitsItsParent()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue