diff --git a/src/Skyline.Apple/AppKitWindowBackend.cs b/src/Skyline.Apple/AppKitWindowBackend.cs index 91524de..09b11dc 100644 --- a/src/Skyline.Apple/AppKitWindowBackend.cs +++ b/src/Skyline.Apple/AppKitWindowBackend.cs @@ -28,6 +28,7 @@ internal sealed class AppKitWindowBackend : IWindowBackend public event Action<(int Width, int Height)>? FramebufferResized; public event Action? MinimizedChanged; + public event Action? FocusChanged; public event Action? Render; public event Action? Pointer; public event Action? Key; @@ -162,6 +163,8 @@ public void Run() internal void OnMinimizedChanged(bool minimized) => MinimizedChanged?.Invoke(minimized); + internal void OnFocusChanged(bool focused) => FocusChanged?.Invoke(focused); + public void Dispose() { _window.Delegate = null; @@ -181,5 +184,9 @@ private sealed class WindowDelegate(AppKitWindowBackend backend) : NSWindowDeleg public override void DidMiniaturize(NSNotification notification) => backend.OnMinimizedChanged(true); public override void DidDeminiaturize(NSNotification notification) => backend.OnMinimizedChanged(false); + + public override void DidBecomeKey(NSNotification notification) => backend.OnFocusChanged(true); + + public override void DidResignKey(NSNotification notification) => backend.OnFocusChanged(false); } } diff --git a/src/Skyline/AppWindow.cs b/src/Skyline/AppWindow.cs index 962a94a..08db5d0 100644 --- a/src/Skyline/AppWindow.cs +++ b/src/Skyline/AppWindow.cs @@ -97,6 +97,7 @@ public AppWindow(AppWindowOptions? options = null) _backend.Pointer += e => PointerInput?.Invoke(e); _backend.Key += e => KeyInput?.Invoke(e); _backend.Text += e => TextInput?.Invoke(e); + _backend.FocusChanged += focused => FocusChanged?.Invoke(focused); } internal void RaisePointer(PointerEventKind kind, float x, float y, int button, float wheelDx, float wheelDy) => @@ -139,6 +140,12 @@ internal void RaiseText(char character) => public event Action? KeyInput; public event Action? TextInput; + /// + /// The window gained or lost focus (true = active). Pause animation, + /// timers, and media while blurred; resume on focus. + /// + public event Action? FocusChanged; + /// /// When set and returning false, the frame is skipped and the loop /// sleeps briefly instead of rendering. Continuous-render apps leave diff --git a/src/Skyline/GlfwWindowBackend.cs b/src/Skyline/GlfwWindowBackend.cs index 3d0d94f..5a5cc35 100644 --- a/src/Skyline/GlfwWindowBackend.cs +++ b/src/Skyline/GlfwWindowBackend.cs @@ -22,6 +22,7 @@ internal sealed class GlfwWindowBackend : IWindowBackend public event Action<(int Width, int Height)>? FramebufferResized; public event Action? MinimizedChanged; + public event Action? FocusChanged; public event Action? Render; public event Action? Pointer; public event Action? Key; @@ -51,6 +52,7 @@ public unsafe GlfwWindowBackend(AppWindowOptions options) _window.FramebufferResize += sz => FramebufferResized?.Invoke((sz.X, sz.Y)); _window.StateChanged += state => MinimizedChanged?.Invoke(state == WindowState.Minimized); + _window.FocusChanged += focused => FocusChanged?.Invoke(focused); _window.Render += delta => Render?.Invoke(delta); _input = _window.CreateInput(); diff --git a/src/Skyline/IWindowBackend.cs b/src/Skyline/IWindowBackend.cs index e1b01c9..b80da32 100644 --- a/src/Skyline/IWindowBackend.cs +++ b/src/Skyline/IWindowBackend.cs @@ -31,6 +31,9 @@ internal interface IWindowBackend : IDisposable /// Minimized state changed; true when minimized. event Action? MinimizedChanged; + /// Focus changed; true when the window became active. + event Action? FocusChanged; + /// A frame tick from the backend's own loop, with the delta seconds. event Action? Render; diff --git a/tests/Skyline.Tests/WindowBackendFactoryTests.cs b/tests/Skyline.Tests/WindowBackendFactoryTests.cs index a7b3ff2..649a793 100644 --- a/tests/Skyline.Tests/WindowBackendFactoryTests.cs +++ b/tests/Skyline.Tests/WindowBackendFactoryTests.cs @@ -21,6 +21,7 @@ private sealed class FakeBackend : IWindowBackend public event Action<(int Width, int Height)>? FramebufferResized; public event Action? MinimizedChanged; + public event Action? FocusChanged; public event Action? Render; public event Action? Pointer; public event Action? Key;