feat(D.2b): UI render infra — overlay layer, DrawFill, crisp text, write-mode focus
The retail-look render + focus primitives this chat pass builds on: - TextRenderer: an OVERLAY layer (sprite/rect/text buckets flushed AFTER the normal layer) so an open popup composites on top of everything incl. rect panel backgrounds; a DrawFill primitive (solid quad via a 1x1 white texture) routed through the SPRITE bucket so a panel background draws UNDER its text instead of being washed by the later rect bucket; and the text pass now disables SampleAlphaToCoverage + Multisample so glyph alpha edges aren't dithered into MSAA coverage (the "fuzzy text") — self-contained GL state per feedback_render_self_contained_gl_state. - UiRenderContext.DrawStringDat: snap the line baseline to a whole pixel ONCE then add the integer per-glyph offset (retail DrawCharacter takes an int pen-Y + schar m_VerticalOffsetBefore) — fixes the "letters dip down" jitter at a fractional line origin. Outline pass is now opt-in (retail gates it per element via SetOutline; default off = crisp fill-only). Adds DrawFill + Begin/EndOverlayLayer. - UiElement: OnDrawOverlay + DrawOverlays (second traversal), FindRoot (blur self), ResetAnchorCapture (re-baseline an anchored element after reflow). - UiRoot: runs the overlay pass after the main tree; Tab/Enter focuses the DefaultTextInput (write-mode activation); a left click on a non-edit target blurs the focused input (exit write mode without submitting). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
828bec5fb5
commit
ebfeaff840
4 changed files with 248 additions and 63 deletions
|
|
@ -154,6 +154,16 @@ public abstract class UiElement
|
|||
/// </summary>
|
||||
protected virtual void OnDraw(UiRenderContext ctx) { }
|
||||
|
||||
/// <summary>
|
||||
/// Draw content that must sit ON TOP of the ENTIRE UI, regardless of this
|
||||
/// element's position in the tree — open menus, dropdowns, tooltips. Called in
|
||||
/// a SECOND traversal after the whole tree's <see cref="OnDraw"/> pass, with the
|
||||
/// same accumulated transform/alpha this element had during its normal draw.
|
||||
/// Retail spawns popups as ROOT elements (UIElement_Menu::MakePopup) for exactly
|
||||
/// this reason; this is the equivalent without reparenting. Default: nothing.
|
||||
/// </summary>
|
||||
protected virtual void OnDrawOverlay(UiRenderContext ctx) { }
|
||||
|
||||
/// <summary>Per-frame tick (animations, timers, caret blink).</summary>
|
||||
protected virtual void OnTick(double deltaSeconds) { }
|
||||
|
||||
|
|
@ -213,6 +223,34 @@ public abstract class UiElement
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>Second draw traversal: re-walks the tree applying the same
|
||||
/// transform/alpha as <see cref="DrawSelfAndChildren"/> and calls
|
||||
/// <see cref="OnDrawOverlay"/> on each element, so popups composite on top of
|
||||
/// everything drawn in the main pass (dat-font glyphs and sprites share one
|
||||
/// submission-ordered bucket, so later submissions win).</summary>
|
||||
internal void DrawOverlays(UiRenderContext ctx)
|
||||
{
|
||||
if (!Visible) return;
|
||||
ctx.PushTransform(Left, Top);
|
||||
ctx.PushAlpha(Opacity);
|
||||
try
|
||||
{
|
||||
OnDrawOverlay(ctx);
|
||||
if (_children.Count > 0)
|
||||
{
|
||||
var ordered = _children.ToArray();
|
||||
Array.Sort(ordered, static (a, b) => a.ZOrder.CompareTo(b.ZOrder));
|
||||
for (int i = 0; i < ordered.Length; i++)
|
||||
ordered[i].DrawOverlays(ctx);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.PopAlpha();
|
||||
ctx.PopTransform();
|
||||
}
|
||||
}
|
||||
|
||||
internal void TickSelfAndChildren(double dt)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
|
@ -275,6 +313,22 @@ public abstract class UiElement
|
|||
Left = x; Top = y; Width = w; Height = h;
|
||||
}
|
||||
|
||||
/// <summary>Forget the captured anchor margins so the next <see cref="ApplyAnchor"/>
|
||||
/// re-captures them from the CURRENT rect. Call after manually repositioning/resizing
|
||||
/// an anchored element at runtime (e.g. reflowing the chat input when the channel
|
||||
/// button width changes) so the new rect becomes the anchor baseline.</summary>
|
||||
internal void ResetAnchorCapture() => _anchorCaptured = false;
|
||||
|
||||
/// <summary>Walk up to the owning <see cref="UiRoot"/> (the top of the tree), or null
|
||||
/// if this element is not attached. Lets a widget reach focus/capture services — e.g.
|
||||
/// a chat input blurring itself (exiting write mode) after submit.</summary>
|
||||
internal UiRoot? FindRoot()
|
||||
{
|
||||
UiElement e = this;
|
||||
while (e.Parent is not null) e = e.Parent;
|
||||
return e as UiRoot;
|
||||
}
|
||||
|
||||
/// <summary>Compute an anchored child rect. Left&Right ⇒ stretch width
|
||||
/// (keep both margins); Right only ⇒ pin to right at fixed width; otherwise
|
||||
/// pin left at fixed width. Same logic vertically.</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue