namespace AcDream.App.UI.Layout; /// Retail geometry policy for the shared melee/missile/magic combat root. internal static class RetailCombatLayout { internal const int VisibleFavoriteSlots = 18; internal const float FavoriteCellWidth = 32f; /// /// Resize the imported combat tree so its favorite list exposes exactly the /// requested number of cells. The DAT deliberately authors the outer combat /// root at 610 pixels while the magic page's descendants use an 800-pixel /// design parent. Retail UIElement::UpdateForParentSizeChange @ /// 0x00462640 carries that width delta through every raw-edge policy. /// Applying the same chain and solving for the list viewport keeps the /// endowment, arrows, list, Cast button, and outer frame in one coherent /// retained layout instead of resizing individual controls ad hoc. /// internal static float FitFavoriteSlots( ImportedLayout layout, int visibleSlots = VisibleFavoriteSlots, float cellWidth = FavoriteCellWidth) { ArgumentNullException.ThrowIfNull(layout); if (visibleSlots < 1) throw new ArgumentOutOfRangeException(nameof(visibleSlots)); if (!(cellWidth > 0f) || !float.IsFinite(cellWidth)) throw new ArgumentOutOfRangeException(nameof(cellWidth)); UiElement root = layout.Root; ApplyDescendantLayout(root); if (layout.FindElement(SpellcastingUiController.FavoriteListId) is not UiItemList list) throw new InvalidOperationException("Retail combat layout has no favorite spell list."); float targetViewport = visibleSlots * cellWidth; root.Width = MathF.Max(0f, root.Width + targetViewport - list.Width); ApplyDescendantLayout(root); return root.Width; } private static void ApplyDescendantLayout(UiElement parent) { foreach (UiElement child in parent.Children) { child.ApplyAnchor(parent.Width, parent.Height); ApplyDescendantLayout(child); } } }