fix(ui): finish retail stack selector polish

Keep the horizontal thumb at its raw pointer position so both endpoints are reachable, honor the stack entry's authored right alignment, and preserve PublicWeenieDesc plural names from CreateObject through the object table. Port retail's singular s/es fallback when no plural was sent.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-11 10:22:11 +02:00
parent a20e5c68c7
commit 6005c51c4d
22 changed files with 233 additions and 36 deletions

View file

@ -33,6 +33,8 @@ public sealed class UiField : UiElement
public int MaxCharacters { get; set; } = 0xFFFF;
public bool OneLine { get; set; } = true;
public bool Selectable { get; set; }
public bool Centered { get; set; }
public bool RightAligned { get; set; }
public bool ClearOnSubmit { get; set; } = true;
public bool RecordHistory { get; set; } = true;
public Func<char, bool>? CharacterFilter { get; set; }
@ -272,7 +274,7 @@ public sealed class UiField : UiElement
/// FindPixelsFromPos inverse. Accounts for the horizontal scroll offset.</summary>
private int HitCharX(float localX)
{
float target = localX - Padding + _scrollX;
float target = localX - Padding - TextAlignmentOffset() + _scrollX;
if (target <= 0f) return 0;
int best = 0;
float bestDist = float.MaxValue;
@ -324,6 +326,7 @@ public sealed class UiField : UiElement
if (caretX - _scrollX > visibleW) _scrollX = caretX - visibleW;
if (caretX < _scrollX) _scrollX = caretX;
_scrollX = Math.Clamp(_scrollX, 0f, MathF.Max(0f, fullW - visibleW));
float alignX = TextAlignmentOffset();
// Visible character window [start, end).
int start = 0;
@ -335,15 +338,15 @@ public sealed class UiField : UiElement
if (HasSelection)
{
var (lo, hi) = SelSpan();
float h0 = MathF.Max(MeasureTo(lo) - _scrollX, 0f);
float h1 = MathF.Min(MeasureTo(hi) - _scrollX, visibleW);
float h0 = MathF.Max(alignX + MeasureTo(lo) - _scrollX, 0f);
float h1 = MathF.Min(alignX + MeasureTo(hi) - _scrollX, visibleW);
if (h1 > h0) ctx.DrawFill(Padding + h0, ty, h1 - h0, lh, SelectionColor);
}
if (end > start)
{
string vis = _text.Substring(start, end - start);
float vx = Padding + (MeasureTo(start) - _scrollX);
float vx = Padding + alignX + (MeasureTo(start) - _scrollX);
if (DatFont is { } df2) ctx.DrawStringDat(df2, vis, vx, ty, TextColor);
else ctx.DrawString(vis, vx, ty, TextColor, Font);
}
@ -351,12 +354,21 @@ public sealed class UiField : UiElement
if (_focused)
{
// Caret on TOP of the text → submitted after the text in the same bucket.
float cx = Padding + (caretX - _scrollX);
float cx = Padding + alignX + (caretX - _scrollX);
if (cx >= Padding - 1f && cx <= Width - Padding + 1f)
ctx.DrawFill(cx, ty, 1f, lh, TextColor);
}
}
private float TextAlignmentOffset()
{
float visibleW = MathF.Max(1f, Width - 2f * Padding);
float spare = MathF.Max(0f, visibleW - MeasureTo(_text.Length));
if (RightAligned) return spare;
if (Centered) return spare * 0.5f;
return 0f;
}
// ── Auto-repeat ──────────────────────────────────────────────────────
protected override void OnTick(double deltaSeconds)