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
6 changes: 3 additions & 3 deletions WheelWizard.Test/Views/Shell/ViewModels/HomeViewModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ public async Task OverlappingClicks_StartOneOperation_AndRestoreInteraction()
await fixture.Model.ExecuteMainAsync();
Assert.True(fixture.Model.IsBusy);
Assert.False(fixture.Model.CanExecuteMain);
fixture.Presentation.Received(1).SetApplicationInteractable(false);
fixture.Presentation.Received(1).SetCloseGuarded(true);
install.SetResult(Ok());
await first;

await fixture.Launcher.Received(1).Install();
fixture.Presentation.Received(1).SetApplicationInteractable(true);
fixture.Presentation.Received(1).SetCloseGuarded(false);
Assert.False(fixture.Model.IsBusy);
Assert.True(fixture.Model.IsInteractable);
}
Expand All @@ -62,7 +62,7 @@ public async Task ThrowingUpdate_RestoresNavigationAndShowsError()
await fixture.Model.ExecuteMainAsync();

fixture.Presentation.Received(1).ShowError(Arg.Is<OperationError>(error => error.Exception is IOException));
fixture.Presentation.Received(1).SetApplicationInteractable(true);
fixture.Presentation.Received(1).SetCloseGuarded(false);
Assert.True(fixture.Model.CanExecuteMain);
}

Expand Down
53 changes: 53 additions & 0 deletions WheelWizard.UI.Test/ApplicationCompositionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,47 @@ public async Task MainWindowAndPages_ConstructWithoutStaticServiceInitialization
Assert.Equal(LocalizationProvider.Translate("component.dropdown.search_placeholder"), languages.SearchPlaceholder);
Assert.Equal(LocalizationProvider.Translate("component.dropdown.empty"), languages.EmptyText);
}

// Closing during setup asks first, and the question stays answerable.
var onClosing = typeof(Layout).GetMethod(
"OnClosing",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic
)!;
original.SetCloseGuarded(true);
var guardedClose = WindowCompositionTests.CreateClosingArgs(false);
onClosing.Invoke(original, [guardedClose]);
Assert.True(guardedClose.Cancel);
var quitQuestion = Assert.IsType<YesNoWindow>(original.Modals.TopModal);
Assert.True(quitQuestion.IsEffectivelyEnabled);
var quitClosed = quitQuestion.ShowDialog();
quitQuestion.FindControl<ActionButton>("NoButton")!.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
await quitClosed.WaitAsync(TimeSpan.FromSeconds(2));
await Dispatcher.UIThread.InvokeAsync(() => { }, DispatcherPriority.Background);
Assert.True(original.IsVisible);
var programmaticClose = WindowCompositionTests.CreateClosingArgs(true);
onClosing.Invoke(original, [programmaticClose]);
Assert.False(programmaticClose.Cancel);
original.SetCloseGuarded(false);
var unguardedClose = WindowCompositionTests.CreateClosingArgs(false);
onClosing.Invoke(original, [unguardedClose]);
Assert.False(unguardedClose.Cancel);
Assert.Null(original.Modals.TopModal);

// Quitting during setup still asks a page with unsaved work.
navigation.NavigateTo<GuardedTestPage>();
var guardedPage = Assert.IsType<GuardedTestPage>(navigation.CurrentPage);
original.SetCloseGuarded(true);
onClosing.Invoke(original, [WindowCompositionTests.CreateClosingArgs(false)]);
var setupQuestion = Assert.IsType<YesNoWindow>(original.Modals.TopModal);
var setupClosed = setupQuestion.ShowDialog();
setupQuestion.FindControl<ActionButton>("YesButton")!.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
await setupClosed.WaitAsync(TimeSpan.FromSeconds(2));
await Dispatcher.UIThread.InvokeAsync(() => { }, DispatcherPriority.Background);
Assert.Equal(1, guardedPage.ConfirmCount);
Assert.True(original.IsVisible);
original.SetCloseGuarded(false);
guardedPage.HasUnsavedWork = false;

