using System;
using System.Numerics;
namespace AcDream.App.UI;
///
/// Rectangular container with an optional translucent background and
/// border. Used as the base of every retail panel (attributes, chat,
/// inventory, login, etc.).
///
/// Retail has panel background art stored as 9-slice sprite assets in
/// the 0x06xxxxxx RenderSurface range, and composed via
/// LayoutDesc (0x21xxxxxx) trees. Until our
/// AcFont/UiSpriteBatch consumes those directly, we draw a
/// simple translucent rectangle so panels are visible during development.
///
public class UiPanel : UiElement
{
/// Background fill color. Set to skip.
public Vector4 BackgroundColor { get; set; } = new(0f, 0f, 0f, 0.55f);
/// Border color. Set to skip.
public Vector4 BorderColor { get; set; } = new(0.15f, 0.15f, 0.2f, 0.8f);
public float BorderThickness { get; set; } = 1f;
/// Optional dat RenderSurface id for the panel background sprite, drawn
/// in place of (or alongside) . 0 = none.
/// When set, the sprite is stretched to fill the panel rect.
/// Used by the attribute-list selected-row highlight (sprite 0x06001397 = Button state 6).
public uint BackgroundSprite { get; set; }
/// Resolves a dat RenderSurface id to (GL tex handle, pixel width, pixel height).
/// Required when is non-zero.
public Func? SpriteResolve { get; set; }
protected override void OnDraw(UiRenderContext ctx)
{
if (BackgroundSprite != 0 && SpriteResolve is { } sr)
{
var (tex, tw, th) = sr(BackgroundSprite);
if (tex != 0 && tw != 0 && th != 0)
ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Vector4.One);
}
else if (BackgroundColor.W > 0f)
{
ctx.DrawRect(0, 0, Width, Height, BackgroundColor);
}
if (BorderColor.W > 0f && BorderThickness > 0f)
ctx.DrawRectOutline(0, 0, Width, Height, BorderColor, BorderThickness);
}
}
///
/// Static text label. Draws a single line of text using the context's
/// default font (or an override). Does not consume input.
///
/// Equivalent retail primitive: wide-string appended to a CString via
/// FUN_0040b8f0 then drawn by the widget's draw method through
/// FUN_00698330.
///
public class UiLabel : UiElement
{
public string Text { get; set; } = string.Empty;
public Vector4 TextColor { get; set; } = new(1f, 1f, 1f, 1f);
public UiLabel() { ClickThrough = true; }
protected override void OnDraw(UiRenderContext ctx)
=> ctx.DrawString(Text, 0, 0, TextColor);
}
///
/// Simple clickable button: panel background + centered label + click
/// callback. Retail equivalent is Keystone's button widget, driven by
/// a StateDesc per UIStateId (normal / hot / pressed /
/// disabled) from the panel layout.
/// Note: the dat-widget button (Type 1 / UIElement_Button) is
/// in UiButton.cs — that is the production widget used by D.2b panels.
/// This class is the earlier dev-scaffold button (plain rect + text; no dat sprites).
///
public class UiSimpleButton : UiPanel
{
public string Text { get; set; } = string.Empty;
public Vector4 TextColor { get; set; } = new(1f, 1f, 1f, 1f);
public event System.Action? Click;
public UiSimpleButton()
{
BackgroundColor = new Vector4(0.1f, 0.1f, 0.15f, 0.8f);
BorderColor = new Vector4(0.45f, 0.45f, 0.55f, 1f);
}
public override bool OnEvent(in UiEvent e)
{
if (e.Type == UiEventType.Click && Enabled)
{
Click?.Invoke();
return true;
}
return false;
}
protected override void OnDraw(UiRenderContext ctx)
{
base.OnDraw(ctx);
if (Text.Length == 0 || ctx.DefaultFont is null) return;
float textW = ctx.DefaultFont.MeasureWidth(Text);
float tx = (Width - textW) * 0.5f;
float ty = (Height - ctx.DefaultFont.LineHeight) * 0.5f;
ctx.DrawString(Text, tx, ty, TextColor);
}
}
///
/// A that fires an callback when the user
/// left-clicks it. Used for the attribute-list rows in the Character window — each row
/// is a transparent container that needs to respond to pointer hits while its children
/// (icon, name, value) are ClickThrough decorations.
///
/// Retail analog: the AttributeInfoRegion row widget in gmAttributeUI
/// catches UIEvent_LeftClick (0x01) and calls SetSelectedAttribute on the
/// parent window. In acdream we wire the equivalent via this action callback instead of
/// the retail message bus.
///
/// When is true and
/// is non-zero, draws the sprite as a thin full-width bar at the TOP and BOTTOM edges of
/// the row (not stretched to fill). This matches retail's selection highlight which shows
/// a horizontal dark bar on both the top and bottom edge of the selected attribute row,
/// with NO left/right end-caps. Bar height is pixels
/// (default 3px).
///
public class UiClickablePanel : UiPanel
{
/// Called when the user releases the left mouse button over this panel.
public Action? OnClick { get; set; }
/// When true and is non-zero, draws
/// the sprite as a thin horizontal bar at the top AND bottom edges of the panel,
/// NOT as a full-height stretched fill. Matches retail's selected-row highlight
/// (sprite 0x06001397 — 300×32 px — shown as bars, not a block fill).
/// Default false (preserves legacy full-stretch behavior).
public bool UseSelectionBars { get; set; }
/// Height in pixels of each selection bar (top and bottom). Default 3px.
/// Ignored when is false.
public float SelectionBarHeight { get; set; } = 3f;
public UiClickablePanel()
{
// Rows must receive pointer events — override the UiPanel default (ClickThrough=false,
// which is the UiElement base default). Explicit for clarity.
ClickThrough = false;
}
/// HandlesClick = true ensures this row receives its own Click even when
/// it is nested inside a Draggable ancestor window frame (e.g. the future character
/// window with a whole-window drag handle). Without this, the press would be consumed
/// by the ancestor's drag logic and the Click would never fire.
public override bool HandlesClick => true;
public override bool OnEvent(in UiEvent e)
{
if (e.Type == UiEventType.Click && Enabled)
{
OnClick?.Invoke();
return true;
}
return false;
}
protected override void OnDraw(UiRenderContext ctx)
{
if (UseSelectionBars && BackgroundSprite != 0 && SpriteResolve is { } sr)
{
// Draw the selection highlight as a thin bar at the TOP and BOTTOM of the row.
// The sprite (0x06001397) is 300×32 px — we draw it as horizontal strips at
// native height (SelectionBarHeight), stretched to full panel width (UV tile
// horizontally). No left/right end-caps: u0=0, u1=Width/nativeW (UV repeat).
var (tex, tw, th) = sr(BackgroundSprite);
if (tex != 0 && tw > 0 && th > 0)
{
float barH = SelectionBarHeight;
float uTile = tw > 0 ? Width / tw : 1f;
// Top bar: shows the top barH px of the sprite (v = 0 → barH/th).
float vBot = th > 0 ? barH / th : 1f;
ctx.DrawSprite(tex, 0f, 0f, Width, barH, 0f, 0f, uTile, vBot, Vector4.One);
// Bottom bar: shows the bottom barH px of the sprite (v = 1−barH/th → 1).
float vTop2 = th > 0 ? 1f - barH / th : 0f;
ctx.DrawSprite(tex, 0f, Height - barH, Width, barH, 0f, vTop2, uTile, 1f, Vector4.One);
}
// Selection-bar mode draws no border (rows have BorderColor=Zero by design).
}
else
{
// Default UiPanel draw: handles BackgroundSprite, BackgroundColor, AND border.
base.OnDraw(ctx);
}
}
}