Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ public static class OverlayDialogPresenter
{
private static readonly TimeSpan FadeIn = TimeSpan.FromMilliseconds(160);

// Number of overlay dialogs currently on screen. Mobile dialogs live in the
// TopLevel.OverlayLayer; the dim Border does not reliably intercept taps on
// the bottom nav, so the shell gates navigation on this instead. Desktop
// uses real modal Windows (ShowDialog) and never goes through here.
private static int _activeCount;

/// <summary>True while at least one overlay (mobile modal) dialog is open.</summary>
public static bool IsAnyOpen => _activeCount > 0;

/// <summary>Raised on the UI thread whenever <see cref="IsAnyOpen"/> may have changed.</summary>
public static event Action? ActiveChanged;

public static async Task<TResult> ShowAsync<TResult>(Control content, Task<TResult> completion)
{
var overlay = GetOverlayLayer();
Expand Down Expand Up @@ -82,6 +94,8 @@ public static async Task<TResult> ShowAsync<TResult>(Control content, Task<TResu
};

overlay.Children.Add(dim);
_activeCount++;
ActiveChanged?.Invoke();
// Kick the opacity transition after the first layout pass — set
// synchronously the Avalonia renderer treats it as initial state
// and skips the animation.
Expand All @@ -94,6 +108,8 @@ public static async Task<TResult> ShowAsync<TResult>(Control content, Task<TResu
finally
{
overlay.Children.Remove(dim);
_activeCount--;
ActiveChanged?.Invoke();
}
}

Expand Down
12 changes: 11 additions & 1 deletion src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,19 @@ public MainWindowViewModel(

WeakReferenceMessenger.Default.Register<OpenCameraMessage>(this);
WeakReferenceMessenger.Default.Register<GoBackToLibraryMessage>(this);

// While a mobile overlay dialog is open the bottom nav must not switch
// pages under it — the dim layer doesn't reliably swallow those taps.
// Re-evaluate CanNavigate whenever an overlay opens/closes; this greys
// out the nav buttons (Avalonia disables a control whose command can't
// execute). This VM is an app-lifetime singleton, so the static
// subscription lives as long as the process — no unsubscribe needed.
OverlayDialogPresenter.ActiveChanged += () => NavigateCommand.NotifyCanExecuteChanged();
}

[RelayCommand]
private static bool CanNavigate() => !OverlayDialogPresenter.IsAnyOpen;

[RelayCommand(CanExecute = nameof(CanNavigate))]
private void Navigate(string target)
{
if (_activeSingleCamera is not null && target != "library")
Expand Down
Loading