From f684f874dfe7dc8078bcd1694750bf9a9edc659c Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 10 Jul 2026 18:20:11 +0200 Subject: [PATCH] fix(ui): retain character sublayout reflow --- .../UI/Layout/CharacterStatController.cs | 29 ++++++++---- .../UI/Layout/CharacterStatControllerTests.cs | 44 +++++++++++++++++++ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/AcDream.App/UI/Layout/CharacterStatController.cs b/src/AcDream.App/UI/Layout/CharacterStatController.cs index 6ceea283..9dfc7211 100644 --- a/src/AcDream.App/UI/Layout/CharacterStatController.cs +++ b/src/AcDream.App/UI/Layout/CharacterStatController.cs @@ -447,16 +447,20 @@ public static class CharacterStatController ? FindInSubtree(contentPage, static el => HasDatElementId(el, ListBoxId)) : null) ?? layout.FindElement(ListBoxId); - if (statList is not null) + // Imported character nodes already carry the exact raw-edge policy from + // the 337px base sublayout into the 575px mounted page. Only synthetic/ + // legacy widgets need compatibility anchors; assigning Anchors on an + // imported node deliberately clears its UiLayoutPolicy. + if (statList is not null && statList.LayoutPolicy is null) statList.Anchors = AnchorEdges.Left | AnchorEdges.Top | AnchorEdges.Bottom; if (layout.Root is { } stretchRoot) { - SetAnchorsAllById(stretchRoot, ListScrollbarId, AnchorEdges.Left | AnchorEdges.Top | AnchorEdges.Bottom); - SetAnchorsAllById(stretchRoot, ListDividerId, AnchorEdges.Left | AnchorEdges.Bottom); - SetAnchorsAllById(stretchRoot, FooterStateAId, AnchorEdges.Left | AnchorEdges.Bottom); - SetAnchorsAllById(stretchRoot, FooterStateBId, AnchorEdges.Left | AnchorEdges.Bottom); - SetAnchorsAllById(stretchRoot, FooterStateCId, AnchorEdges.Left | AnchorEdges.Bottom); + SetCompatibilityAnchorsAllById(stretchRoot, ListScrollbarId, AnchorEdges.Left | AnchorEdges.Top | AnchorEdges.Bottom); + SetCompatibilityAnchorsAllById(stretchRoot, ListDividerId, AnchorEdges.Left | AnchorEdges.Bottom); + SetCompatibilityAnchorsAllById(stretchRoot, FooterStateAId, AnchorEdges.Left | AnchorEdges.Bottom); + SetCompatibilityAnchorsAllById(stretchRoot, FooterStateBId, AnchorEdges.Left | AnchorEdges.Bottom); + SetCompatibilityAnchorsAllById(stretchRoot, FooterStateCId, AnchorEdges.Left | AnchorEdges.Bottom); } UiScrollbar? skillScrollbar = PrepareSkillScrollbar(layout, contentPage, statList, spriteResolve); @@ -1637,12 +1641,19 @@ public static class CharacterStatController // ── Helpers ────────────────────────────────────────────────────────────── - private static void SetAnchorsAllById(UiElement node, uint targetId, AnchorEdges anchors) + private static void SetCompatibilityAnchorsAllById( + UiElement node, + uint targetId, + AnchorEdges anchors) { - if (node is UiDatElement d && d.ElementId == targetId) + if (node is UiDatElement d + && d.ElementId == targetId + && node.LayoutPolicy is null) + { node.Anchors = anchors; + } foreach (var child in node.Children) - SetAnchorsAllById(child, targetId, anchors); + SetCompatibilityAnchorsAllById(child, targetId, anchors); } /// Depth-first search of and its descendants. diff --git a/tests/AcDream.App.Tests/UI/Layout/CharacterStatControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/CharacterStatControllerTests.cs index c6de80bd..00a9c6ee 100644 --- a/tests/AcDream.App.Tests/UI/Layout/CharacterStatControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/CharacterStatControllerTests.cs @@ -1471,6 +1471,41 @@ public class CharacterStatControllerTests Assert.NotNull(meter.Fill()); } + [Fact] + public void ProductionFixture_MountedPagesUseRetailListFooterAndScrollbarReflow() + { + var layout = FixtureLoader.LoadCharacter(); + CharacterStatController.Bind( + layout, + SampleData.SampleCharacter, + spriteResolve: id => (id, 16, 16)); + + ApplyLayoutPass(layout.Root); + + var page = layout.Root.Children.Single( + e => e.DatElementId == CharacterStatController.AttributesPageId); + var list = Descendants(page).Single( + e => e.DatElementId == CharacterStatController.ListBoxId); + var statLayout = list.Parent!; + var scrollbar = statLayout.Children.OfType().Single( + e => e.DatElementId == CharacterStatController.ListScrollbarId); + var divider = statLayout.Children.Single( + e => e.DatElementId == CharacterStatController.ListDividerId); + var footers = statLayout.Children.Where( + e => e.DatElementId is CharacterStatController.FooterStateAId + or CharacterStatController.FooterStateBId + or CharacterStatController.FooterStateCId).ToList(); + + Assert.Equal((112f, 398f), (list.Top, list.Height)); + Assert.Equal((112f, 398f), (scrollbar.Top, scrollbar.Height)); + Assert.Equal((510f, 7f), (divider.Top, divider.Height)); + Assert.Equal(3, footers.Count); + Assert.All(footers, footer => Assert.Equal((520f, 55f), (footer.Top, footer.Height))); + + ClickTab(layout, left: 92f); + Assert.True(scrollbar.Visible); + } + // ── Helpers ────────────────────────────────────────────────────────────── private static void ClickTab(ImportedLayout layout, float left) @@ -1482,6 +1517,15 @@ public class CharacterStatControllerTests tab.OnClick!(); } + private static void ApplyLayoutPass(UiElement parent) + { + foreach (var child in parent.Children) + { + child.ApplyAnchor(parent.Width, parent.Height); + ApplyLayoutPass(child); + } + } + private static string FirstRowName(UiElement list) { var row = Descendants(list).OfType().FirstOrDefault();