From c3f772f0da35dd16589c993835a5e1da1824d53d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Jun 2023 18:29:34 +0900 Subject: [PATCH 01/13] Add method to queue a restart after app is exited (when supported) --- osu.Desktop/OsuGameDesktop.cs | 20 +++++++++++++++++++ .../Screens/Setup/TournamentSwitcher.cs | 6 +++++- osu.Game/OsuGameBase.cs | 6 ++++++ .../Sections/Graphics/RendererSettings.cs | 13 +++++++++--- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 21cea3ba76..efd3d358b7 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -17,6 +17,7 @@ using osu.Game.Updater; using osu.Desktop.Windows; using osu.Game.IO; using osu.Game.IPC; +using osu.Game.Online.Multiplayer; using osu.Game.Utils; using SDL2; @@ -108,6 +109,25 @@ namespace osu.Desktop } } + public override bool RestartAppWhenExited() + { + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + Debug.Assert(OperatingSystem.IsWindows()); + + // Of note, this is an async method in squirrel that adds an arbitrary delay before returning + // likely to ensure the external process is in a good state. + // + // We're not waiting on that here, but the outro playing before the actual exit should be enough + // to cover this. + Squirrel.UpdateManager.RestartAppWhenExited().FireAndForget(); + return true; + } + + return base.RestartAppWhenExited(); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs index 7a8b03a7aa..01d86274d7 100644 --- a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs +++ b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs @@ -28,7 +28,11 @@ namespace osu.Game.Tournament.Screens.Setup dropdown.Items = storage.ListTournaments(); dropdown.Current.BindValueChanged(v => Button.Enabled.Value = v.NewValue != startupTournament, true); - Action = () => game.AttemptExit(); + Action = () => + { + game.RestartAppWhenExited(); + game.AttemptExit(); + }; folderButton.Action = () => storage.PresentExternally(); ButtonText = "Close osu!"; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 63efe0e2c8..6737caa5f9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -515,6 +515,12 @@ namespace osu.Game Scheduler.AddDelayed(AttemptExit, 2000); } + /// + /// If supported by the platform, the game will automatically restart after the next exit. + /// + /// Whether a restart operation was queued. + public virtual bool RestartAppWhenExited() => false; + public bool Migrate(string path) { Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index a1f728ca87..d4cef3f4d1 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -67,10 +67,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics if (r.NewValue == RendererType.Automatic && automaticRendererInUse) return; - dialogOverlay?.Push(new ConfirmDialog(GraphicsSettingsStrings.ChangeRendererConfirmation, () => game?.AttemptExit(), () => + if (game?.RestartAppWhenExited() == true) { - renderer.Value = automaticRendererInUse ? RendererType.Automatic : host.ResolvedRenderer; - })); + game.AttemptExit(); + } + else + { + dialogOverlay?.Push(new ConfirmDialog(GraphicsSettingsStrings.ChangeRendererConfirmation, () => game?.AttemptExit(), () => + { + renderer.Value = automaticRendererInUse ? RendererType.Automatic : host.ResolvedRenderer; + })); + } }); // TODO: remove this once we support SDL+android. From 59b1f08d530fa1fa254a102ac678d0f34830da6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Jun 2023 18:27:29 +0900 Subject: [PATCH 02/13] Don't require exit confirmation when there are no ongoing operations that could be interrupted --- osu.Game/OsuGame.cs | 16 +++++++++++++--- osu.Game/Overlays/NotificationOverlay.cs | 2 ++ osu.Game/Screens/Menu/MainMenu.cs | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a80639d4ff..7bfe88f248 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -83,7 +83,7 @@ namespace osu.Game /// protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f; - public Toolbar Toolbar; + public Toolbar Toolbar { get; private set; } private ChatOverlay chatOverlay; @@ -778,8 +778,18 @@ namespace osu.Game public override void AttemptExit() { - // Using PerformFromScreen gives the user a chance to interrupt the exit process if needed. - PerformFromScreen(menu => menu.Exit()); + bool requiresConfirmationToExit = Notifications.HasOngoingOperations; + + PerformFromScreen(menu => + { + var mainMenu = ((MainMenu)menu); + + // The main menu exit implementation gives the user a chance to interrupt the exit process if needed. + if (requiresConfirmationToExit) + mainMenu.Exit(); + else + mainMenu.ExitWithoutConfirmation(); + }, new[] { typeof(MainMenu) }); } /// diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 15e6c94b34..c53b6b667c 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -33,6 +33,8 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; + public bool HasOngoingOperations => sections.Any(s => s.Children.OfType().Any()); + private FlowContainer sections = null!; [Resolved] diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 69b8596474..998e36c2be 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -154,6 +154,8 @@ namespace osu.Game.Screens.Menu public void ReturnToOsuLogo() => Buttons.State = ButtonSystemState.Initial; + public void ExitWithoutConfirmation() => confirmAndExit(); + private void confirmAndExit() { if (exitConfirmed) return; From 11a97e1bb88f39571333837385f442ceb21a7b47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Jun 2023 19:06:47 +0900 Subject: [PATCH 03/13] Move confirmation bypass implementation to `MainMenu` to allow for more correct logic --- .../Visual/Menus/TestSceneToolbar.cs | 2 + .../TestSceneFirstRunSetupOverlay.cs | 2 + osu.Game/OsuGame.cs | 14 +----- osu.Game/Overlays/INotificationOverlay.cs | 5 ++ osu.Game/Screens/Menu/MainMenu.cs | 47 ++++++++++++++----- 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 22c7bb64b2..471700988c 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -250,6 +250,8 @@ namespace osu.Game.Tests.Visual.Menus } public virtual IBindable UnreadCount => null; + + public bool HasOngoingOperations => false; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 77ed97e3ed..0b0c29acf6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -214,6 +214,8 @@ namespace osu.Game.Tests.Visual.UserInterface } public virtual IBindable UnreadCount => null; + + public bool HasOngoingOperations => false; } // interface mocks break hot reload, mocking this stub implementation instead works around it. diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7bfe88f248..160dc26790 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -778,18 +778,8 @@ namespace osu.Game public override void AttemptExit() { - bool requiresConfirmationToExit = Notifications.HasOngoingOperations; - - PerformFromScreen(menu => - { - var mainMenu = ((MainMenu)menu); - - // The main menu exit implementation gives the user a chance to interrupt the exit process if needed. - if (requiresConfirmationToExit) - mainMenu.Exit(); - else - mainMenu.ExitWithoutConfirmation(); - }, new[] { typeof(MainMenu) }); + // The main menu exit implementation gives the user a chance to interrupt the exit process if needed. + PerformFromScreen(menu => menu.Exit(), new[] { typeof(MainMenu) }); } /// diff --git a/osu.Game/Overlays/INotificationOverlay.cs b/osu.Game/Overlays/INotificationOverlay.cs index b9ac466229..0d8f73a1d7 100644 --- a/osu.Game/Overlays/INotificationOverlay.cs +++ b/osu.Game/Overlays/INotificationOverlay.cs @@ -30,5 +30,10 @@ namespace osu.Game.Overlays /// Current number of unread notifications. /// IBindable UnreadCount { get; } + + /// + /// Whether there are any ongoing operations, such as imports or downloads. + /// + bool HasOngoingOperations { get; } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 998e36c2be..5078751823 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -53,6 +53,9 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } + [Resolved] + private INotificationOverlay notifications { get; set; } + [Resolved] private MusicController musicController { get; set; } @@ -74,6 +77,9 @@ namespace osu.Game.Screens.Menu private ExitConfirmOverlay exitConfirmOverlay; + private bool exitConfirmedViaDialog; + private bool exitConfirmedViaHoldOrClick; + private ParallaxContainer buttonsContainer; private SongTicker songTicker; @@ -89,10 +95,8 @@ namespace osu.Game.Screens.Menu { Action = () => { - if (holdDelay.Value > 0) - confirmAndExit(); - else - this.Exit(); + exitConfirmedViaHoldOrClick = holdDelay.Value > 0; + this.Exit(); } }); } @@ -114,7 +118,11 @@ namespace osu.Game.Screens.Menu OnSolo = loadSoloSongSelect, OnMultiplayer = () => this.Push(new Multiplayer()), OnPlaylists = () => this.Push(new Playlists()), - OnExit = confirmAndExit, + OnExit = () => + { + exitConfirmedViaHoldOrClick = true; + this.Exit(); + } } } }, @@ -154,13 +162,11 @@ namespace osu.Game.Screens.Menu public void ReturnToOsuLogo() => Buttons.State = ButtonSystemState.Initial; - public void ExitWithoutConfirmation() => confirmAndExit(); - private void confirmAndExit() { - if (exitConfirmed) return; + if (exitConfirmedViaDialog) return; - exitConfirmed = true; + exitConfirmedViaDialog = true; performer?.PerformFromScreen(menu => menu.Exit()); } @@ -203,8 +209,6 @@ namespace osu.Game.Screens.Menu dialogOverlay?.Push(new StorageErrorDialog(osuStorage, osuStorage.Error)); } - private bool exitConfirmed; - protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); @@ -281,12 +285,29 @@ namespace osu.Game.Screens.Menu public override bool OnExiting(ScreenExitEvent e) { - if (!exitConfirmed && dialogOverlay != null) + bool requiresConfirmation = + // we need to have a dialog overlay to confirm in the first place. + dialogOverlay != null + // if the dialog has already displayed and been accepted by the user, we are good. + && !exitConfirmedViaDialog + // Only require confirmation if there is either an ongoing operation or the user exited via a non-hold escape press. + && (notifications.HasOngoingOperations || !exitConfirmedViaHoldOrClick); + + if (requiresConfirmation) { if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog) exitDialog.PerformOkAction(); else - dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); + { + dialogOverlay.Push(new ConfirmExitDialog(() => + { + exitConfirmedViaDialog = true; + this.Exit(); + }, () => + { + exitConfirmOverlay.Abort(); + })); + } return true; } From 6df617d5366b3a8f15ecaa03c9ce52e1d97f71eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Jun 2023 14:46:38 +0900 Subject: [PATCH 04/13] Rename `ExitConfirmOverlay` to be more explicit about purpose --- .../Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs | 4 ++-- .../{ExitConfirmOverlay.cs => HoldToExitGameOverlay.cs} | 6 ++---- osu.Game/Screens/Menu/MainMenu.cs | 8 ++++---- 3 files changed, 8 insertions(+), 10 deletions(-) rename osu.Game/Screens/Menu/{ExitConfirmOverlay.cs => HoldToExitGameOverlay.cs} (86%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs index 801bef62c8..9c29f17382 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface Alpha = 0, }; - var overlay = new TestHoldToConfirmOverlay + var overlay = new TestHoldToExitGameOverlay { Action = () => { @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait until fired again", () => overlay.Fired); } - private partial class TestHoldToConfirmOverlay : ExitConfirmOverlay + private partial class TestHoldToExitGameOverlay : HoldToExitGameOverlay { public void Begin() => BeginConfirm(); } diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/HoldToExitGameOverlay.cs similarity index 86% rename from osu.Game/Screens/Menu/ExitConfirmOverlay.cs rename to osu.Game/Screens/Menu/HoldToExitGameOverlay.cs index bc2f6ea00f..a8f4913368 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/HoldToExitGameOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; @@ -10,13 +8,13 @@ using osu.Game.Overlays; namespace osu.Game.Screens.Menu { - public partial class ExitConfirmOverlay : HoldToConfirmOverlay, IKeyBindingHandler + public partial class HoldToExitGameOverlay : HoldToConfirmOverlay, IKeyBindingHandler { protected override bool AllowMultipleFires => true; public void Abort() => AbortConfirm(); - public ExitConfirmOverlay() + public HoldToExitGameOverlay() : base(0.7f) { } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 5078751823..2946425998 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Menu private Bindable holdDelay; private Bindable loginDisplayed; - private ExitConfirmOverlay exitConfirmOverlay; + private HoldToExitGameOverlay holdToExitGameOverlay; private bool exitConfirmedViaDialog; private bool exitConfirmedViaHoldOrClick; @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Menu if (host.CanExit) { - AddInternal(exitConfirmOverlay = new ExitConfirmOverlay + AddInternal(holdToExitGameOverlay = new HoldToExitGameOverlay { Action = () => { @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.TopRight, Margin = new MarginPadding { Right = 15, Top = 5 } }, - exitConfirmOverlay?.CreateProxy() ?? Empty() + holdToExitGameOverlay?.CreateProxy() ?? Empty() }); Buttons.StateChanged += state => @@ -305,7 +305,7 @@ namespace osu.Game.Screens.Menu this.Exit(); }, () => { - exitConfirmOverlay.Abort(); + holdToExitGameOverlay.Abort(); })); } From 20aedc82ac674b441ee01c31d5b76cf32232564d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Jun 2023 14:47:32 +0900 Subject: [PATCH 05/13] Remove unused code --- osu.Game/Screens/Menu/MainMenu.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 2946425998..f55c44d427 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -157,19 +157,8 @@ namespace osu.Game.Screens.Menu preloadSongSelect(); } - [Resolved(canBeNull: true)] - private IPerformFromScreenRunner performer { get; set; } - public void ReturnToOsuLogo() => Buttons.State = ButtonSystemState.Initial; - private void confirmAndExit() - { - if (exitConfirmedViaDialog) return; - - exitConfirmedViaDialog = true; - performer?.PerformFromScreen(menu => menu.Exit()); - } - private void preloadSongSelect() { if (songSelect == null) From 7fa07805b0bffb300ff1e071868294d3fb7d16cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Jun 2023 14:58:19 +0900 Subject: [PATCH 06/13] Expose all notifications from `INotificationOverlay` Also fixes `HasOngoingOperations` not actually working. --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 3 ++- .../UserInterface/TestSceneFirstRunSetupOverlay.cs | 2 +- osu.Game/Overlays/INotificationOverlay.cs | 14 +++++++++++++- osu.Game/Overlays/NotificationOverlay.cs | 4 +++- osu.Game/Overlays/NotificationOverlayToastTray.cs | 5 +++++ .../Overlays/Notifications/NotificationSection.cs | 5 +++++ 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 471700988c..ce9f80a84f 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Moq; @@ -251,7 +252,7 @@ namespace osu.Game.Tests.Visual.Menus public virtual IBindable UnreadCount => null; - public bool HasOngoingOperations => false; + public IEnumerable AllNotifications => Enumerable.Empty(); } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 0b0c29acf6..9275f9755f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.UserInterface public virtual IBindable UnreadCount => null; - public bool HasOngoingOperations => false; + public IEnumerable AllNotifications => Enumerable.Empty(); } // interface mocks break hot reload, mocking this stub implementation instead works around it. diff --git a/osu.Game/Overlays/INotificationOverlay.cs b/osu.Game/Overlays/INotificationOverlay.cs index 0d8f73a1d7..1dd6dd2c6c 100644 --- a/osu.Game/Overlays/INotificationOverlay.cs +++ b/osu.Game/Overlays/INotificationOverlay.cs @@ -3,6 +3,8 @@ #nullable disable +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Overlays.Notifications; @@ -34,6 +36,16 @@ namespace osu.Game.Overlays /// /// Whether there are any ongoing operations, such as imports or downloads. /// - bool HasOngoingOperations { get; } + public bool HasOngoingOperations => OngoingOperations.Any(); + + /// + /// All current displayed notifications, whether in the toast tray or a section. + /// + IEnumerable AllNotifications { get; } + + /// + /// All ongoing operations (ie. any not in a completed state). + /// + public IEnumerable OngoingOperations => AllNotifications.OfType().Where(p => p.State != ProgressNotificationState.Completed); } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index c53b6b667c..6ef93fac9e 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -33,7 +34,8 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - public bool HasOngoingOperations => sections.Any(s => s.Children.OfType().Any()); + public IEnumerable AllNotifications => + toastTray.Notifications.Concat(sections.SelectMany(s => s.Notifications)); private FlowContainer sections = null!; diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 7a793ee092..0ebaff9437 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -28,6 +28,11 @@ namespace osu.Game.Overlays public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => toastFlow.ReceivePositionalInputAt(screenSpacePos); + /// + /// All notifications currently being displayed by the toast tray. + /// + public IEnumerable Notifications => toastFlow; + public bool IsDisplayingToasts => toastFlow.Count > 0; private FillFlowContainer toastFlow = null!; diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index de4c72e473..4e28ade802 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -19,6 +19,11 @@ namespace osu.Game.Overlays.Notifications { public partial class NotificationSection : AlwaysUpdateFillFlowContainer { + /// + /// All notifications currently being displayed in this section. + /// + public IEnumerable Notifications => notifications; + private OsuSpriteText countDrawable = null!; private FlowContainer notifications = null!; From 693b7c99067c64f6daa10da3300041a0be0a91b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Jun 2023 15:19:47 +0900 Subject: [PATCH 07/13] Reorganise resolved fields in `MainMenu` --- osu.Game/Screens/Menu/MainMenu.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index f55c44d427..f34a1954a5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -59,12 +59,15 @@ namespace osu.Game.Screens.Menu [Resolved] private MusicController musicController { get; set; } - [Resolved(canBeNull: true)] - private LoginOverlay login { get; set; } - [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private Storage storage { get; set; } + + [Resolved(canBeNull: true)] + private LoginOverlay login { get; set; } + [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } @@ -174,9 +177,6 @@ namespace osu.Game.Screens.Menu return s; } - [Resolved] - private Storage storage { get; set; } - public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); From f66b787b12d5f0a449d3d0c1f8bf497d15c7a216 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Jun 2023 15:20:19 +0900 Subject: [PATCH 08/13] Show ongoing operations in exit confirmation dialog Also changes the button to a dangerous button, forcing user acknowledgement --- osu.Game/Screens/Menu/ConfirmExitDialog.cs | 63 ++++++++++++++++++---- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs index 4906232d21..fb22f7eff8 100644 --- a/osu.Game/Screens/Menu/ConfirmExitDialog.cs +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.cs @@ -2,38 +2,79 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Menu { public partial class ConfirmExitDialog : PopupDialog { + private readonly Action onConfirm; + private readonly Action? onCancel; + /// /// Construct a new exit confirmation dialog. /// /// An action to perform on confirmation. /// An optional action to perform on cancel. public ConfirmExitDialog(Action onConfirm, Action? onCancel = null) + { + this.onConfirm = onConfirm; + this.onCancel = onCancel; + } + + [BackgroundDependencyLoader] + private void load(INotificationOverlay notifications) { HeaderText = "Are you sure you want to exit osu!?"; - BodyText = "Last chance to turn back"; Icon = FontAwesome.Solid.ExclamationTriangle; - Buttons = new PopupDialogButton[] + if (notifications.HasOngoingOperations) { - new PopupDialogOkButton + string text = "There are currently some background operations which will be aborted if you continue:\n\n"; + + foreach (var n in notifications.OngoingOperations) + text += $"{n.Text} ({n.Progress:0%})\n"; + + text += "\nLast chance to turn back"; + + BodyText = text; + + Buttons = new PopupDialogButton[] { - Text = @"Let me out!", - Action = onConfirm - }, - new PopupDialogCancelButton + new PopupDialogDangerousButton + { + Text = @"Let me out!", + Action = onConfirm + }, + new PopupDialogCancelButton + { + Text = @"Cancel", + Action = onCancel + }, + }; + } + else + { + BodyText = "Last chance to turn back"; + + Buttons = new PopupDialogButton[] { - Text = @"Just a little more...", - Action = onCancel - }, - }; + new PopupDialogOkButton + { + Text = @"Let me out!", + Action = onConfirm + }, + new PopupDialogCancelButton + { + Text = @"Just a little more...", + Action = onCancel + }, + }; + } } } } From a76037b6433951f8e5dbe5ab7d6534edb5f37a4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Jun 2023 15:30:21 +0900 Subject: [PATCH 09/13] Add test coverage of confirm-for-operations --- .../Navigation/TestSceneScreenNavigation.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 18aef99ccd..c3b668f591 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -22,6 +22,7 @@ using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -683,6 +684,44 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("center cursor", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); } + [Test] + public void TestExitWithOperationInProgress() + { + AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); + + ProgressNotification progressNotification = null!; + + AddStep("start ongoing operation", () => + { + progressNotification = new ProgressNotification + { + Text = "Something is still running", + Progress = 0.5f, + State = ProgressNotificationState.Active, + }; + Game.Notifications.Post(progressNotification); + }); + + AddStep("Hold escape", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("confirmation dialog shown", () => Game.ChildrenOfType().Single().CurrentDialog is ConfirmExitDialog); + AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape)); + + AddStep("cancel exit", () => InputManager.Key(Key.Escape)); + AddAssert("dialog dismissed", () => Game.ChildrenOfType().Single().CurrentDialog == null); + + AddStep("complete operation", () => + { + progressNotification.Progress = 100; + progressNotification.State = ProgressNotificationState.Completed; + }); + + AddStep("Hold escape", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroScreen); + AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape)); + + AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null); + } + [Test] public void TestExitGameFromSongSelect() { @@ -699,7 +738,7 @@ namespace osu.Game.Tests.Visual.Navigation } [Test] - public void TestRapidBackButtonExit() + public void TestExitWithHoldDisabled() { AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0)); @@ -711,7 +750,7 @@ namespace osu.Game.Tests.Visual.Navigation pushEscape(); - AddAssert("exit dialog is shown", () => Game.Dependencies.Get().CurrentDialog != null); + AddAssert("exit dialog is shown", () => Game.Dependencies.Get().CurrentDialog is ConfirmExitDialog); } private Func playToResults() From 95e8dd2e8ef27669bf9a19a326fde1d6397c35f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Jun 2023 21:25:07 +0900 Subject: [PATCH 10/13] Don't attempt to access notifications before loaded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Overlays/NotificationOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 28803fe590..f1e39b947d 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; public IEnumerable AllNotifications => - toastTray.Notifications.Concat(sections.SelectMany(s => s.Notifications)); + IsLoaded ? toastTray.Notifications.Concat(sections.SelectMany(s => s.Notifications)) : Array.Empty(); private FlowContainer sections = null!; From 3d1a8aeb540799e3d62fc915805b3dd3678beb69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Jun 2023 21:24:39 +0900 Subject: [PATCH 11/13] Use more understandable cancel button text --- osu.Game/Screens/Menu/ConfirmExitDialog.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs index fb22f7eff8..0041d047bd 100644 --- a/osu.Game/Screens/Menu/ConfirmExitDialog.cs +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -52,7 +53,7 @@ namespace osu.Game.Screens.Menu }, new PopupDialogCancelButton { - Text = @"Cancel", + Text = CommonStrings.Back, Action = onCancel }, }; From cf43cd2bdcc165eca96d7b569b2f26a617e049bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Jun 2023 21:26:08 +0900 Subject: [PATCH 12/13] Rename test scene to match updated class name --- ...dToConfirmOverlay.cs => TestSceneHoldToExitGameOverlay.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneHoldToConfirmOverlay.cs => TestSceneHoldToExitGameOverlay.cs} (94%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToExitGameOverlay.cs similarity index 94% rename from osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneHoldToExitGameOverlay.cs index 58d1637ca7..df423268b6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToExitGameOverlay.cs @@ -8,11 +8,11 @@ using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.UserInterface { - public partial class TestSceneHoldToConfirmOverlay : OsuTestScene + public partial class TestSceneHoldToExitGameOverlay : OsuTestScene { protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms - public TestSceneHoldToConfirmOverlay() + public TestSceneHoldToExitGameOverlay() { bool fired = false; From 4215ca313f17f147d3b0ca2d2feaeb04abf062bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Jun 2023 14:36:21 +0200 Subject: [PATCH 13/13] Add missing using --- osu.Game/Overlays/NotificationOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index f1e39b947d..21027b0931 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation;