diff --git a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs index 287971fe..2fb75977 100644 --- a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs +++ b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs @@ -77,8 +77,11 @@ public static class DatWidgetFactory e.Width = info.Width; e.Height = info.Height; - // Honor the dat's draw order so overlapping pieces (grip overlay over bevel chrome) layer correctly. - e.ZOrder = (int)info.ReadOrder; + // Honor the dat's draw order. ZLevel is the primary layer (higher = further BACK — e.g. the + // gmInventoryUI full-window backdrop at ZLevel 100 sits behind the ZLevel-0 panels, #145); + // ReadOrder is the within-layer tiebreaker (higher = on top). K=10000 exceeds any window's + // element count so ZLevel always dominates. Vitals (all ZLevel 0) keep ZOrder == ReadOrder. + e.ZOrder = (int)info.ReadOrder - (int)info.ZLevel * 10000; // Map the four raw edge-anchor values to the AnchorEdges bit-flag that the // UI layout engine uses for reflow. diff --git a/src/AcDream.App/UI/Layout/ElementReader.cs b/src/AcDream.App/UI/Layout/ElementReader.cs index 93a4eb30..bbafcd05 100644 --- a/src/AcDream.App/UI/Layout/ElementReader.cs +++ b/src/AcDream.App/UI/Layout/ElementReader.cs @@ -39,6 +39,12 @@ public sealed class ElementInfo /// Draw order within the parent (lower = drawn first / behind). public uint ReadOrder; + /// Layer level from the dat (ElementDesc.ZLevel). Higher = drawn further + /// BACK (a full-window backdrop at ZLevel 100 sits behind ZLevel-0 panels). The factory + /// folds it into so ZLevel dominates and ReadOrder is the + /// within-layer tiebreaker. Issue #145 (vitals are all ZLevel 0, so they're unaffected). + public uint ZLevel; + /// /// Font dat object id inherited from the base element's Properties[0x1A] /// (ArrayBaseProperty → DataIdBaseProperty). 0 = none / not inherited. @@ -152,6 +158,7 @@ public static class ElementReader Right = derived.Right, Bottom = derived.Bottom, ReadOrder = derived.ReadOrder, + ZLevel = derived.ZLevel != 0 ? derived.ZLevel : base_.ZLevel, FontDid = derived.FontDid != 0 ? derived.FontDid : base_.FontDid, // DefaultStateName: derived wins if set; otherwise inherit the base's default. DefaultStateName = !string.IsNullOrEmpty(derived.DefaultStateName) ? derived.DefaultStateName : base_.DefaultStateName, diff --git a/src/AcDream.App/UI/Layout/LayoutImporter.cs b/src/AcDream.App/UI/Layout/LayoutImporter.cs index aa6264c9..26c63465 100644 --- a/src/AcDream.App/UI/Layout/LayoutImporter.cs +++ b/src/AcDream.App/UI/Layout/LayoutImporter.cs @@ -270,6 +270,7 @@ public static class LayoutImporter Right = d.RightEdge, Bottom = d.BottomEdge, ReadOrder = d.ReadOrder, + ZLevel = d.ZLevel, DefaultStateName = (defState is "Undef" or "Undefined" or "0") ? "" : defState, }; diff --git a/tests/AcDream.App.Tests/UI/DatWidgetFactoryZOrderTests.cs b/tests/AcDream.App.Tests/UI/DatWidgetFactoryZOrderTests.cs new file mode 100644 index 00000000..52ba5332 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/DatWidgetFactoryZOrderTests.cs @@ -0,0 +1,43 @@ +using AcDream.App.UI.Layout; + +namespace AcDream.App.Tests.UI; + +public class DatWidgetFactoryZOrderTests +{ + private static (uint, int, int) NoChrome(uint id) => (0u, 0, 0); + + [Fact] + public void ZOrder_HigherZLevel_DrawsBehind() + { + // The gmInventoryUI backdrop (ZLevel 100, ReadOrder 4) must draw BEHIND the panels + // (ZLevel 0, ReadOrder 1): lower ZOrder = drawn first = behind. Before the fix, ZOrder + // came from ReadOrder only, so the backdrop (4) painted over the panels (1). + var backdrop = new ElementInfo { Type = 3, ReadOrder = 4, ZLevel = 100 }; + var panel = new ElementInfo { Type = 3, ReadOrder = 1, ZLevel = 0 }; + + var b = DatWidgetFactory.Create(backdrop, NoChrome, null); + var p = DatWidgetFactory.Create(panel, NoChrome, null); + + Assert.True(b!.ZOrder < p!.ZOrder, $"backdrop ZOrder {b.ZOrder} should be < panel ZOrder {p.ZOrder}"); + } + + [Fact] + public void ZOrder_AllZeroZLevel_EqualsReadOrder() + { + // Regression guard: a vitals-style window (every element ZLevel 0) keeps ZOrder == ReadOrder. + var e = new ElementInfo { Type = 3, ReadOrder = 5, ZLevel = 0 }; + var w = DatWidgetFactory.Create(e, NoChrome, null); + Assert.Equal(5, w!.ZOrder); + } + + [Fact] + public void ZOrder_SameZLevel_OrderedByReadOrder() + { + // Within one ZLevel, ReadOrder is the tiebreaker (higher ReadOrder draws on top). + var lo = new ElementInfo { Type = 3, ReadOrder = 2, ZLevel = 2 }; + var hi = new ElementInfo { Type = 3, ReadOrder = 7, ZLevel = 2 }; + var l = DatWidgetFactory.Create(lo, NoChrome, null); + var h = DatWidgetFactory.Create(hi, NoChrome, null); + Assert.True(l!.ZOrder < h!.ZOrder); + } +}