feat(chat): port retail client command families
Expand the typed client-command boundary across travel, character queries, local UI and layout controls, AFK and consent, emotes, friends, squelch and filters, and fill-components. Preserve retail packet layouts and queue ownership, import the confirmation dialog, and keep authoritative social state in Core. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
5a45a7ac7f
commit
9ea579bdd0
47 changed files with 6659 additions and 99 deletions
|
|
@ -311,6 +311,7 @@ public sealed class UiText : UiElement
|
|||
if (y < top || y + lh > bottom) continue; // whole-line vertical clip (no scissor yet)
|
||||
|
||||
string text = lines[i].Text;
|
||||
float lineX = HorizontalOffset(text, datFont, bitmapFont);
|
||||
|
||||
// Selection highlight behind this line's selected character span.
|
||||
if (hasSel && i >= selStart.Line && i <= selEnd.Line)
|
||||
|
|
@ -324,12 +325,12 @@ public sealed class UiText : UiElement
|
|||
float hx, hw;
|
||||
if (datFont is not null)
|
||||
{
|
||||
hx = Padding + datFont.MeasureWidth(text.Substring(0, c0));
|
||||
hx = lineX + datFont.MeasureWidth(text.Substring(0, c0));
|
||||
hw = datFont.MeasureWidth(text.Substring(c0, c1 - c0));
|
||||
}
|
||||
else
|
||||
{
|
||||
hx = Padding + bitmapFont!.MeasureWidth(text.Substring(0, c0));
|
||||
hx = lineX + bitmapFont!.MeasureWidth(text.Substring(0, c0));
|
||||
hw = bitmapFont.MeasureWidth(text.Substring(c0, c1 - c0));
|
||||
}
|
||||
// Highlight sits BEHIND the line's text → sprite bucket, submitted
|
||||
|
|
@ -339,12 +340,24 @@ public sealed class UiText : UiElement
|
|||
}
|
||||
|
||||
if (datFont is not null)
|
||||
ctx.DrawStringDat(datFont, text, Padding, y, lines[i].Color);
|
||||
ctx.DrawStringDat(datFont, text, lineX, y, lines[i].Color);
|
||||
else
|
||||
ctx.DrawString(text, Padding, y, lines[i].Color, bitmapFont);
|
||||
ctx.DrawString(text, lineX, y, lines[i].Color, bitmapFont);
|
||||
}
|
||||
}
|
||||
|
||||
private float HorizontalOffset(string text, UiDatFont? datFont, BitmapFont? bitmapFont)
|
||||
{
|
||||
float width = datFont is not null
|
||||
? datFont.MeasureWidth(text)
|
||||
: bitmapFont?.MeasureWidth(text) ?? 0f;
|
||||
if (Centered)
|
||||
return Math.Max(Padding, (Width - width) * 0.5f);
|
||||
if (RightAligned)
|
||||
return Math.Max(Padding, Width - Padding - width);
|
||||
return Padding;
|
||||
}
|
||||
|
||||
public override bool OnEvent(in UiEvent e)
|
||||
{
|
||||
switch (e.Type)
|
||||
|
|
@ -545,16 +558,80 @@ public sealed class UiText : UiElement
|
|||
line = Math.Clamp(line, 0, lines.Count - 1);
|
||||
|
||||
string text = lines[line].Text;
|
||||
float lineX = HorizontalOffset(text, _lastDatFont, _lastFont);
|
||||
int col = _lastDatFont is { } df
|
||||
? CharIndexAt(text, ch => df.TryGetGlyph(ch, out var g) ? UiDatFont.GlyphAdvance(g) : 0f,
|
||||
localX - _lastPadding)
|
||||
localX - lineX)
|
||||
: (_lastFont is { } bf
|
||||
? CharIndexAt(text, ch => bf.TryGetGlyph(ch, out var bg) ? bg.Advance : 0f,
|
||||
localX - _lastPadding)
|
||||
localX - lineX)
|
||||
: 0);
|
||||
return new Pos(line, col);
|
||||
}
|
||||
|
||||
/// <summary>Word-wrap text to a measured pixel width, preserving explicit newlines.</summary>
|
||||
public static IReadOnlyList<string> WrapWords(
|
||||
string text,
|
||||
Func<string, float> measureWidth,
|
||||
float maximumWidth)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(text);
|
||||
ArgumentNullException.ThrowIfNull(measureWidth);
|
||||
if (maximumWidth <= 0f) throw new ArgumentOutOfRangeException(nameof(maximumWidth));
|
||||
|
||||
var result = new List<string>();
|
||||
string[] paragraphs = text.Replace("\r", string.Empty).Split('\n');
|
||||
foreach (string paragraph in paragraphs)
|
||||
{
|
||||
if (paragraph.Length == 0)
|
||||
{
|
||||
result.Add(string.Empty);
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] words = paragraph.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
var line = new StringBuilder();
|
||||
foreach (string word in words)
|
||||
{
|
||||
string candidate = line.Length == 0 ? word : $"{line} {word}";
|
||||
if (measureWidth(candidate) <= maximumWidth)
|
||||
{
|
||||
if (line.Length != 0) line.Append(' ');
|
||||
line.Append(word);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.Length != 0 && measureWidth(word) <= maximumWidth)
|
||||
{
|
||||
result.Add(line.ToString());
|
||||
line.Clear();
|
||||
line.Append(word);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Retail GlyphList wrapping can split an over-width glyph run.
|
||||
// Pack as much of the long token as possible onto the current
|
||||
// line, then continue at character boundaries without hyphens.
|
||||
for (int i = 0; i < word.Length; i++)
|
||||
{
|
||||
string prefix = i == 0 && line.Length != 0 ? " " : string.Empty;
|
||||
if (line.Length != 0
|
||||
&& measureWidth(line + prefix + word[i]) > maximumWidth)
|
||||
{
|
||||
result.Add(line.ToString());
|
||||
line.Clear();
|
||||
prefix = string.Empty;
|
||||
}
|
||||
line.Append(prefix).Append(word[i]);
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(line.ToString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The caret column for a horizontal position <paramref name="x"/> (already
|
||||
/// adjusted for the left padding, so x=0 is the start of the text). Walks the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue