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);
}
}
}