feat(D.2b): UiButton (Type 1) — Send + Max/Min as generic buttons (widget-generalization Task 3)
Introduces UiButton: a dedicated dat-widget button that ports UIElement_Button (RegisterElementClass(1,...) @ acclient_2013_pseudo_c.txt:125828). State selection, tiled DrawSprite, and label rendering mirror UiDatElement exactly so the chat Send and Max/Min buttons have zero behavioral change. DatWidgetFactory now maps Type 1 → UiButton (beside Type 7 → UiMeter, Type 11 → UiScrollbar). ChatWindowController's Send and Max/Min bind blocks updated from UiDatElement casts to UiButton casts; ClickThrough=false lines dropped (UiButton is interactive by construction). The old UiPanel.cs UiButton (a plain dev-scaffold rect+text button with no dat sprites) is renamed UiSimpleButton to free the name — no production code instantiated it. Full suite: 402 passed, 2 skipped, 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3593d6623d
commit
805ab5f40b
6 changed files with 154 additions and 6 deletions
|
|
@ -243,9 +243,8 @@ public sealed class ChatWindowController
|
|||
|
||||
// ── Send button — Enter-alternate submit trigger ──────────────────
|
||||
// Retail's gmMainChatUI wires the Send button to the same ProcessCommand path.
|
||||
if (layout.FindElement(SendId) is UiDatElement sendEl)
|
||||
if (layout.FindElement(SendId) is UiButton sendEl)
|
||||
{
|
||||
sendEl.ClickThrough = false;
|
||||
sendEl.OnClick = () => c.Input.Submit();
|
||||
// The Send sprite is a blank gold button — retail draws the caption as text.
|
||||
sendEl.Label = "Send";
|
||||
|
|
@ -276,14 +275,13 @@ public sealed class ChatWindowController
|
|||
}
|
||||
|
||||
// ── Max/min toggle — simplified gmMainChatUI::HandleMaximizeButton ──
|
||||
if (layout.FindElement(MaxMinId) is UiDatElement maxMinEl)
|
||||
if (layout.FindElement(MaxMinId) is UiButton maxMinEl)
|
||||
{
|
||||
// The dat puts max/min and the scrollbar up-button at the SAME X (both
|
||||
// right-anchored), so at content width they overlap. Retail shows max/min
|
||||
// just LEFT of the scrollbar column — shift it one button-width left.
|
||||
if (track is not null)
|
||||
maxMinEl.Left = track.Left - maxMinEl.Width;
|
||||
maxMinEl.ClickThrough = false;
|
||||
maxMinEl.OnClick = c.ToggleMaximize;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using AcDream.App.UI;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
|
|
@ -57,6 +58,7 @@ public static class DatWidgetFactory
|
|||
|
||||
UiElement e = info.Type switch
|
||||
{
|
||||
1 => new UiButton(info, resolve), // UIElement_Button (reg :125828)
|
||||
7 => BuildMeter(info, resolve, datFont), // UIElement_Meter
|
||||
11 => new UiScrollbar(), // UIElement_Scrollbar (reg :124137)
|
||||
_ => new UiDatElement(info, resolve), // generic fallback for all other types
|
||||
|
|
|
|||
111
src/AcDream.App/UI/UiButton.cs
Normal file
111
src/AcDream.App/UI/UiButton.cs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using AcDream.App.UI.Layout;
|
||||
|
||||
namespace AcDream.App.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
///
|
||||
/// <para>
|
||||
/// Draws per-state sprite media exactly like <see cref="UiDatElement"/> (same
|
||||
/// <c>ActiveState</c> defaulting, same <c>ActiveMedia()</c> fallback chain, same tiled
|
||||
/// <c>DrawSprite</c> call with UV-repeat so chrome edges tile correctly) plus an
|
||||
/// optional centered text label. The click behavior mirrors <see cref="UiDatElement"/>
|
||||
/// one-for-one so the chat Send and Max/Min buttons that previously bound through
|
||||
/// <c>UiDatElement.OnClick</c> continue to work without behavioral change.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// State selection: picks <see cref="ElementInfo.DefaultStateName"/> if set, then
|
||||
/// "Normal" if the element has a Normal state sprite, then falls back to the unnamed
|
||||
/// DirectState ("" key) — identical to <see cref="UiDatElement"/>.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Built by <see cref="DatWidgetFactory"/> for Type-1 elements (chat Send 0x10000019,
|
||||
/// Max/Min 0x1000046F). NOT the same as <see cref="UiSimpleButton"/>, which is an
|
||||
/// earlier dev-scaffold widget with no dat sprites.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class UiButton : UiElement
|
||||
{
|
||||
private readonly ElementInfo _info;
|
||||
private readonly Func<uint, (uint tex, int w, int h)> _resolve;
|
||||
|
||||
/// <summary>Optional click handler. Wired by the controller (e.g. chat Submit, ToggleMaximize).</summary>
|
||||
public Action? OnClick { get; set; }
|
||||
|
||||
/// <summary>Optional centered text label drawn over the sprite (e.g. "Send" on a blank gold frame).</summary>
|
||||
public string? Label { get; set; }
|
||||
|
||||
/// <summary>Dat font for <see cref="Label"/>. Required for the label to draw.</summary>
|
||||
public UiDatFont? LabelFont { get; set; }
|
||||
|
||||
/// <summary>Label color (default white).</summary>
|
||||
public Vector4 LabelColor { get; set; } = Vector4.One;
|
||||
|
||||
/// <summary>
|
||||
/// Active state name, runtime-settable (e.g. Max/Min toggling Normal ↔ Minimized).
|
||||
/// Matches <see cref="UiDatElement.ActiveState"/>.
|
||||
/// </summary>
|
||||
public string ActiveState { get; set; } = "";
|
||||
|
||||
/// <param name="info">Merged <see cref="ElementInfo"/> for this element.</param>
|
||||
/// <param name="resolve">Dat file-id → (GL texture handle, native px width, native px height).
|
||||
/// Returns (0,0,0) when the texture is not yet uploaded.</param>
|
||||
public UiButton(ElementInfo info, Func<uint, (uint tex, int w, int h)> 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)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the File id for the current <see cref="ActiveState"/>, falling back to
|
||||
/// the DirectState ("" key) if the named state is absent.
|
||||
/// Returns 0 if neither exists.
|
||||
/// Mirrors <see cref="UiDatElement.ActiveMedia()"/>.
|
||||
/// </summary>
|
||||
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 = (Width - lf.MeasureWidth(label)) * 0.5f;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -57,14 +57,17 @@ public class UiLabel : UiElement
|
|||
/// callback. Retail equivalent is Keystone's button widget, driven by
|
||||
/// a <c>StateDesc</c> per <c>UIStateId</c> (normal / hot / pressed /
|
||||
/// disabled) from the panel layout.
|
||||
/// Note: the dat-widget button (Type 1 / UIElement_Button) is <see cref="AcDream.App.UI.UiButton"/>
|
||||
/// in <c>UiButton.cs</c> — that is the production widget used by D.2b panels.
|
||||
/// This class is the earlier dev-scaffold button (plain rect + text; no dat sprites).
|
||||
/// </summary>
|
||||
public class UiButton : UiPanel
|
||||
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 UiButton()
|
||||
public UiSimpleButton()
|
||||
{
|
||||
BackgroundColor = new Vector4(0.1f, 0.1f, 0.15f, 0.8f);
|
||||
BorderColor = new Vector4(0.45f, 0.45f, 0.55f, 1f);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue