Skip to content

hover: send the move through the window's input, so a declared ToolTip.Tip opens (#1265) - #1304

Merged
imojenisys merged 3 commits into
mainfrom
fix/1265-hover-tooltip
Oct 21, 2026
Merged

imojenisys merged 3 commits into
mainfrom
fix/1265-hover-tooltip

Conversation

@imojenisys

Copy link
Copy Markdown
Contributor

Closes #1265. The live measurement is split out as #1303, and the gaps doc keeps a narrowed entry 7 until it is done.

What changed

hover now sends its mouse move through the window's own input, the way the platform delivers a real one. Previously it raised PointerEntered/PointerMoved on the element, which Avalonia's tooltip service never sees, so a declared ToolTip.Tip never opened.

How the move is sent:

  • It uses TopLevel.PlatformImpl.Input, with the window's non-public input root, a raw pointer event and one mouse device shared by every hover.
  • Avalonia 12.0.4's reference assemblies hide all of these, so each is reached by reflection. The lookups live in a class of their own, so a changed Avalonia breaks only this route.
  • The tool waits for the window's next frame first, as click does.

What the reply says:

  • The move lands on whatever the window routed it to.
  • If a control is drawn over the target, the reply names that control and reports its tip, since that is what a person would see.
  • If the target is disabled, the reply says so.
  • If the move hits nothing, or the reflected members can't be reached, it is raised on the control as before, and the reply says why.
  • A successful reply names the route: hovered (60, 20) through the window's input.

After the watch:

  • The pointer leaves the window (a raw LeaveWindow), which closes the tip, so the tip doesn't cover the next click.
  • The code editor now clears its last hover position on pointer exit, so a hover back at the same point opens a tip again.

Review

One adversarial reviewer, working headless with probes against both this branch and main.

Finding Severity Outcome
A second hover at the same editor point reported no tip. A new device per hover made the editor think the pointer had left, and the repeat was then dropped as jitter. major Fixed: one shared device, and the editor clears its hover on exit. Test covers both orders.
The fallback fired for disabled or covered targets. It gave a false "hovered" and a wrong reason, and missed a tip that did open. major Fixed: the reply reports what the move landed on and that control's tip, or says the target is disabled. The fallback is now only for a move that hits nothing, or members that can't be reached.
A catch-all swallowed exceptions from app handlers, mislabelled them, and delivered the move twice. minor Fixed: only the reflection lookups are caught. A handler's exception fails the hover once. Tested.
The tip and pointer-over lingered after a hover, and a later click was intercepted. minor Fixed: the tool ends the hover with LeaveWindow. Tested, including that a click reaches its target afterwards.
The description overreached. minor Rewritten. It says "measured headless only".
The gap was archived before the live check. minor The gap is narrowed and kept open, and the live run is filed as #1303.
No editor test went through the new route. minor Added.
Reflection in static initialisers could take down every UI tool. minor Moved into a nested class, initialised inside the catch.
Nits: stale comments and remarks, the dap-adapter-smoke.md line, the reply not naming the route. nit Fixed.

Mutation-checked: shared device, editor exit reset, the reached/covered decision, the disabled case and EndHover. Each mutant fails a test.

Not covered by any test: the tool-level frame wait, the EndHover call in the tool, and the declared-tip notes. No test project references HexIDE.Desktop. These parts were read from code, not run.

Full suites: HexIDE.Tests 3086 passed, HexIDE.Integration.Tests 1459 passed.

🤖 Generated with Claude Code

imojenisys and others added 3 commits October 21, 2026 05:22
…p.Tip opens (#1265)

hover raised PointerEntered/PointerMoved on the element, which Avalonia's
tooltip service never sees: it handles raw input as the input manager
processes it. The move now goes through TopLevel.PlatformImpl.Input with
the window's input root and a raw pointer event, each reached by
reflection since 12.0.4 hides them, so it is hit-tested and routed as a
real move. The tool waits for the window's next frame first, as click
does. When the target does not see the move, or a member is unreachable,
the move is raised on the target as before and the reply says why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1265)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1265 review)

- The move's hit is what the window routed it to. A control drawn over
  the target is reported by name with its tip; a disabled target is said
  to be disabled. Only a move that hits nothing, or an Avalonia whose
  members cannot be reached, falls back to raising on the control.
- One mouse device for every hover, and the code editor forgets its
  hover on pointer exit: a second hover at the same point opened no
  quick info.
- The tool ends the hover with a raw LeaveWindow, so the tip does not
  cover the next click.
- Reflection lookups live in their own class, so a changed Avalonia
  fails only this; what a handler throws is no longer swallowed and
  retried.
- The live run is #1303; the gap stays open, narrowed, until it is done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@imojenisys
imojenisys enabled auto-merge (squash) October 21, 2026 05:08
@imojenisys
imojenisys merged commit d9be122 into main Oct 21, 2026
14 checks passed
@imojenisys
imojenisys deleted the fix/1265-hover-tooltip branch October 21, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hover cannot open a declarative ToolTip.Tip, because its pointer events never pass through the window's input, which the tooltip service reads

1 participant