Assert.Equal(Avalonia.Controls.ShutdownMode.OnMainWindowClose, desktop.ShutdownMode);
desktop.DidNotReceive().Shutdown(Arg.Any<int>());
}
Expand All @@ -229,6 +270,18 @@ public async Task MainWindowAndPages_ConstructWithoutStaticServiceInitialization
}
}

public sealed class GuardedTestPage : UserControl, INavigationGuard
{
public bool HasUnsavedWork { get; set; } = true;
public int ConfirmCount { get; private set; }

public Task<bool> ConfirmLeaveAsync()
{
ConfirmCount++;
return Task.FromResult(false);
}
}

public sealed class OfflineApiCaller<TApi> : IApiCaller<TApi>
where TApi : class
{
Expand Down
6 changes: 1 addition & 5 deletions WheelWizard.UI.Test/InAppModalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -525,12 +525,9 @@ private sealed class ModalTestWindow : BaseWindow, IDisposable

public void RequestNativeClose(WindowClosingEventArgs args) => base.OnClosing(args);

private readonly Border _overlay = new();
public Button BackgroundButton { get; } = new() { Content = "Background" };
public bool CanInteract => _body.IsEnabled;
protected override Control InteractionContent => _frame;
protected override Control InteractionOverlay => _overlay;
protected override Control ModalInteractionContent => _body;
protected override Control InteractionContent => _body;

public ModalTestWindow()
{
Expand All @@ -544,7 +541,6 @@ public ModalTestWindow()
_body.Children.Add(BackgroundButton);
Grid.SetRow(_body, 1);
_frame.Children.Add(_body);
_frame.Children.Add(_overlay);
Content = _frame;
}
}
Expand Down
4 changes: 1 addition & 3 deletions WheelWizard.UI.Test/ModDetailsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,13 @@ private sealed class TestWindow : BaseWindow, IDisposable
public void Dispose() => Close();

private readonly Grid _body = new();
private readonly Border _overlay = new();
protected override Control InteractionContent => _body;
protected override Control InteractionOverlay => _overlay;

public TestWindow()
{
Width = 656;
Height = 876;
Content = new Grid { Children = { _body, _overlay } };
Content = new Grid { Children = { _body } };
}
}
}
4 changes: 1 addition & 3 deletions WheelWizard.UI.Test/UiTestHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ namespace WheelWizard.UI.Test;
internal sealed class UiTestHost : BaseWindow, IDisposable
{
public Border Body { get; } = new();
private readonly Border _overlay = new();
protected override Control InteractionContent => Body;
protected override Control InteractionOverlay => _overlay;

public UiTestHost()
{
Width = 656;
Height = 876;
Content = new Grid { Children = { Body, _overlay } };
Content = new Grid { Children = { Body } };
}

public void Dispose() => Close();
Expand Down
25 changes: 1 addition & 24 deletions WheelWizard.UI.Test/WindowCompositionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,25 +228,6 @@ public async Task ProgressDismissal_DoesNotCancelButExplicitCancelDoes()
Assert.True(owner.CanInteract);
}

[AvaloniaFact]
public void DisabledWindow_RejectsNativeCloseButAllowsCompletionAndShutdown()
{
using var window = new TestWindow();
window.SetInteractable(false);
var native = CreateClosingArgs(false);
window.RequestClose(native);
Assert.True(native.Cancel);
foreach (var args in new[] { CreateClosingArgs(true), CreateClosingArgs(false, WindowCloseReason.ApplicationShutdown) })
{
window.RequestClose(args);
Assert.False(args.Cancel);
}
window.SetInteractable(true);
var allowed = CreateClosingArgs(false);
window.RequestClose(allowed);
Assert.False(allowed.Cancel);
}

