Skip to content

Start the fade-in only after the menu surface has been submitted - #76

Merged
danielchalmers merged 1 commit into
mainfrom
claude/fade-clickthrough-74
Aug 10, 2026
Merged

Start the fade-in only after the menu surface has been submitted#76
danielchalmers merged 1 commit into
mainfrom
claude/fade-clickthrough-74

Conversation

@danielchalmers

Copy link
Copy Markdown
Owner

Fixes #74.

Mechanism (measured, not guessed)

The OS hit-tests a layered window against its last submitted surface, and the first submission after Window.Show() lags by ~120-150 ms on a cold open (~30-60 ms warm). The fade animation started immediately on show, so its clock ran against a window that was still invisible and click-through. Probing WindowFromPoint (the same OS path real clicks take) against the fade timeline proved the point:

  • With the fade artificially stretched to 4 s, the window became clickable at a fixed ~150 ms regardless of opacity (~11% at that moment).
  • At the normal 220 ms fade, that same ~150 ms meant the menu appeared mid-animation at ~85-95% opacity (the fade was effectively invisible) and clicks in the gap fell through to the window beneath - which then activated, deactivated the menu, and dismissed it.

Fix

Defer BeginFadeIn by two CompositionTarget.Rendering ticks after ShowMenu: one tick renders the shown surface, the second guarantees it was submitted. Event-driven, no timers. A version counter cancels a pending fade-in if the menu hides first.

Measured result (same probe, fixed build)

Open Before: visible / hittable After: visible / hittable
#1 (cold) +115 ms / +122 ms +70 ms / +63 ms
#2 +54 ms / +50 ms +54 ms / +46 ms
#3 +58 ms / +32 ms +50 ms / +45 ms

The menu is now hittable before the first visible pixel on every open, and the full 0-to-1 fade actually renders instead of popping in near-full opacity. Clicking a slice the instant it appears executes it (verified 3/3 opens, cold included).

Verification

  • Hit-test/visibility probe as above, plus the full user-level smoke (mouse click, keyboard activation, flick, drag-reorder with settings persistence): all pass.
  • dotnet test: 148/148.
  • Pie interior pixel diff vs main: 3 of 121,905 pixels within anti-aliasing tolerance - no visual change to the rendered menu.

Residual (inherent to layered windows): a truly blind click in the first ~50-70 ms - before anything is on screen - still lands beneath the menu; that window shrank from ~150 ms and can't reach zero because a surface cannot be hit-tested before it exists.

The OS hit-tests a layered window against its last submitted surface,
which lags Window.Show by several frames. Running the fade immediately
meant the animation played against an invisible, click-through window:
the menu popped in mid-fade at high opacity, and clicks in the gap fell
through to the window beneath, dismissing the menu via deactivation.
Deferring the fade to the second composition tick makes the menu
hittable from the first visible pixel and makes the fade visible at all.

Fixes #74
@danielchalmers
danielchalmers merged commit 661ebfc into main Aug 10, 2026
1 check passed
@danielchalmers
danielchalmers deleted the claude/fade-clickthrough-74 branch August 10, 2026 04:01
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.

Clicks during the fade-in fall through the menu and dismiss it

1 participant