From f6e770efafaa258007ccf2c6a159dea826a7e4d5 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sat, 20 May 2023 17:31:25 +0200 Subject: [PATCH 01/17] feat: implement window flashing --- .../Visual/Platform/TestSceneWindowFlash.cs | 70 +++++++++++++++++++ osu.Framework/Platform/IWindow.cs | 17 +++++ osu.Framework/Platform/OsuTKWindow.cs | 8 +++ osu.Framework/Platform/SDL2Window.cs | 13 ++++ 4 files changed, 108 insertions(+) create mode 100644 osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs new file mode 100644 index 0000000000..353e47fd31 --- /dev/null +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Platform; + +namespace osu.Framework.Tests.Visual.Platform +{ + public partial class TestSceneWindowFlash : FrameworkTestScene + { + private IBindable isActive = null!; + private IWindow window = null!; + private SpriteText text = null!; + private TextFlowContainer behaviourText = null!; + private readonly Bindable flashUntilFocused = new BindableBool(); + + [BackgroundDependencyLoader] + private void load(GameHost gameHost) + { + isActive = gameHost.IsActive.GetBoundCopy(); + window = gameHost.Window; + Child = new FillFlowContainer + { + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + text = new SpriteText + { + Text = "This window will flash as soon as you unfocus it.", + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + behaviourText = new TextFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + AddToggleStep("Flash until focused", a => flashUntilFocused.Value = a); + flashUntilFocused.BindValueChanged(e => + { + window.CancelFlash(); + text.Text = "This window will flash " + (e.NewValue ? "until focused again" : "briefly") + + " as soon as it is unfocused."; + }, true); + isActive.BindValueChanged(e => + { + if (!e.NewValue) + window.Flash(flashUntilFocused.Value); + }, true); + behaviourText.AddParagraph("Behaviour is platform dependent (only for desktops):"); + behaviourText.AddParagraph("- Windows: icon flashes on the taskbar and raises it. Only once if briefly."); + behaviourText.AddParagraph("- MacOS: icon jumps on the Dock and raises it. Only once if briefly."); + behaviourText.AddParagraph("- Linux: depends on DE/WM setup."); + } + } +} diff --git a/osu.Framework/Platform/IWindow.cs b/osu.Framework/Platform/IWindow.cs index 6c6772713b..a5bee8b0d7 100644 --- a/osu.Framework/Platform/IWindow.cs +++ b/osu.Framework/Platform/IWindow.cs @@ -159,6 +159,23 @@ public interface IWindow : IDisposable /// void Raise(); + /// + /// Attempts to flash the window in order to request the user's attention. + /// + /// + /// This behaviour is only available on desktop platforms and is different depending on the OS: + /// - On Windows: flashes the icon on the taskbar and raises it (only once if briefly) + /// - On MacOS: jumps on the Dock and raises it (only once if briefly) + /// - On Linux: depends on the Desktop Environment / Window Manager setup. + /// + /// Flash the window until the window is focused. + void Flash(bool untilFocused = false); + + /// + /// Attempts to cancel any window flash requested with . + /// + void CancelFlash(); + /// /// Start the window's run loop. /// Is a blocking call on desktop platforms, and a non-blocking call on mobile platforms. diff --git a/osu.Framework/Platform/OsuTKWindow.cs b/osu.Framework/Platform/OsuTKWindow.cs index 50292c811b..9d1463fd47 100644 --- a/osu.Framework/Platform/OsuTKWindow.cs +++ b/osu.Framework/Platform/OsuTKWindow.cs @@ -90,6 +90,14 @@ public void Raise() { } + public void Flash(bool _) + { + } + + public void CancelFlash() + { + } + public abstract bool Focused { get; } public abstract IBindable IsActive { get; } diff --git a/osu.Framework/Platform/SDL2Window.cs b/osu.Framework/Platform/SDL2Window.cs index 45bbfc8b3a..02c6d3474e 100644 --- a/osu.Framework/Platform/SDL2Window.cs +++ b/osu.Framework/Platform/SDL2Window.cs @@ -366,6 +366,19 @@ public void Raise() => ScheduleCommand(() => SDL.SDL_RaiseWindow(SDLWindowHandle); }); + public void Flash(bool untilFocused = false) => ScheduleCommand(() => + { + if (isActive.Value) + return; + + SDL.SDL_FlashWindow(SDLWindowHandle, untilFocused + ? SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED + : SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY); + }); + + public void CancelFlash() + => ScheduleCommand(() => SDL.SDL_FlashWindow(SDLWindowHandle, SDL.SDL_FlashOperation.SDL_FLASH_CANCEL)); + /// /// Attempts to set the window's icon to the specified image. /// From 709c3d1d00610915a2de7c4b7eede8b7a86d27ea Mon Sep 17 00:00:00 2001 From: tsrk Date: Sat, 20 May 2023 18:04:55 +0200 Subject: [PATCH 02/17] fix: make IWindow nullable to avoid NRE in test --- osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs index 353e47fd31..09dda6f7f1 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs @@ -13,7 +13,7 @@ namespace osu.Framework.Tests.Visual.Platform public partial class TestSceneWindowFlash : FrameworkTestScene { private IBindable isActive = null!; - private IWindow window = null!; + private IWindow? window; private SpriteText text = null!; private TextFlowContainer behaviourText = null!; private readonly Bindable flashUntilFocused = new BindableBool(); @@ -52,14 +52,14 @@ protected override void LoadComplete() AddToggleStep("Flash until focused", a => flashUntilFocused.Value = a); flashUntilFocused.BindValueChanged(e => { - window.CancelFlash(); + window?.CancelFlash(); text.Text = "This window will flash " + (e.NewValue ? "until focused again" : "briefly") + " as soon as it is unfocused."; }, true); isActive.BindValueChanged(e => { if (!e.NewValue) - window.Flash(flashUntilFocused.Value); + window?.Flash(flashUntilFocused.Value); }, true); behaviourText.AddParagraph("Behaviour is platform dependent (only for desktops):"); behaviourText.AddParagraph("- Windows: icon flashes on the taskbar and raises it. Only once if briefly."); From 92d0aa5ebcf61af215fca6db6ae2439ab4e6630f Mon Sep 17 00:00:00 2001 From: tsrk Date: Sat, 20 May 2023 18:40:33 +0200 Subject: [PATCH 03/17] fix: remove misinfomation regarding MacOS --- osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs | 2 +- osu.Framework/Platform/IWindow.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs index 09dda6f7f1..58b728a655 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs @@ -63,7 +63,7 @@ protected override void LoadComplete() }, true); behaviourText.AddParagraph("Behaviour is platform dependent (only for desktops):"); behaviourText.AddParagraph("- Windows: icon flashes on the taskbar and raises it. Only once if briefly."); - behaviourText.AddParagraph("- MacOS: icon jumps on the Dock and raises it. Only once if briefly."); + behaviourText.AddParagraph("- MacOS: icon jumps on the Dock (can be seen even hidden). Only once if briefly."); behaviourText.AddParagraph("- Linux: depends on DE/WM setup."); } } diff --git a/osu.Framework/Platform/IWindow.cs b/osu.Framework/Platform/IWindow.cs index a5bee8b0d7..64cc92c293 100644 --- a/osu.Framework/Platform/IWindow.cs +++ b/osu.Framework/Platform/IWindow.cs @@ -164,8 +164,8 @@ public interface IWindow : IDisposable /// /// /// This behaviour is only available on desktop platforms and is different depending on the OS: - /// - On Windows: flashes the icon on the taskbar and raises it (only once if briefly) - /// - On MacOS: jumps on the Dock and raises it (only once if briefly) + /// - On Windows: flashes the icon on the taskbar (only once if briefly) and raises it + /// - On MacOS: jumps on the Dock (only once if briefly) /// - On Linux: depends on the Desktop Environment / Window Manager setup. /// /// Flash the window until the window is focused. From b38f38dab275f452ae4b5d30f76cb30fbe3d54ae Mon Sep 17 00:00:00 2001 From: tsrk Date: Thu, 17 Aug 2023 23:51:39 +0200 Subject: [PATCH 04/17] refactor: move flash-related methods declarations Moved `Flash()` and `CancelFlash()` to be methods specific for `SDL2DesktopWindow`. Other platforms don't need to be bloated with that as it's specific for desktop platforms only. --- .../Visual/Platform/TestSceneWindowFlash.cs | 4 ++-- osu.Framework/Platform/IWindow.cs | 17 ----------------- osu.Framework/Platform/OsuTKWindow.cs | 8 -------- osu.Framework/Platform/SDL2DesktopWindow.cs | 13 +++++++++++++ osu.Framework/Platform/SDL2Window.cs | 13 ------------- 5 files changed, 15 insertions(+), 40 deletions(-) diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs index 58b728a655..b4d601bbd8 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs @@ -13,7 +13,7 @@ namespace osu.Framework.Tests.Visual.Platform public partial class TestSceneWindowFlash : FrameworkTestScene { private IBindable isActive = null!; - private IWindow? window; + private SDL2DesktopWindow? window; private SpriteText text = null!; private TextFlowContainer behaviourText = null!; private readonly Bindable flashUntilFocused = new BindableBool(); @@ -22,7 +22,7 @@ public partial class TestSceneWindowFlash : FrameworkTestScene private void load(GameHost gameHost) { isActive = gameHost.IsActive.GetBoundCopy(); - window = gameHost.Window; + window = gameHost.Window as SDL2DesktopWindow; Child = new FillFlowContainer { Direction = FillDirection.Vertical, diff --git a/osu.Framework/Platform/IWindow.cs b/osu.Framework/Platform/IWindow.cs index 64cc92c293..6c6772713b 100644 --- a/osu.Framework/Platform/IWindow.cs +++ b/osu.Framework/Platform/IWindow.cs @@ -159,23 +159,6 @@ public interface IWindow : IDisposable /// void Raise(); - /// - /// Attempts to flash the window in order to request the user's attention. - /// - /// - /// This behaviour is only available on desktop platforms and is different depending on the OS: - /// - On Windows: flashes the icon on the taskbar (only once if briefly) and raises it - /// - On MacOS: jumps on the Dock (only once if briefly) - /// - On Linux: depends on the Desktop Environment / Window Manager setup. - /// - /// Flash the window until the window is focused. - void Flash(bool untilFocused = false); - - /// - /// Attempts to cancel any window flash requested with . - /// - void CancelFlash(); - /// /// Start the window's run loop. /// Is a blocking call on desktop platforms, and a non-blocking call on mobile platforms. diff --git a/osu.Framework/Platform/OsuTKWindow.cs b/osu.Framework/Platform/OsuTKWindow.cs index 9d1463fd47..50292c811b 100644 --- a/osu.Framework/Platform/OsuTKWindow.cs +++ b/osu.Framework/Platform/OsuTKWindow.cs @@ -90,14 +90,6 @@ public void Raise() { } - public void Flash(bool _) - { - } - - public void CancelFlash() - { - } - public abstract bool Focused { get; } public abstract IBindable IsActive { get; } diff --git a/osu.Framework/Platform/SDL2DesktopWindow.cs b/osu.Framework/Platform/SDL2DesktopWindow.cs index c72aab50b1..175700cd2e 100644 --- a/osu.Framework/Platform/SDL2DesktopWindow.cs +++ b/osu.Framework/Platform/SDL2DesktopWindow.cs @@ -12,6 +12,19 @@ public SDL2DesktopWindow(GraphicsSurfaceType surfaceType) { } + public void Flash(bool untilFocused = false) => ScheduleCommand(() => + { + if (IsActive.Value) + return; + + SDL.SDL_FlashWindow(SDLWindowHandle, untilFocused + ? SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED + : SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY); + }); + + public void CancelFlash() + => ScheduleCommand(() => SDL.SDL_FlashWindow(SDLWindowHandle, SDL.SDL_FlashOperation.SDL_FLASH_CANCEL)); + protected override void UpdateWindowStateAndSize(WindowState state, Display display, DisplayMode displayMode) { // this reset is required even on changing from one fullscreen resolution to another. diff --git a/osu.Framework/Platform/SDL2Window.cs b/osu.Framework/Platform/SDL2Window.cs index 02c6d3474e..45bbfc8b3a 100644 --- a/osu.Framework/Platform/SDL2Window.cs +++ b/osu.Framework/Platform/SDL2Window.cs @@ -366,19 +366,6 @@ public void Raise() => ScheduleCommand(() => SDL.SDL_RaiseWindow(SDLWindowHandle); }); - public void Flash(bool untilFocused = false) => ScheduleCommand(() => - { - if (isActive.Value) - return; - - SDL.SDL_FlashWindow(SDLWindowHandle, untilFocused - ? SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED - : SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY); - }); - - public void CancelFlash() - => ScheduleCommand(() => SDL.SDL_FlashWindow(SDLWindowHandle, SDL.SDL_FlashOperation.SDL_FLASH_CANCEL)); - /// /// Attempts to set the window's icon to the specified image. /// From 3029f3aad509c663c5c957214f23d399273b5e2c Mon Sep 17 00:00:00 2001 From: tsrk Date: Fri, 18 Aug 2023 00:11:33 +0200 Subject: [PATCH 05/17] test(WindowFlash): better rewording --- osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs index b4d601bbd8..826594ce11 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs @@ -53,8 +53,9 @@ protected override void LoadComplete() flashUntilFocused.BindValueChanged(e => { window?.CancelFlash(); - text.Text = "This window will flash " + (e.NewValue ? "until focused again" : "briefly") - + " as soon as it is unfocused."; + text.Text = "This window will flash " + + (e.NewValue ? "continuously, until focused again, " : "briefly") + + " as soon as it is unfocused."; }, true); isActive.BindValueChanged(e => { From 4cc9a49d95b28e8876fa6aa2808443a4f4383b0d Mon Sep 17 00:00:00 2001 From: tsrk Date: Fri, 18 Aug 2023 20:23:39 +0200 Subject: [PATCH 06/17] chore(xmldoc): re-document flash methods --- osu.Framework/Platform/SDL2DesktopWindow.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Framework/Platform/SDL2DesktopWindow.cs b/osu.Framework/Platform/SDL2DesktopWindow.cs index ed010461b2..c9dd86811c 100644 --- a/osu.Framework/Platform/SDL2DesktopWindow.cs +++ b/osu.Framework/Platform/SDL2DesktopWindow.cs @@ -12,6 +12,16 @@ public SDL2DesktopWindow(GraphicsSurfaceType surfaceType) { } + /// + /// Attempts to flash the window in order to request the user's attention while unfocused. + /// + /// The flash can be canceled with . + /// + /// + /// + /// This behaviour is available only on desktop platforms and may differ depending on the operating system. + /// + /// Whether the window should flash briefly or until focused. public void Flash(bool untilFocused = false) => ScheduleCommand(() => { if (IsActive.Value) @@ -22,6 +32,12 @@ public void Flash(bool untilFocused = false) => ScheduleCommand(() => : SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY); }); + /// + /// Cancels any flash triggered with + /// + /// + /// This can also cancel brief flashes (especially on Linux and Windows). + /// public void CancelFlash() => ScheduleCommand(() => SDL.SDL_FlashWindow(SDLWindowHandle, SDL.SDL_FlashOperation.SDL_FLASH_CANCEL)); From d63059fa91f652862e998e978e68560301131e8c Mon Sep 17 00:00:00 2001 From: tsrk Date: Sat, 19 Aug 2023 00:55:10 +0200 Subject: [PATCH 07/17] style: invert bool in `Flash()` to make arg more explicit --- osu.Framework/Platform/SDL2DesktopWindow.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Framework/Platform/SDL2DesktopWindow.cs b/osu.Framework/Platform/SDL2DesktopWindow.cs index c9dd86811c..17c35b08ad 100644 --- a/osu.Framework/Platform/SDL2DesktopWindow.cs +++ b/osu.Framework/Platform/SDL2DesktopWindow.cs @@ -21,15 +21,15 @@ public SDL2DesktopWindow(GraphicsSurfaceType surfaceType) /// /// This behaviour is available only on desktop platforms and may differ depending on the operating system. /// - /// Whether the window should flash briefly or until focused. - public void Flash(bool untilFocused = false) => ScheduleCommand(() => + /// Whether the window should flash briefly or until focused. + public void Flash(bool briefly = true) => ScheduleCommand(() => { if (IsActive.Value) return; - SDL.SDL_FlashWindow(SDLWindowHandle, untilFocused - ? SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED - : SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY); + SDL.SDL_FlashWindow(SDLWindowHandle, briefly + ? SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY + : SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED); }); /// From 6c6425847b909542347a66bd4bea24e229e7e5de Mon Sep 17 00:00:00 2001 From: tsrk Date: Sat, 19 Aug 2023 02:37:06 +0200 Subject: [PATCH 08/17] feat(DesktopGameHost): expose window flashing --- osu.Framework/Platform/DesktopGameHost.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Framework/Platform/DesktopGameHost.cs b/osu.Framework/Platform/DesktopGameHost.cs index a9c1674834..86288a7d2a 100644 --- a/osu.Framework/Platform/DesktopGameHost.cs +++ b/osu.Framework/Platform/DesktopGameHost.cs @@ -26,6 +26,10 @@ protected DesktopGameHost(string gameName, HostOptions options = null) IsPortableInstallation = Options.PortableInstallation; } + public void FlashWindow(bool briefly = true) => (Window as SDL2DesktopWindow)?.Flash(briefly); + + public void CancelWindowFlashing() => (Window as SDL2DesktopWindow)?.CancelFlash(); + protected sealed override Storage GetDefaultGameStorage() { if (IsPortableInstallation || File.Exists(Path.Combine(RuntimeInfo.StartupDirectory, FrameworkConfigManager.FILENAME))) From 08d5fc2584754beb04ed07baa424a2bac9171523 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sat, 19 Aug 2023 15:17:00 +0200 Subject: [PATCH 09/17] revert: bring back flash declaration in `IWindow` Turns out that my initial idea wan't that bad. Refs: 6c642584, b38f38d --- .../Visual/Platform/TestSceneWindowFlash.cs | 4 +-- osu.Framework/Platform/DesktopGameHost.cs | 4 --- osu.Framework/Platform/IWindow.cs | 17 +++++++++++ osu.Framework/Platform/OsuTKWindow.cs | 4 +++ osu.Framework/Platform/SDL2DesktopWindow.cs | 29 ------------------- osu.Framework/Platform/SDL2Window.cs | 13 +++++++++ 6 files changed, 36 insertions(+), 35 deletions(-) diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs index 826594ce11..8c67dbbf5e 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs @@ -13,7 +13,7 @@ namespace osu.Framework.Tests.Visual.Platform public partial class TestSceneWindowFlash : FrameworkTestScene { private IBindable isActive = null!; - private SDL2DesktopWindow? window; + private IWindow? window; private SpriteText text = null!; private TextFlowContainer behaviourText = null!; private readonly Bindable flashUntilFocused = new BindableBool(); @@ -22,7 +22,7 @@ public partial class TestSceneWindowFlash : FrameworkTestScene private void load(GameHost gameHost) { isActive = gameHost.IsActive.GetBoundCopy(); - window = gameHost.Window as SDL2DesktopWindow; + window = gameHost.Window; Child = new FillFlowContainer { Direction = FillDirection.Vertical, diff --git a/osu.Framework/Platform/DesktopGameHost.cs b/osu.Framework/Platform/DesktopGameHost.cs index 86288a7d2a..a9c1674834 100644 --- a/osu.Framework/Platform/DesktopGameHost.cs +++ b/osu.Framework/Platform/DesktopGameHost.cs @@ -26,10 +26,6 @@ protected DesktopGameHost(string gameName, HostOptions options = null) IsPortableInstallation = Options.PortableInstallation; } - public void FlashWindow(bool briefly = true) => (Window as SDL2DesktopWindow)?.Flash(briefly); - - public void CancelWindowFlashing() => (Window as SDL2DesktopWindow)?.CancelFlash(); - protected sealed override Storage GetDefaultGameStorage() { if (IsPortableInstallation || File.Exists(Path.Combine(RuntimeInfo.StartupDirectory, FrameworkConfigManager.FILENAME))) diff --git a/osu.Framework/Platform/IWindow.cs b/osu.Framework/Platform/IWindow.cs index 949931d8c5..8c4589e824 100644 --- a/osu.Framework/Platform/IWindow.cs +++ b/osu.Framework/Platform/IWindow.cs @@ -165,6 +165,23 @@ public interface IWindow : IDisposable /// void Raise(); + /// + /// Attempts to flash the window in order to request the user's attention. + /// + /// + /// This behaviour is only available on desktop platforms and is different depending on the OS: + /// - On Windows: flashes the icon on the taskbar (only once if briefly) and raises it + /// - On MacOS: jumps on the Dock (only once if briefly) + /// - On Linux: depends on the Desktop Environment / Window Manager setup. + /// + /// Flash the window until the window is focused. + void Flash(bool untilFocused = false); + + /// + /// Attempts to cancel any window flash requested with . + /// + void CancelFlash(); + /// /// Start the window's run loop. /// Is a blocking call on desktop platforms, and a non-blocking call on mobile platforms. diff --git a/osu.Framework/Platform/OsuTKWindow.cs b/osu.Framework/Platform/OsuTKWindow.cs index 6ea821ad00..6cb4386616 100644 --- a/osu.Framework/Platform/OsuTKWindow.cs +++ b/osu.Framework/Platform/OsuTKWindow.cs @@ -94,6 +94,10 @@ public void Raise() { } + public void Flash(bool _) => throw new NotImplementedException("OsuTK windows don't implement flashing."); + + public void CancelFlash() => throw new NotImplementedException("OsuTK windows don't implement flashing."); + public abstract bool Focused { get; } public abstract IBindable IsActive { get; } diff --git a/osu.Framework/Platform/SDL2DesktopWindow.cs b/osu.Framework/Platform/SDL2DesktopWindow.cs index 17c35b08ad..56807f19fb 100644 --- a/osu.Framework/Platform/SDL2DesktopWindow.cs +++ b/osu.Framework/Platform/SDL2DesktopWindow.cs @@ -12,35 +12,6 @@ public SDL2DesktopWindow(GraphicsSurfaceType surfaceType) { } - /// - /// Attempts to flash the window in order to request the user's attention while unfocused. - /// - /// The flash can be canceled with . - /// - /// - /// - /// This behaviour is available only on desktop platforms and may differ depending on the operating system. - /// - /// Whether the window should flash briefly or until focused. - public void Flash(bool briefly = true) => ScheduleCommand(() => - { - if (IsActive.Value) - return; - - SDL.SDL_FlashWindow(SDLWindowHandle, briefly - ? SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY - : SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED); - }); - - /// - /// Cancels any flash triggered with - /// - /// - /// This can also cancel brief flashes (especially on Linux and Windows). - /// - public void CancelFlash() - => ScheduleCommand(() => SDL.SDL_FlashWindow(SDLWindowHandle, SDL.SDL_FlashOperation.SDL_FLASH_CANCEL)); - protected override void UpdateWindowStateAndSize(WindowState state, Display display, DisplayMode displayMode) { // this reset is required even on changing from one fullscreen resolution to another. diff --git a/osu.Framework/Platform/SDL2Window.cs b/osu.Framework/Platform/SDL2Window.cs index dd7cffd17c..eb5b30514a 100644 --- a/osu.Framework/Platform/SDL2Window.cs +++ b/osu.Framework/Platform/SDL2Window.cs @@ -407,6 +407,19 @@ public void Raise() => ScheduleCommand(() => SDL.SDL_RaiseWindow(SDLWindowHandle); }); + public void Flash(bool untilFocused = false) => ScheduleCommand(() => + { + if (isActive.Value) + return; + + SDL.SDL_FlashWindow(SDLWindowHandle, untilFocused + ? SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED + : SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY); + }); + + public void CancelFlash() + => ScheduleCommand(() => SDL.SDL_FlashWindow(SDLWindowHandle, SDL.SDL_FlashOperation.SDL_FLASH_CANCEL)); + /// /// Attempts to set the window's icon to the specified image. /// From 04a28a2ffef1bad46f4083402789cf1d01d5f097 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sat, 19 Aug 2023 15:32:54 +0200 Subject: [PATCH 10/17] test: ignore `TestSceneWindowFlash` --- osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs index 8c67dbbf5e..985c68deb5 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -10,6 +11,7 @@ namespace osu.Framework.Tests.Visual.Platform { + [Ignore("This test cannot run in headless mode (a window instance is required).")] public partial class TestSceneWindowFlash : FrameworkTestScene { private IBindable isActive = null!; From df1e867857c8ccb15eb5f929333fbc48c552a0c1 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sat, 19 Aug 2023 17:42:54 +0200 Subject: [PATCH 11/17] refactor: change exception thrown for `OsuTKWindow.{Cancel}Flash` --- osu.Framework/Platform/OsuTKWindow.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Framework/Platform/OsuTKWindow.cs b/osu.Framework/Platform/OsuTKWindow.cs index 6cb4386616..1296d1534a 100644 --- a/osu.Framework/Platform/OsuTKWindow.cs +++ b/osu.Framework/Platform/OsuTKWindow.cs @@ -94,9 +94,9 @@ public void Raise() { } - public void Flash(bool _) => throw new NotImplementedException("OsuTK windows don't implement flashing."); + public void Flash(bool _) => throw new NotSupportedException("OsuTK windows can't flash."); - public void CancelFlash() => throw new NotImplementedException("OsuTK windows don't implement flashing."); + public void CancelFlash() => throw new NotSupportedException("OsuTK windows can't flash."); public abstract bool Focused { get; } From 85d9ab37bcd589a58209557955f0f6a0d068dde8 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sat, 19 Aug 2023 17:51:36 +0200 Subject: [PATCH 12/17] feat: guard use of flash methods on non-desktop platforms --- osu.Framework/Platform/SDL2Window.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Framework/Platform/SDL2Window.cs b/osu.Framework/Platform/SDL2Window.cs index eb5b30514a..f001f6139a 100644 --- a/osu.Framework/Platform/SDL2Window.cs +++ b/osu.Framework/Platform/SDL2Window.cs @@ -412,13 +412,21 @@ public void Flash(bool untilFocused = false) => ScheduleCommand(() => if (isActive.Value) return; + if (!RuntimeInfo.IsDesktop) + return; + SDL.SDL_FlashWindow(SDLWindowHandle, untilFocused ? SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED : SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY); }); - public void CancelFlash() - => ScheduleCommand(() => SDL.SDL_FlashWindow(SDLWindowHandle, SDL.SDL_FlashOperation.SDL_FLASH_CANCEL)); + public void CancelFlash() => ScheduleCommand(() => + { + if (!RuntimeInfo.IsDesktop) + return; + + SDL.SDL_FlashWindow(SDLWindowHandle, SDL.SDL_FlashOperation.SDL_FLASH_CANCEL); + }); /// /// Attempts to set the window's icon to the specified image. From 95b5b1dd0d718296f11c42ab7e552fcc7329c805 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sun, 20 Aug 2023 05:31:37 +0200 Subject: [PATCH 13/17] refactor: no-op flash methods in `OsuTKWindow` should avoid the consumer doing platform checks everytime this method is called --- osu.Framework/Platform/IWindow.cs | 9 +++++---- osu.Framework/Platform/OsuTKWindow.cs | 8 ++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Framework/Platform/IWindow.cs b/osu.Framework/Platform/IWindow.cs index 8c4589e824..c47df19ad9 100644 --- a/osu.Framework/Platform/IWindow.cs +++ b/osu.Framework/Platform/IWindow.cs @@ -167,12 +167,10 @@ public interface IWindow : IDisposable /// /// Attempts to flash the window in order to request the user's attention. + /// This behaviour is only available on desktop platforms and is different depending on the OS. /// /// - /// This behaviour is only available on desktop platforms and is different depending on the OS: - /// - On Windows: flashes the icon on the taskbar (only once if briefly) and raises it - /// - On MacOS: jumps on the Dock (only once if briefly) - /// - On Linux: depends on the Desktop Environment / Window Manager setup. + /// This has no effect on s and on iOS/iPadOS. /// /// Flash the window until the window is focused. void Flash(bool untilFocused = false); @@ -180,6 +178,9 @@ public interface IWindow : IDisposable /// /// Attempts to cancel any window flash requested with . /// + /// + /// This has no effect on s and on iOS/iPadOS. + /// void CancelFlash(); /// diff --git a/osu.Framework/Platform/OsuTKWindow.cs b/osu.Framework/Platform/OsuTKWindow.cs index 1296d1534a..95f11afbb8 100644 --- a/osu.Framework/Platform/OsuTKWindow.cs +++ b/osu.Framework/Platform/OsuTKWindow.cs @@ -94,9 +94,13 @@ public void Raise() { } - public void Flash(bool _) => throw new NotSupportedException("OsuTK windows can't flash."); + public void Flash(bool _) + { + } - public void CancelFlash() => throw new NotSupportedException("OsuTK windows can't flash."); + public void CancelFlash() + { + } public abstract bool Focused { get; } From 5fbbe6350473e557b363fde3cb3376ddc2640836 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sun, 20 Aug 2023 05:37:37 +0200 Subject: [PATCH 14/17] chore(xmldoc): explicit behaviour for param in `IWindow.Flash` --- osu.Framework/Platform/IWindow.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Framework/Platform/IWindow.cs b/osu.Framework/Platform/IWindow.cs index c47df19ad9..7024ba594f 100644 --- a/osu.Framework/Platform/IWindow.cs +++ b/osu.Framework/Platform/IWindow.cs @@ -172,7 +172,10 @@ public interface IWindow : IDisposable /// /// This has no effect on s and on iOS/iPadOS. /// - /// Flash the window until the window is focused. + /// + /// When true, the window will flash until it is focused again. + /// Only once otherwise. + /// void Flash(bool untilFocused = false); /// From 812647e60352f695f07d334214f5ac0020645c60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 19:43:22 +0900 Subject: [PATCH 15/17] Simplify test --- .../Visual/Platform/TestSceneWindowFlash.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs index 985c68deb5..c377ff8739 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs @@ -17,7 +17,6 @@ public partial class TestSceneWindowFlash : FrameworkTestScene private IBindable isActive = null!; private IWindow? window; private SpriteText text = null!; - private TextFlowContainer behaviourText = null!; private readonly Bindable flashUntilFocused = new BindableBool(); [BackgroundDependencyLoader] @@ -34,16 +33,10 @@ private void load(GameHost gameHost) { text = new SpriteText { - Text = "This window will flash as soon as you unfocus it.", + Text = "This window will flash as soon as you un-focus it.", Anchor = Anchor.Centre, Origin = Anchor.Centre }, - behaviourText = new TextFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both - } } }; } @@ -51,7 +44,7 @@ private void load(GameHost gameHost) protected override void LoadComplete() { base.LoadComplete(); - AddToggleStep("Flash until focused", a => flashUntilFocused.Value = a); + flashUntilFocused.BindValueChanged(e => { window?.CancelFlash(); @@ -59,15 +52,18 @@ protected override void LoadComplete() + (e.NewValue ? "continuously, until focused again, " : "briefly") + " as soon as it is unfocused."; }, true); + isActive.BindValueChanged(e => { if (!e.NewValue) window?.Flash(flashUntilFocused.Value); }, true); - behaviourText.AddParagraph("Behaviour is platform dependent (only for desktops):"); - behaviourText.AddParagraph("- Windows: icon flashes on the taskbar and raises it. Only once if briefly."); - behaviourText.AddParagraph("- MacOS: icon jumps on the Dock (can be seen even hidden). Only once if briefly."); - behaviourText.AddParagraph("- Linux: depends on DE/WM setup."); + } + + [Test] + public void TestBasic() + { + AddToggleStep("Flash until focused", a => flashUntilFocused.Value = a); } } } From 8b5a35b4e97b40e8fdab2ba2d6be2c0e3a1b8683 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 19:45:41 +0900 Subject: [PATCH 16/17] Update parameter naming and xmldoc --- osu.Framework/Platform/IWindow.cs | 13 ++++++------- osu.Framework/Platform/SDL2Window.cs | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Framework/Platform/IWindow.cs b/osu.Framework/Platform/IWindow.cs index 7024ba594f..7a7366f642 100644 --- a/osu.Framework/Platform/IWindow.cs +++ b/osu.Framework/Platform/IWindow.cs @@ -167,22 +167,21 @@ public interface IWindow : IDisposable /// /// Attempts to flash the window in order to request the user's attention. - /// This behaviour is only available on desktop platforms and is different depending on the OS. /// /// - /// This has no effect on s and on iOS/iPadOS. + /// On platforms which don't support any kind of flashing (ie. mobile), this will be a no-op. /// - /// - /// When true, the window will flash until it is focused again. - /// Only once otherwise. + /// + /// When true, the window will flash until it is focused again. + /// When false it will only flash momentarily. /// - void Flash(bool untilFocused = false); + void Flash(bool flashUntilFocused = false); /// /// Attempts to cancel any window flash requested with . /// /// - /// This has no effect on s and on iOS/iPadOS. + /// On platforms which don't support any kind of flashing (ie. mobile), this will be a no-op. /// void CancelFlash(); diff --git a/osu.Framework/Platform/SDL2Window.cs b/osu.Framework/Platform/SDL2Window.cs index f001f6139a..8ae1d72155 100644 --- a/osu.Framework/Platform/SDL2Window.cs +++ b/osu.Framework/Platform/SDL2Window.cs @@ -407,7 +407,7 @@ public void Raise() => ScheduleCommand(() => SDL.SDL_RaiseWindow(SDLWindowHandle); }); - public void Flash(bool untilFocused = false) => ScheduleCommand(() => + public void Flash(bool flashUntilFocused = false) => ScheduleCommand(() => { if (isActive.Value) return; @@ -415,7 +415,7 @@ public void Flash(bool untilFocused = false) => ScheduleCommand(() => if (!RuntimeInfo.IsDesktop) return; - SDL.SDL_FlashWindow(SDLWindowHandle, untilFocused + SDL.SDL_FlashWindow(SDLWindowHandle, flashUntilFocused ? SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED : SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY); }); From 1089893d8b546753081fbedd8100992f6949facd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 15:10:06 +0200 Subject: [PATCH 17/17] Do not display window flash test scene on mobile As it does nothing there. --- osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs index c377ff8739..76f39de299 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowFlash.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.Runtime.Versioning; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,6 +13,9 @@ namespace osu.Framework.Tests.Visual.Platform { [Ignore("This test cannot run in headless mode (a window instance is required).")] + [SupportedOSPlatform("windows")] + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] public partial class TestSceneWindowFlash : FrameworkTestScene { private IBindable isActive = null!;