fix(ui): morning gate — world tooltips ride retail's mouse-idle dwell, not the found edge
User finding 1 (side-by-side vs retail): our world-object tooltips popped the instant the found object changed; retail's "lag". The night round's derivation from RecvNotice_SmartBoxObjectFound @0x004E5AD0 misread the notice as edge-MOUNTING: its immediate StartTooltipAtMouse @0x004E5DFB is inside `if (s_pInstance->m_dragElement != 0)` (@0x004E5D8E) — and m_dragElement is a real, distinct PDB field in acclient.h's UIElementManager (separate from the m_pTooltipElement family), so the immediate mount is DRAG-AND-DROP ONLY. The ordinary hover path merely STAGES the name (SetTooltip @0x004E5D74 + the |=0x20 TooltipOn bit) and the display rides the SAME UIElementManager::CheckTooltip @0x0045B6E0 mouse-idle dwell as UI tooltips: 250 ms (m_tooltipDelay @0x0045f75d) since m_lastMouseMoveTime (stamped on EVERY move, MouseMoveHandler @0x0045e736). Found swaps under an IDLE mouse replace the popup the same frame (SetTooltip's own text-change teardown @0x004617FF -> ResetTooltip @0x0045C360 tail-calling CheckTooltip); the 10 s duration expiry (@0x0045b78a) requires a fresh mouse move before re-arming (SwitchMouseOver(null) @0x0045b7b2 clears m_pElementLastEntered). Port: UiRoot gains the unconditional last-mouse-move stamp (m_lastMouseMoveTime 1:1 — the existing _hoverStartedMs stamps are deliberately conditional) exposed as MouseIdleMs/NowMs; RetailTooltipPresenter.UpdateWorldHoverTooltip now stages text at the notice edge (ShowTooltips gate + name resolve read there, @0x004E5D21/ @0x004E5D3B, empty-name SetTooltip skip @0x004E5D48 included) and mounts via the CheckTooltip dwell block (no-capture gate @0x0045b715, m_tooltipEnable via MouseHover @0x0046254C — which the drag-immediate branch faithfully bypasses). Session reset also forgets the staged text. Tests: the world-hover fixture section rewritten to the corrected model — found edge stages but never mounts before the dwell; a continuously moving mouse never mounts until it rests; idle found-swap replaces same-frame without stacking; duration auto-hide needs a move + fresh dwell to remount; drag-in-progress mounts immediately. 38/38 pass. Register TS-85 and ISSUES item 2 corrected honestly: the "edge-fired (no dwell)" conclusion is superseded by the user's retail evidence and the m_dragElement branch read. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e316e190cb
commit
9d9280a069
5 changed files with 444 additions and 108 deletions
|
|
@ -377,6 +377,30 @@ public sealed class UiRoot : UiElement
|
|||
|
||||
private long _nowMs;
|
||||
|
||||
/// <summary>Retail <c>UIElementManager::m_lastMouseMoveTime</c>, ported
|
||||
/// 1:1: stamped UNCONDITIONALLY at the top of every mouse move
|
||||
/// (<c>MouseMoveHandler @0x0045E710</c>, <c>@0x0045e729</c>/<c>@0x0045e736</c>
|
||||
/// — before hit-testing, capture handling, everything) and re-stamped on
|
||||
/// capture release (<c>ReleaseMouseCapture @0x0045D2B0</c>,
|
||||
/// <c>@0x0045d2da</c>). Distinct from <see cref="_hoverStartedMs"/>, whose
|
||||
/// stamps are deliberately conditional (the <c>!_tooltipFired</c> guard in
|
||||
/// <see cref="UpdateHover"/>, no stamp during captured moves) because that
|
||||
/// field also carries <c>m_bHoverStarted</c> interplay. The world-hover
|
||||
/// tooltip's idle-dwell gate (<see cref="Layout.RetailTooltipPresenter"/>)
|
||||
/// needs retail's raw, unconditional timestamp.</summary>
|
||||
private long _lastMouseMoveMs;
|
||||
|
||||
/// <summary>Milliseconds since the last mouse move — retail
|
||||
/// <c>CheckTooltip @0x0045B6E0</c>'s dwell operand
|
||||
/// (<c>@0x0045b747</c>: <c>m_lastMouseMoveTime + delay</c> vs now).</summary>
|
||||
public long MouseIdleMs => _nowMs - _lastMouseMoveMs;
|
||||
|
||||
/// <summary>The clock <see cref="Tick"/> last ran at — retail
|
||||
/// <c>Timer::local_time</c> as the UI tree sees it. Exposed for sibling
|
||||
/// per-frame consumers (<see cref="Layout.RetailTooltipPresenter"/>'s
|
||||
/// world-tooltip duration clock) so they share ONE frame timestamp.</summary>
|
||||
public long NowMs => _nowMs;
|
||||
|
||||
/// <summary>Raised when an event was not consumed by any widget.</summary>
|
||||
public event Action<UiMouseButton, int, int, uint>? WorldMouseFallThrough;
|
||||
|
||||
|
|
@ -602,6 +626,9 @@ public sealed class UiRoot : UiElement
|
|||
int dy = y - MouseY;
|
||||
MouseX = x;
|
||||
MouseY = y;
|
||||
// MouseMoveHandler @0x0045e729/@0x0045e736: m_lastMouseMoveTime is
|
||||
// stamped before ANY routing below (resize/window-drag/capture/hover).
|
||||
_lastMouseMoveMs = _nowMs;
|
||||
|
||||
// Window resize takes precedence over move / drag-drop / hover.
|
||||
if (_resizeTarget is not null)
|
||||
|
|
@ -1124,6 +1151,7 @@ public sealed class UiRoot : UiElement
|
|||
// tooltip is already up must leave it up, not clear-then-re-fire it
|
||||
// 250ms later without ever going through TooltipHide.
|
||||
_hoverStartedMs = _nowMs;
|
||||
_lastMouseMoveMs = _nowMs; // ReleaseMouseCapture @0x0045d2da — the same restart
|
||||
NotifyCaptureLost(previous);
|
||||
if (previous is not null)
|
||||
PointerCaptureChanged?.Invoke(previous, null);
|
||||
|
|
@ -1299,6 +1327,7 @@ public sealed class UiRoot : UiElement
|
|||
public void ResetTooltipTracking()
|
||||
{
|
||||
_hoverStartedMs = _nowMs;
|
||||
_lastMouseMoveMs = _nowMs; // same fresh idle deadline for the world-hover dwell
|
||||
_tooltipFired = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue