feat(ui): port retail spellbook interactions
Resolve the authored spell shortcut row prototype so the spellbook presents the retail icon, name, separator, selected overlay, and scrollbar geometry. Port exact school and level filters, stable display ordering, selection exposure, and learned-spell drags into the open favorite bar. Route deletion through the shared retail confirmation dialog and send CM_Magic::Event_RemoveSpell only after an affirmative answer, leaving the inbound server notice authoritative for list state. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
ad30c37a48
commit
ac2ca8f965
17 changed files with 727 additions and 47 deletions
|
|
@ -1,12 +1,13 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
|
||||
namespace AcDream.App.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Icon-list cell for non-weenie catalog entries such as spells and components.
|
||||
/// It reuses the retail UIItemList geometry without pretending a spell id is an
|
||||
/// object GUID and without participating in item drag/drop.
|
||||
/// object GUID; catalog drags stay distinct from physical item drag/drop.
|
||||
/// </summary>
|
||||
public sealed class UiCatalogSlot : UiItemSlot
|
||||
{
|
||||
|
|
@ -17,6 +18,16 @@ public sealed class UiCatalogSlot : UiItemSlot
|
|||
public string Label { get; set; } = string.Empty;
|
||||
public string? Detail { get; set; }
|
||||
public bool ShowLabel { get; init; }
|
||||
public uint BackgroundSprite { get; init; }
|
||||
public UiDatFont? LabelFont { get; init; }
|
||||
public Vector4 LabelColor { get; init; } = new(0.92f, 0.88f, 0.70f, 1f);
|
||||
public float IconLeft { get; init; }
|
||||
public float IconTop { get; init; }
|
||||
public float IconWidth { get; init; }
|
||||
public float IconHeight { get; init; }
|
||||
public float LabelLeft { get; init; }
|
||||
public float LabelWidth { get; init; }
|
||||
public bool SelectionBehindContent { get; init; }
|
||||
|
||||
public new Action? Clicked { get; set; }
|
||||
public new Action? DoubleClicked { get; set; }
|
||||
|
|
@ -67,31 +78,78 @@ public sealed class UiCatalogSlot : UiItemSlot
|
|||
|
||||
protected override void OnDraw(UiRenderContext ctx)
|
||||
{
|
||||
float iconSize = ShowLabel ? MathF.Min(32f, Height) : Width;
|
||||
if (BackgroundSprite != 0u && SpriteResolve is not null)
|
||||
{
|
||||
var (texture, _, _) = SpriteResolve(BackgroundSprite);
|
||||
if (texture != 0u)
|
||||
ctx.DrawSprite(texture, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
|
||||
}
|
||||
|
||||
if (SelectionBehindContent)
|
||||
DrawSelection(ctx);
|
||||
|
||||
float iconWidth = IconWidth > 0f ? IconWidth : ShowLabel ? MathF.Min(32f, Height) : Width;
|
||||
float iconHeight = IconHeight > 0f ? IconHeight : ShowLabel ? MathF.Min(32f, Height) : Height;
|
||||
if (CatalogIconTexture != 0)
|
||||
ctx.DrawSprite(CatalogIconTexture, 0f, 0f, iconSize, iconSize, 0f, 0f, 1f, 1f, Vector4.One);
|
||||
ctx.DrawSprite(CatalogIconTexture, IconLeft, IconTop, iconWidth, iconHeight, 0f, 0f, 1f, 1f, Vector4.One);
|
||||
else if (SpriteResolve is not null && EmptySprite != 0)
|
||||
{
|
||||
var (texture, _, _) = SpriteResolve(EmptySprite);
|
||||
if (texture != 0)
|
||||
ctx.DrawSprite(texture, 0f, 0f, iconSize, iconSize, 0f, 0f, 1f, 1f, Vector4.One);
|
||||
ctx.DrawSprite(texture, IconLeft, IconTop, iconWidth, iconHeight, 0f, 0f, 1f, 1f, Vector4.One);
|
||||
}
|
||||
|
||||
if (CatalogOverlayTexture != 0)
|
||||
ctx.DrawSprite(CatalogOverlayTexture, 0f, 0f, iconSize, iconSize, 0f, 0f, 1f, 1f, Vector4.One);
|
||||
ctx.DrawSprite(CatalogOverlayTexture, IconLeft, IconTop, iconWidth, iconHeight, 0f, 0f, 1f, 1f, Vector4.One);
|
||||
|
||||
if (Selected && SpriteResolve is not null && SelectedSprite != 0)
|
||||
{
|
||||
var (texture, _, _) = SpriteResolve(SelectedSprite);
|
||||
if (texture != 0)
|
||||
ctx.DrawSprite(texture, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
|
||||
}
|
||||
if (!SelectionBehindContent)
|
||||
DrawSelection(ctx);
|
||||
|
||||
if (ShowLabel)
|
||||
{
|
||||
ctx.DrawString(Label, iconSize + 4f, 3f, new Vector4(0.92f, 0.88f, 0.70f, 1f));
|
||||
float labelLeft = LabelLeft > 0f ? LabelLeft : IconLeft + iconWidth + 4f;
|
||||
float labelWidth = LabelWidth > 0f ? LabelWidth : MathF.Max(0f, Width - labelLeft);
|
||||
if (LabelFont is not null)
|
||||
{
|
||||
string text = FitText(Label, labelWidth, LabelFont.MeasureWidth);
|
||||
float y = MathF.Max(0f, (Height - LabelFont.LineHeight) * 0.5f);
|
||||
ctx.DrawStringDat(LabelFont, text, labelLeft, y, LabelColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
BitmapFont? font = ctx.DefaultFont;
|
||||
string text = font is null ? Label : FitText(Label, labelWidth, font.MeasureWidth);
|
||||
float y = font is null ? 3f : MathF.Max(0f, (Height - font.LineHeight) * 0.5f);
|
||||
ctx.DrawString(text, labelLeft, y, LabelColor, font);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(Detail))
|
||||
ctx.DrawString(Detail!, MathF.Max(iconSize + 4f, Width - 48f), 3f, Vector4.One);
|
||||
ctx.DrawString(Detail!, MathF.Max(labelLeft, Width - 48f), 3f, Vector4.One);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSelection(UiRenderContext ctx)
|
||||
{
|
||||
if (!Selected || SpriteResolve is null || SelectedSprite == 0u) return;
|
||||
var (texture, _, _) = SpriteResolve(SelectedSprite);
|
||||
if (texture != 0u)
|
||||
ctx.DrawSprite(texture, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
|
||||
}
|
||||
|
||||
private static string FitText(string text, float width, Func<string, float> measure)
|
||||
{
|
||||
if (width <= 0f || string.IsNullOrEmpty(text) || measure(text) <= width)
|
||||
return text;
|
||||
|
||||
const string ellipsis = "...";
|
||||
float ellipsisWidth = measure(ellipsis);
|
||||
if (ellipsisWidth >= width) return string.Empty;
|
||||
int low = 0, high = text.Length;
|
||||
while (low < high)
|
||||
{
|
||||
int mid = (low + high + 1) / 2;
|
||||
if (measure(text[..mid]) + ellipsisWidth <= width) low = mid;
|
||||
else high = mid - 1;
|
||||
}
|
||||
return text[..low] + ellipsis;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue