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
|
|
@ -44,6 +44,10 @@ public sealed class UiRoot : UiElement
|
|||
/// <summary>Widget currently receiving keyboard events.</summary>
|
||||
public UiElement? KeyboardFocus { get; private set; }
|
||||
|
||||
/// <summary>The edit control activated by Tab/Enter when nothing is focused — retail's
|
||||
/// chat input "write mode" toggle. Set by the host once the chat window is built.</summary>
|
||||
public UiElement? DefaultTextInput { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Single modal overlay; while set, mouse clicks outside its rect
|
||||
/// are ignored. Retail sets this via Device vtable +0x48.
|
||||
|
|
@ -131,6 +135,13 @@ public sealed class UiRoot : UiElement
|
|||
// Render children (panels) sorted by z-order — modal last so it
|
||||
// sits on top.
|
||||
DrawSelfAndChildren(ctx);
|
||||
// Second pass: open popups/menus draw ON TOP of the whole tree (so e.g. the
|
||||
// chat channel menu isn't greyed by the translucent chat panel that draws
|
||||
// after it in the main pass). Routed to the renderer's overlay layer so it
|
||||
// beats even rect backgrounds. Faithful to retail's root-level MakePopup.
|
||||
ctx.BeginOverlayLayer();
|
||||
DrawOverlays(ctx);
|
||||
ctx.EndOverlayLayer();
|
||||
}
|
||||
|
||||
// ── Input entry points (called from GameWindow's Silk.NET handlers) ──
|
||||
|
|
@ -200,12 +211,18 @@ public sealed class UiRoot : UiElement
|
|||
var (target, _, _) = HitTestTopDown(x, y);
|
||||
if (target is null)
|
||||
{
|
||||
// Clicking the 3D world exits write mode (no submit) and returns control to
|
||||
// the character — retail blurs the chat input on an outside click.
|
||||
if (btn == UiMouseButton.Left) SetKeyboardFocus(null);
|
||||
WorldMouseFallThrough?.Invoke(btn, x, y, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set keyboard focus if target accepts it.
|
||||
if (target.AcceptsFocus) SetKeyboardFocus(target);
|
||||
// Keyboard focus follows a left click: the input bar (an edit control) takes
|
||||
// focus = enters write mode; clicking anything else (chrome, Send, scrollbar,
|
||||
// menu, another window) blurs the input = exits write mode WITHOUT submitting.
|
||||
if (btn == UiMouseButton.Left)
|
||||
SetKeyboardFocus(target.AcceptsFocus ? target : null);
|
||||
|
||||
SetCapture(target);
|
||||
|
||||
|
|
@ -355,6 +372,18 @@ public sealed class UiRoot : UiElement
|
|||
|
||||
public void OnKeyDown(int vk, uint lparam = 0)
|
||||
{
|
||||
// Nothing focused yet: Tab or Enter enters "write mode" by focusing the chat
|
||||
// input (retail's chat-activation hotkeys). Consumed so the same press doesn't
|
||||
// also fall through to a game hotkey.
|
||||
if (KeyboardFocus is null && DefaultTextInput is not null
|
||||
&& (vk == (int)Silk.NET.Input.Key.Tab
|
||||
|| vk == (int)Silk.NET.Input.Key.Enter
|
||||
|| vk == (int)Silk.NET.Input.Key.KeypadEnter))
|
||||
{
|
||||
SetKeyboardFocus(DefaultTextInput);
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus widget first.
|
||||
if (KeyboardFocus is not null)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue