@@ -29,6 +29,18 @@ public static class OverlayDialogPresenter
2929{
3030 private static readonly TimeSpan FadeIn = TimeSpan . FromMilliseconds ( 160 ) ;
3131
32+ // Number of overlay dialogs currently on screen. Mobile dialogs live in the
33+ // TopLevel.OverlayLayer; the dim Border does not reliably intercept taps on
34+ // the bottom nav, so the shell gates navigation on this instead. Desktop
35+ // uses real modal Windows (ShowDialog) and never goes through here.
36+ private static int _activeCount ;
37+
38+ /// <summary>True while at least one overlay (mobile modal) dialog is open.</summary>
39+ public static bool IsAnyOpen => _activeCount > 0 ;
40+
41+ /// <summary>Raised on the UI thread whenever <see cref="IsAnyOpen"/> may have changed.</summary>
42+ public static event Action ? ActiveChanged ;
43+
3244 public static async Task < TResult > ShowAsync < TResult > ( Control content , Task < TResult > completion )
3345 {
3446 var overlay = GetOverlayLayer ( ) ;
@@ -82,6 +94,8 @@ public static async Task<TResult> ShowAsync<TResult>(Control content, Task<TResu
8294 } ;
8395
8496 overlay . Children . Add ( dim ) ;
97+ _activeCount ++ ;
98+ ActiveChanged ? . Invoke ( ) ;
8599 // Kick the opacity transition after the first layout pass — set
86100 // synchronously the Avalonia renderer treats it as initial state
87101 // and skips the animation.
@@ -94,6 +108,8 @@ public static async Task<TResult> ShowAsync<TResult>(Control content, Task<TResu
94108 finally
95109 {
96110 overlay . Children . Remove ( dim ) ;
111+ _activeCount -- ;
112+ ActiveChanged ? . Invoke ( ) ;
97113 }
98114 }
99115
0 commit comments