diff --git a/RadialActions/Services/MenuService.cs b/RadialActions/Services/MenuService.cs
index fbc6aa6..01d2d28 100644
--- a/RadialActions/Services/MenuService.cs
+++ b/RadialActions/Services/MenuService.cs
@@ -1,5 +1,6 @@
using System.Windows;
using System.Windows.Input;
+using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
@@ -14,6 +15,7 @@ internal sealed class MenuService
private readonly Dispatcher _dispatcher;
private bool _isFadingOut;
+ private int _fadeInRequestVersion;
public MenuService(
Window window,
@@ -51,7 +53,44 @@ public void ShowMenu(bool atCursor)
_ = FocusMenuForKeyboardInputAsync();
_pieMenu.ResetInputState();
_window.IsHitTestVisible = true;
- BeginFadeIn();
+ BeginFadeInWhenSurfaceReady();
+ }
+
+ ///
+ /// Starts the fade-in only after the layered window has rendered and submitted a frame.
+ ///
+ ///
+ /// The OS hit-tests a layered window against its last submitted surface, and the first submission after
+ /// lags by several frames. Starting the animation immediately made the fade run
+ /// against a still-invisible, click-through window: the menu appeared mid-animation at high opacity, and
+ /// clicks in that gap fell through to the window beneath, which dismissed the menu via deactivation.
+ /// Waiting two composition ticks (one to render the shown surface, one so it is submitted) keeps the menu
+ /// hittable from the first visible pixel and lets the full fade actually be seen.
+ ///
+ private void BeginFadeInWhenSurfaceReady()
+ {
+ var version = ++_fadeInRequestVersion;
+ var renderedTicks = 0;
+
+ void OnRendering(object sender, EventArgs e)
+ {
+ if (version != _fadeInRequestVersion)
+ {
+ CompositionTarget.Rendering -= OnRendering;
+ return;
+ }
+
+ renderedTicks++;
+ if (renderedTicks < 2)
+ {
+ return;
+ }
+
+ CompositionTarget.Rendering -= OnRendering;
+ BeginFadeIn();
+ }
+
+ CompositionTarget.Rendering += OnRendering;
}
public void HideMenu(bool animate = true)
@@ -122,6 +161,8 @@ private void BeginFadeIn()
private void BeginFadeOut()
{
+ // Cancels any fade-in still waiting on its first rendered frame.
+ _fadeInRequestVersion++;
StopFadeAnimations();
if (IsReducedMotionEnabled())
@@ -148,6 +189,8 @@ private static bool IsReducedMotionEnabled()
private void HideMenuImmediately()
{
+ // Cancels any fade-in still waiting on its first rendered frame.
+ _fadeInRequestVersion++;
_isFadingOut = false;
StopFadeAnimations();
_window.IsHitTestVisible = false;