using System; using System.Numerics; using AcDream.App.UI.Layout; namespace AcDream.App.UI; /// /// Generic dat-widget button — the production replacement for any dat element of /// Type 1 (UIElement_Button, registered via RegisterElementClass(1, UIElement_Button::Create) /// @ acclient_2013_pseudo_c.txt:125828). /// /// /// Draws per-state sprite media exactly like (same /// ActiveState defaulting, same ActiveMedia() fallback chain, same tiled /// DrawSprite call with UV-repeat so chrome edges tile correctly) plus an /// optional centered text label. The click behavior mirrors /// one-for-one so the chat Send and Max/Min buttons that previously bound through /// UiDatElement.OnClick continue to work without behavioral change. /// /// /// /// State selection: picks if set, then /// "Normal" if the element has a Normal state sprite, then falls back to the unnamed /// DirectState ("" key) — identical to . /// /// /// /// Built by for Type-1 elements (chat Send 0x10000019, /// Max/Min 0x1000046F). NOT the same as , which is an /// earlier dev-scaffold widget with no dat sprites. /// /// public sealed class UiButton : UiElement { private readonly ElementInfo _info; private readonly Func _resolve; /// Optional click handler. Wired by the controller (e.g. chat Submit, ToggleMaximize). public Action? OnClick { get; set; } /// Optional centered text label drawn over the sprite (e.g. "Send" on a blank gold frame). public string? Label { get; set; } /// Dat font for . Required for the label to draw. public UiDatFont? LabelFont { get; set; } /// Label color (default white). public Vector4 LabelColor { get; set; } = Vector4.One; /// Horizontal alignment of . Center (default) for normal buttons; /// Left for the paperdoll "Slots" caption that sits at the left edge, before the slots. public LabelAlignment LabelAlign { get; set; } = LabelAlignment.Center; /// Label horizontal alignment options. public enum LabelAlignment { Center, Left } /// /// Active state name, runtime-settable (e.g. Max/Min toggling Normal ↔ Minimized). /// Matches . /// public string ActiveState { get; set; } = ""; /// Merged for this element. /// Dat file-id → (GL texture handle, native px width, native px height). /// Returns (0,0,0) when the texture is not yet uploaded. public UiButton(ElementInfo info, Func resolve) { _info = info; _resolve = resolve; ClickThrough = false; // buttons are interactive — opt OUT of click-through // State defaulting matches UiDatElement exactly: // DefaultStateName wins; else "Normal" if that state has a sprite; else DirectState (""). if (!string.IsNullOrEmpty(info.DefaultStateName)) ActiveState = info.DefaultStateName; else if (info.StateMedia.ContainsKey("Normal")) ActiveState = "Normal"; // else ActiveState stays "" (DirectState) } /// The button draws its own face + label; any dat label child is reproduced /// procedurally, so the importer must not build the button's children as widgets. public override bool ConsumesDatChildren => true; /// A button is interactive — it must receive its Click even inside a whole-window-Draggable /// frame (e.g. the paperdoll "Slots" toggle in the inventory window), so it opts out of the /// IA-12 whole-window-drag that would otherwise swallow the press. public override bool HandlesClick => true; /// /// Returns the File id for the current , falling back to /// the DirectState ("" key) if the named state is absent. /// Returns 0 if neither exists. /// Mirrors . /// private uint ActiveFile() => _info.StateMedia.TryGetValue(ActiveState, out var m) ? m.File : _info.StateMedia.TryGetValue("", out var d) ? d.File : 0u; protected override void OnDraw(UiRenderContext ctx) { uint file = ActiveFile(); if (file != 0) { var (tex, tw, th) = _resolve(file); if (tex != 0 && tw != 0 && th != 0) { // Tiled draw — same call shape as UiDatElement.OnDraw (UV-repeat; GL_REPEAT-wrapped // UI texture). Matches ImgTex::TileCSI; no Stretch mode exists. ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Vector4.One); } } if (Label is { Length: > 0 } label && LabelFont is { } lf) { float tx = LabelAlign == LabelAlignment.Left ? 3f // small left pad (room for a future checkbox box) : (Width - lf.MeasureWidth(label)) * 0.5f; // centered (default) float ty = (Height - lf.LineHeight) * 0.5f; ctx.DrawStringDat(lf, label, tx, ty, LabelColor); } } public override bool OnEvent(in UiEvent e) { if (e.Type == UiEventType.Click && OnClick is not null) { OnClick(); return true; } return false; } }