[AvaloniaFact]
public void MessagePopup_GrowsForLateContentWithoutEnlargingItsTitle()
{
Expand Down Expand Up @@ -361,7 +342,7 @@ public async Task PlayerProfilePopup_FitsLateProfileAndHistoryWithoutScalingCont
Assert.False(owner.CanInteract);
}

private static WindowClosingEventArgs CreateClosingArgs(
internal static WindowClosingEventArgs CreateClosingArgs(
bool programmatic,
WindowCloseReason reason = WindowCloseReason.WindowClosing
) =>
Expand All @@ -378,11 +359,7 @@ private sealed class TestWindow : BaseWindow, IDisposable
{
public void Dispose() => Close();

public void RequestClose(WindowClosingEventArgs args) => OnClosing(args);

private readonly Border _overlay = new();
private readonly Border _content = new();
protected override Control InteractionOverlay => _overlay;
protected override Control InteractionContent => _content;
public bool CanInteract => _content.IsEnabled;

Expand Down
5 changes: 5 additions & 0 deletions WheelWizard/Resources/Languages/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ en:
redo: "Redo"
leave: "Leave"
keep_editing: "Keep editing"
quit: "Quit"
keep_installing: "Keep installing"
surprise_me: "Surprise me"
unfavorite: "Unfavorite"
favorite: "Favorite"
Expand Down Expand Up @@ -340,6 +342,9 @@ en:
leave_mii_editor:
title: "Leave without saving?"
extra: "Your changes to this Mii will be lost."
quit_during_setup:
title: "Quit while installing?"
extra: "An install or update is still running. Quitting now can leave the game incomplete."
recomp_uninstall:
title: "Uninstall WiiCompiled?"
extra_shared: "This removes the WiiCompiled installation. Your saves stay in Dolphin's Wii folder, and Retro Rewind stays installed."
Expand Down
8 changes: 3 additions & 5 deletions WheelWizard/Views/DesignTime/DevToolWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,12 @@

<components:ActionButton Grid.Row="0" Grid.Column="1" Tone="Secondary" Text="Show Mii Animator" HorizontalAlignment="Stretch" Margin="0,2"
Click="ShowMiiAnimator_OnClick" />
<components:ActionButton Grid.Row="1" Grid.Column="0" Tone="Secondary" Text="Force enable layout" HorizontalAlignment="Stretch" Margin="0,2"
Click="ForceEnableLayout_OnClick" />
<components:ActionButton Grid.Row="1" Grid.Column="1" Tone="Secondary" Text="Kitchen Sink"
<components:ActionButton Grid.Row="1" Grid.Column="0" Tone="Secondary" Text="Kitchen Sink"
IconData="{StaticResource Sink}" HorizontalAlignment="Stretch" Margin="0,2"
Click="KitchenSink_OnClick" />
<components:ActionButton Grid.Row="2" Grid.Column="0" Tone="Secondary" Text="Clear Cache" HorizontalAlignment="Stretch" Margin="0,2"
<components:ActionButton Grid.Row="1" Grid.Column="1" Tone="Secondary" Text="Clear Cache" HorizontalAlignment="Stretch" Margin="0,2"
Click="ClearCache_OnClick" />
<components:ActionButton Grid.Row="2" Grid.Column="1" Tone="Primary" Text="Hide all dev things" HorizontalAlignment="Stretch" Margin="0,2"
<components:ActionButton Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Tone="Primary" Text="Hide all dev things" HorizontalAlignment="Stretch" Margin="0,2"
Click="HideDevelopmentFeatures_OnClick" />
</Grid>
<Border Background="{StaticResource BackgroundLineColor}" Margin="-10,10" Height="1"
Expand Down
2 changes: 0 additions & 2 deletions WheelWizard/Views/DesignTime/DevToolWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ private void SuccessStatusIconOverride_OnClick(object sender, RoutedEventArgs e)

private void ShowMiiAnimator_OnClick(object sender, RoutedEventArgs e) => Popups.Create<MiiAnimatorDevWindow>().Show();

private void ForceEnableLayout_OnClick(object sender, RoutedEventArgs e) => ViewUtils.GetLayout().SetInteractable(true);

private void ClearCache_OnClick(object sender, RoutedEventArgs e) => ((MemoryCache)Cache).Clear();

private void KitchenSink_OnClick(object sender, RoutedEventArgs e)
Expand Down
34 changes: 4 additions & 30 deletions WheelWizard/Views/Shell/BaseWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ public abstract class BaseWindow : Window

internal static BaseWindow GetModalOwner()
{
var foreground = OpenWindows.LastOrDefault(window => window.IsActive && window.IsVisible && window.Modals.IsEnabled);
var foreground = OpenWindows.LastOrDefault(window => window.IsActive && window.IsVisible);
if (foreground != null)
return foreground;
var current = OpenWindows.LastOrDefault(window => window.IsVisible && window.Modals.IsEnabled);
var current = OpenWindows.LastOrDefault(window => window.IsVisible);
if (current != null)
return current;
if (
Expand Down Expand Up @@ -57,11 +57,7 @@ private void InstallModalHost()

internal void UpdateModalInteraction()
{
InteractionOverlay.IsVisible = _disableCount != 0;
InteractionContent.IsEnabled = _disableCount == 0;
ModalInteractionContent.IsEnabled = _disableCount == 0 && !(_modals?.HasModals ?? false);
if (_modals != null)
_modals.IsEnabled = _disableCount == 0;
InteractionContent.IsEnabled = !(_modals?.HasModals ?? false);
UpdateModalChromeDimming();
}

Expand All @@ -75,14 +71,10 @@ internal void UpdateModalChromeDimming()
}
}

private int _disableCount = 0;
private readonly Action<Size, Size>? _setWaylandSizeConstraints;
private bool _waylandSizeUpdateQueued;

protected abstract Control InteractionOverlay { get; } // Just the visual part of the interaction
protected abstract Control InteractionContent { get; } // The content that will be disabled when the overlay is shown

protected virtual Control ModalInteractionContent => InteractionContent;
protected abstract Control InteractionContent { get; } // The content that will be disabled while a modal is shown

private void InstallMacWindowMenu()
{
Expand Down Expand Up @@ -162,28 +154,10 @@ protected override void OnOpened(EventArgs e)
base.OnOpened(e);
}

protected override void OnClosing(WindowClosingEventArgs e)
{
// Blocking application work still gates native close; in-app modals leave window controls usable.
if (_disableCount != 0 && !e.IsProgrammatic && e.CloseReason == WindowCloseReason.WindowClosing)
e.Cancel = true;
base.OnClosing(e);
}

protected override void OnClosed(EventArgs e)
{
_modals?.CloseAll();
OpenWindows.Remove(this);
base.OnClosed(e);
}

public void SetInteractable(bool value)
{
if (!value)
_disableCount++;
else if (_disableCount > 0)
_disableCount--;

UpdateModalInteraction();
}
}
4 changes: 1 addition & 3 deletions WheelWizard/Views/Shell/Layout.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
xmlns:design="clr-namespace:WheelWizard.Views.DesignTime"
xmlns:miiImages="clr-namespace:WheelWizard.MiiImages.Views"
xmlns:shellControls="clr-namespace:WheelWizard.Views.Shell.Controls">
<Grid x:Name="WindowFrame" RowDefinitions="30,*"
<Grid RowDefinitions="30,*"
Background="{StaticResource FrameColor}">
<Border Grid.Row="0" Background="Transparent"
WindowDecorationProperties.ElementRole="TitleBar"
Expand Down Expand Up @@ -495,7 +495,5 @@
</Grid.Styles>
</Grid>
</Grid>
<Border Grid.RowSpan="2" ZIndex="999" IsVisible="False" IsHitTestVisible="False"
x:Name="DisabledDarkenEffect" Background="Black" Opacity="0.3" />
</Grid>
</Window>
Loading
Loading