feat(D.2b): chat wiring — menu/input sprites, button reflow, char-wrap, panel wash fix
- ChatWindowController: wires the menu chrome (popup bevel, row/checkbox sprites), the input focused-field sprite + keyboard, and autosizes the channel button + reflows the input field to start after it (anchor re-capture so the per-frame layout doesn't fight it). DefaultTextInput / write-mode focus hooked up. - WrapText now breaks an over-long UNBROKEN token at character boundaries (no hyphen), packed onto the current line first — so a spaceless token wraps instead of overflowing, and a "You say," prefix stays on the same row as the start of the message. - UiChatView: transcript background + selection highlight use DrawFill (sprite bucket) so the transcript text draws ON TOP instead of being dimmed by its own translucent rect background. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2284a376ae
commit
ce848c154d
2 changed files with 58 additions and 11 deletions
|
|
@ -49,6 +49,9 @@ public sealed class ChatWindowController
|
|||
private const uint UpSprite = 0x06004C6Cu; // up arrow (top button)
|
||||
private const uint DownSprite = 0x06004C69u; // down arrow (bottom button)
|
||||
|
||||
// Chat input focused-field background (element 0x10000016 Normal_focussed state).
|
||||
private const uint InputFocusField = 0x060011ABu; // gold "lit" field when in write mode
|
||||
|
||||
// Channel menu sprite ids (confirmed in chat element dump).
|
||||
private const uint MenuNormal = 0x06004D65u; // button face
|
||||
private const uint MenuPressed = 0x06004D66u; // button pressed
|
||||
|
|
@ -187,6 +190,8 @@ public sealed class ChatWindowController
|
|||
Anchors = ElementReader.ToAnchors(iInfo.Left, iInfo.Top, iInfo.Right, iInfo.Bottom),
|
||||
DatFont = datFont,
|
||||
Font = debugFont,
|
||||
SpriteResolve = resolve,
|
||||
FocusFieldSprite = InputFocusField,
|
||||
};
|
||||
inputBar.AddChild(c.Input);
|
||||
c.Input.OnSubmit = text => ChatCommandRouter.Submit(text, vm, busProvider(), c._activeChannel);
|
||||
|
|
@ -257,6 +262,28 @@ public sealed class ChatWindowController
|
|||
sendEl.LabelColor = new Vector4(1f, 0.92f, 0.72f, 1f);
|
||||
}
|
||||
|
||||
// ── Size the channel button to its label + reflow the input field ─
|
||||
// Retail's talk-focus button autosizes to the selected channel name; the input
|
||||
// field then fills the gap from the button's right edge to the Send button. The
|
||||
// dat authors the button at a fixed 46px (too narrow for "Chat" once the LED +
|
||||
// arrow are accounted for), so widen it to its content and shift the input.
|
||||
// Recompute on every channel change (the button grows/shrinks with the label).
|
||||
if (c.Menu is not null)
|
||||
{
|
||||
float inputRight = c.Input.Left + c.Input.Width; // == Send button's left edge
|
||||
void ReflowInputRow()
|
||||
{
|
||||
c.Menu.Width = System.MathF.Round(c.Menu.NaturalButtonWidth());
|
||||
c.Menu.ResetAnchorCapture();
|
||||
c.Input.Left = c.Menu.Left + c.Menu.Width;
|
||||
c.Input.Width = System.MathF.Max(40f, inputRight - c.Input.Left);
|
||||
c.Input.ResetAnchorCapture();
|
||||
}
|
||||
var onChanged = c.Menu.OnChannelChanged;
|
||||
c.Menu.OnChannelChanged = k => { onChanged?.Invoke(k); ReflowInputRow(); };
|
||||
ReflowInputRow();
|
||||
}
|
||||
|
||||
// ── Max/min toggle — simplified gmMainChatUI::HandleMaximizeButton ──
|
||||
if (layout.FindElement(MaxMinId) is UiDatElement maxMinEl)
|
||||
{
|
||||
|
|
@ -349,33 +376,47 @@ public sealed class ChatWindowController
|
|||
/// <summary>
|
||||
/// Greedy word-wrap: split <paramref name="text"/> into fragments that each fit in
|
||||
/// <paramref name="maxW"/> pixels (per <paramref name="measure"/>), breaking at spaces.
|
||||
/// A single word longer than the width overflows its own line (retail does not
|
||||
/// hyphenate chat). Mirrors retail GlyphList::Recalculate's per-GlyphLine emission.
|
||||
/// A word that is itself wider than the line is broken at CHARACTER boundaries (no
|
||||
/// hyphen), packed onto the current line first — so a long unbroken token (e.g. a URL
|
||||
/// or "wwwww…") wraps instead of overflowing, and a "You say," prefix stays on the same
|
||||
/// row as the start of the message. Mirrors retail GlyphList::Recalculate's per-GlyphLine
|
||||
/// emission (which breaks mid-glyph-run when a run exceeds the wrap width).
|
||||
/// </summary>
|
||||
public static IEnumerable<string> WrapText(string text, float maxW, Func<string, float> measure)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text) || maxW <= 0f || measure(text) <= maxW)
|
||||
{
|
||||
yield return text;
|
||||
yield return text ?? string.Empty;
|
||||
yield break;
|
||||
}
|
||||
|
||||
var line = new System.Text.StringBuilder();
|
||||
foreach (var word in text.Split(' '))
|
||||
{
|
||||
if (line.Length == 0)
|
||||
string sep = line.Length > 0 ? " " : string.Empty;
|
||||
if (measure(line.ToString() + sep + word) <= maxW)
|
||||
{
|
||||
line.Append(word);
|
||||
line.Append(sep).Append(word); // fits on the current line
|
||||
continue;
|
||||
}
|
||||
else if (measure(line.ToString() + " " + word) > maxW)
|
||||
if (line.Length > 0 && measure(word) <= maxW)
|
||||
{
|
||||
yield return line.ToString(); // word fits alone → push to a new line
|
||||
line.Clear();
|
||||
line.Append(word);
|
||||
continue;
|
||||
}
|
||||
// Word too long for any single line: char-wrap it, packing onto the current
|
||||
// line's remaining space first (keeps the prefix with the message start).
|
||||
if (line.Length > 0) line.Append(' ');
|
||||
foreach (char ch in word)
|
||||
{
|
||||
if (line.Length > 0 && measure(line.ToString() + ch) > maxW)
|
||||
{
|
||||
yield return line.ToString();
|
||||
line.Clear();
|
||||
line.Append(word);
|
||||
}
|
||||
else
|
||||
{
|
||||
line.Append(' ').Append(word);
|
||||
line.Append(ch);
|
||||
}
|
||||
}
|
||||
if (line.Length > 0) yield return line.ToString();
|
||||
|
|
|
|||
|
|
@ -93,7 +93,11 @@ public sealed class UiChatView : UiElement
|
|||
|
||||
protected override void OnDraw(UiRenderContext ctx)
|
||||
{
|
||||
ctx.DrawRect(0, 0, Width, Height, BackgroundColor);
|
||||
// Background must draw UNDER the transcript text. DrawStringDat emits into the
|
||||
// sprite bucket which flushes BEFORE rects, so a DrawRect background would wash
|
||||
// over the text. DrawFill routes the background through the sprite bucket too,
|
||||
// submitted first → text on top.
|
||||
ctx.DrawFill(0, 0, Width, Height, BackgroundColor);
|
||||
|
||||
// Prefer the retail dat font when set; fall back to BitmapFont.
|
||||
var datFont = DatFont;
|
||||
|
|
@ -161,7 +165,9 @@ public sealed class UiChatView : UiElement
|
|||
hx = Padding + bitmapFont!.MeasureWidth(text.Substring(0, c0));
|
||||
hw = bitmapFont.MeasureWidth(text.Substring(c0, c1 - c0));
|
||||
}
|
||||
ctx.DrawRect(hx, y, hw, lh, SelectionColor);
|
||||
// Highlight sits BEHIND the line's text → sprite bucket, submitted
|
||||
// before this line's DrawStringDat.
|
||||
ctx.DrawFill(hx, y, hw, lh, SelectionColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue