diff --git a/src/AcDream.App/UI/Layout/ChatWindowController.cs b/src/AcDream.App/UI/Layout/ChatWindowController.cs index e02efb56..d3b33019 100644 --- a/src/AcDream.App/UI/Layout/ChatWindowController.cs +++ b/src/AcDream.App/UI/Layout/ChatWindowController.cs @@ -46,8 +46,8 @@ public sealed class ChatWindowController private const uint ThumbSprite = 0x06004C63u; // 3-slice middle tile private const uint ThumbTopSprite = 0x06004C60u; // 3-slice top cap private const uint ThumbBotSprite = 0x06004C66u; // 3-slice bottom cap - private const uint UpSprite = 0x06004C69u; - private const uint DownSprite = 0x06004C6Cu; + private const uint UpSprite = 0x06004C6Cu; // up arrow (top button) + private const uint DownSprite = 0x06004C69u; // down arrow (bottom button) // Channel menu sprite ids (confirmed in chat element dump). private const uint MenuNormal = 0x06004D65u; // button face @@ -199,10 +199,13 @@ public sealed class ChatWindowController { c.Scrollbar = new UiChatScrollbar { + // Pull the bar up to the panel top so the top arrow meets the window + // border (and lines up with the max/min button at root y=0); the dat + // track sits 6px down, which left a gap after the resize-bar reclaim. Left = track.Left, - Top = track.Top, + Top = 0f, Width = track.Width, - Height = track.Height, + Height = track.Height + track.Top, Anchors = track.Anchors, Model = c.Transcript.Scroll, SpriteResolve = resolve, @@ -248,6 +251,10 @@ public sealed class ChatWindowController { 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"; + sendEl.LabelFont = datFont; + sendEl.LabelColor = new Vector4(1f, 0.92f, 0.72f, 1f); } // ── Max/min toggle — simplified gmMainChatUI::HandleMaximizeButton ── diff --git a/src/AcDream.App/UI/Layout/UiDatElement.cs b/src/AcDream.App/UI/Layout/UiDatElement.cs index 43cc4032..5f6ea79c 100644 --- a/src/AcDream.App/UI/Layout/UiDatElement.cs +++ b/src/AcDream.App/UI/Layout/UiDatElement.cs @@ -87,21 +87,36 @@ public sealed class UiDatElement : UiElement return false; } + /// Optional centered text label drawn over the sprite (e.g. the "Send" + /// button face whose dat sprite is a blank frame). Null = sprite only. + 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; + protected override void OnDraw(UiRenderContext ctx) { var (file, _) = ActiveMedia(); - if (file == 0) return; + if (file != 0) + { + var (tex, tw, th) = _resolve(file); + if (tex != 0 && tw != 0 && th != 0) + { + // Normal → TILE at native size on both axes (UV-repeat; GL_REPEAT-wrapped UI + // texture), matching ImgTex::TileCSI. Overlay/Alphablend use the same blit (the + // sprite shader already alpha-blends). No Stretch mode exists in DrawModeType. + ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Vector4.One); + } + } - var (tex, tw, th) = _resolve(file); - if (tex == 0 || tw == 0 || th == 0) return; - - // Normal → TILE at native size on both axes (UV-repeat; GL_REPEAT-wrapped UI texture), - // matching ImgTex::TileCSI. Overlay/Alphablend are the same blit with a blend state; the - // sprite shader already alpha-blends, so the quad is identical for all draw modes in Plan 1. - // (No Stretch mode exists in DatReaderWriter.Enums.DrawModeType.) - // DrawMode is not yet branched here — Plan 2 can add per-mode behavior if needed. - float u1 = Width / tw; - float v1 = Height / th; - ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, u1, v1, Vector4.One); + // Centered text label over the sprite (retail draws button captions as text; + // their dat sprites are blank frames). + 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); + } } } diff --git a/src/AcDream.App/UI/UiChannelMenu.cs b/src/AcDream.App/UI/UiChannelMenu.cs index 01d1f735..b9e01f62 100644 --- a/src/AcDream.App/UI/UiChannelMenu.cs +++ b/src/AcDream.App/UI/UiChannelMenu.cs @@ -134,11 +134,12 @@ public sealed class UiChannelMenu : UiElement bool selected = Items[i].Channel is { } c && c == Selected; DrawSprite(ctx, resolve, selected ? ItemHighlightSprite : ItemNormalSprite, x, y, ColW, ItemH); } + float textY = (ItemH - LineH()) * 0.5f; // center the label in its row for (int i = 0; i < Items.Length; i++) { int col = i / Rows, row = i % Rows; bool avail = Items[i].Channel is { } c && IsAvailable(c); - DrawLabel(ctx, Items[i].Label, 4f + col * ColW, top + row * ItemH, + DrawLabel(ctx, Items[i].Label, 4f + col * ColW, top + row * ItemH + textY, avail ? TextColorAvailable : TextColorGhosted); } } diff --git a/src/AcDream.App/UI/UiChatScrollbar.cs b/src/AcDream.App/UI/UiChatScrollbar.cs index 6d163ef3..debea724 100644 --- a/src/AcDream.App/UI/UiChatScrollbar.cs +++ b/src/AcDream.App/UI/UiChatScrollbar.cs @@ -89,11 +89,10 @@ public sealed class UiChatScrollbar : UiElement // sprite (~16×32) repeats to fill the element height instead of stretch-distorting. DrawTiled(ctx, resolve, TrackSprite, 0f, 0f, Width, Height); - // Up button — top ButtonH rows. The dat up/down arrow sprites both point DOWN - // (confirmed by sprite export), so the TOP button is drawn V-FLIPPED to point UP. - DrawSpriteFlipV(ctx, resolve, UpSprite, 0f, 0f, Width, ButtonH); + // Up button — top ButtonH rows. UpSprite (0x06004C6C) is the up-arrow art, drawn 1:1. + DrawSprite(ctx, resolve, UpSprite, 0f, 0f, Width, ButtonH); - // Down button — bottom ButtonH rows (down arrow as-is). + // Down button — bottom ButtonH rows. DownSprite (0x06004C69) is the down-arrow art. DrawSprite(ctx, resolve, DownSprite, 0f, Height - ButtonH, Width, ButtonH); // Thumb — only when content overflows the view. Retail 3-slice: top cap +