fix(ui): restore retail magic shortcut bar

Port the retail horizontal ItemList empty-slot padding and share UIItem shortcut-number graphics with the status toolbar. Preserve all authored face children on compound DAT buttons so the three-piece Cast control reflows and renders as one complete button.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-15 19:11:07 +02:00
parent 0527325b25
commit e3605672bb
12 changed files with 466 additions and 62 deletions

View file

@ -45,6 +45,7 @@ public sealed class SpellcastingUiController : IRetainedPanelController
private readonly Action<uint> _useItem;
private readonly Action<int, int, uint>? _addFavorite;
private readonly Action<int, uint>? _removeFavorite;
private readonly UiShortcutSlotGraphics? _shortcutGraphics;
private readonly UiElement[] _tabs;
private readonly UiElement[] _groups;
private readonly UiItemList?[] _lists;
@ -77,7 +78,8 @@ public sealed class SpellcastingUiController : IRetainedPanelController
UiElement[] groups,
UiItemList?[] lists,
UiButton cast,
UiElement endowmentHost)
UiElement endowmentHost,
UiShortcutSlotGraphics? shortcutGraphics)
{
_spellbook = spellbook;
_casting = casting;
@ -89,6 +91,7 @@ public sealed class SpellcastingUiController : IRetainedPanelController
_useItem = useItem;
_addFavorite = addFavorite;
_removeFavorite = removeFavorite;
_shortcutGraphics = shortcutGraphics;
_tabs = tabs;
_groups = groups;
_lists = lists;
@ -139,7 +142,8 @@ public sealed class SpellcastingUiController : IRetainedPanelController
Action<uint> useItem,
SelectionState selection,
Action<int, int, uint>? addFavorite,
Action<int, uint>? removeFavorite)
Action<int, uint>? removeFavorite,
UiShortcutSlotGraphics? shortcutGraphics = null)
{
if (layout.FindElement(CastButtonId) is not UiButton cast
|| layout.FindElement(EndowmentId) is not { } endowmentHost)
@ -162,7 +166,7 @@ public sealed class SpellcastingUiController : IRetainedPanelController
layout, spellbook, casting, objects, playerGuid, resolveSpellIcon,
resolveItemDragIcon, useItem, selection,
addFavorite, removeFavorite,
tabs, groups, lists, cast, endowmentHost);
tabs, groups, lists, cast, endowmentHost, shortcutGraphics);
}
public void AddFavorite(uint spellId)
@ -278,14 +282,17 @@ public sealed class SpellcastingUiController : IRetainedPanelController
list.CatalogDropped = (payload, x, _) =>
{
if (payload is SpellbookShortcutDragPayload shortcut)
DropSpellbookShortcut(shortcut, targetTab, DropPosition(list, x));
DropSpellbookShortcut(
shortcut, targetTab, DropPosition(list, x, favorites.Count));
};
using (list.DeferLayout())
{
list.Flush();
list.Columns = 9;
list.SingleRow = true;
list.CellWidth = 32f;
list.CellHeight = 32f;
list.EmptySlotFactory = () => CreateEmptyFavoriteSlot(list, targetTab);
list.FillVisibleEmptySlots = true;
foreach (uint spellId in favorites)
{
uint id = spellId;
@ -313,10 +320,59 @@ public sealed class SpellcastingUiController : IRetainedPanelController
list.AddItem(slot);
}
}
StampShortcutOverlays(list);
}
SelectTab(_activeTab);
}
private UiCatalogSlot CreateEmptyFavoriteSlot(UiItemList list, int targetTab)
{
int shortcutIndex = list.GetNumUIItems();
UiCatalogSlot? slot = null;
slot = new UiCatalogSlot
{
SpriteResolve = list.SpriteResolve,
EmptySprite = _shortcutGraphics is { EmptySprite: > 0u } graphics
? graphics.EmptySprite
: 0x060074CFu,
Dropped = payload =>
{
int position = Math.Max(0, list.IndexOf(slot!));
int favoriteCount = _spellbook.GetFavorites(targetTab).Count;
position = Math.Min(position, favoriteCount);
if (payload is SpellFavoriteDragPayload favorite)
DropFavorite(favorite, targetTab, position);
else if (payload is SpellbookShortcutDragPayload shortcut)
DropSpellbookShortcut(shortcut, targetTab, position);
},
};
ConfigureShortcutOverlay(slot, shortcutIndex);
return slot;
}
private void StampShortcutOverlays(UiItemList list)
{
for (int i = 0; i < list.GetNumUIItems(); i++)
{
UiItemSlot? slot = list.GetItem(i);
if (slot is null) continue;
ConfigureShortcutOverlay(slot, i);
}
}
private void ConfigureShortcutOverlay(UiItemSlot slot, int index)
{
slot.RegularDigits = _shortcutGraphics?.RegularDigits;
slot.GhostedDigits = _shortcutGraphics?.GhostedDigits;
slot.EmptyDigits = _shortcutGraphics?.EmptyDigits;
if (_shortcutGraphics is { EmptySprite: > 0u } graphics)
slot.EmptySprite = graphics.EmptySprite;
if (index < 9)
slot.SetShortcutNum(index, ghosted: false);
else
slot.ClearShortcutNum();
}
private void BeginFavoriteDrag(SpellFavoriteDragPayload payload)
=> _removeFavorite?.Invoke(payload.SourceTab, payload.SpellId);
@ -343,12 +399,12 @@ public sealed class SpellcastingUiController : IRetainedPanelController
_selected[targetTab] = payload.SpellId;
}
private static int DropPosition(UiItemList list, int localX)
private static int DropPosition(UiItemList list, int localX, int favoriteCount)
{
int position = list.CellWidth <= 0f
? list.GetNumUIItems()
? favoriteCount
: (int)MathF.Floor(MathF.Max(0f, localX) / list.CellWidth);
return Math.Clamp(position, 0, list.GetNumUIItems());
return Math.Clamp(position, 0, favoriteCount);
}
private void OnSpellbookChanged() => _favoritesDirty = true;
@ -381,9 +437,9 @@ public sealed class SpellcastingUiController : IRetainedPanelController
for (int i = 0; i < list.GetNumUIItems(); i++)
if (list.GetItem(i) is UiCatalogSlot slot)
slot.Selected = slot.EntryId == _selected[tab];
}
_endowmentSlot.Selected = _endowmentItemId != 0u
&& _endowmentSelected[_activeTab];
}
}
private void OnSelectionChanged(SelectionTransition _) => UpdateCastAvailability();