From 2f4c3558f592742470c4b1bf9df8304cd9f1ffbd Mon Sep 17 00:00:00 2001 From: beauchama Date: Fri, 19 Jun 2026 20:21:26 -0400 Subject: [PATCH 1/2] Added Mouse button, motion and wheel events --- analyzers/disabled.editorconfig | 5 + .../Events/CultureChangedEvent.cs | 2 +- src/KappaDuck.Quack/Events/Event.cs | 116 +++++++++++++++++ src/KappaDuck.Quack/Events/EventManager.cs | 8 +- src/KappaDuck.Quack/Events/EventQueue.cs | 54 ++------ src/KappaDuck.Quack/Events/EventType.cs | 106 ++++----------- src/KappaDuck.Quack/Events/KeyPressedEvent.cs | 28 ++-- .../Events/KeyReleasedEvent.cs | 26 +++- .../Events/KeyboardAddedEvent.cs | 15 ++- .../Events/KeyboardRemovedEvent.cs | 15 ++- src/KappaDuck.Quack/Events/MouseAddedEvent.cs | 15 ++- .../Events/MouseButtonPressedEvent.cs | 54 ++++++++ .../Events/MouseButtonReleasedEvent.cs | 48 +++++++ src/KappaDuck.Quack/Events/MouseMovedEvent.cs | 55 ++++++++ .../Events/MouseRemovedEvent.cs | 15 ++- src/KappaDuck.Quack/Events/MouseWheelEvent.cs | 52 ++++++++ .../Events/QuitRequestedEvent.cs | 4 +- .../Events/ThemeChangedEvent.cs | 2 +- src/KappaDuck.Quack/Input/MouseButton.cs | 35 +++++ src/KappaDuck.Quack/Input/MouseButtonState.cs | 41 ++++++ .../SDL/Primitives/Events/SDL_Event.cs | 18 ++- .../Primitives/Events/SDL_KeyboardEvent.cs | 6 +- .../Primitives/Events/SDL_MouseButtonEvent.cs | 30 +++++ .../Primitives/Events/SDL_MouseDeviceEvent.cs | 6 +- .../Primitives/Events/SDL_MouseMotionEvent.cs | 28 ++++ .../Primitives/Events/SDL_MouseWheelEvent.cs | 29 +++++ .../SDL/Primitives/Events/SDL_QuitEvent.cs | 12 -- .../SDL/Primitives/SDL_MouseWheelDirection.cs | 10 ++ src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs | 24 +++- .../Events/EventQueueTests.cs | 121 ------------------ tests/Integration.Tests/SmokeTests.cs | 13 ++ 31 files changed, 679 insertions(+), 314 deletions(-) create mode 100644 src/KappaDuck.Quack/Events/MouseButtonPressedEvent.cs create mode 100644 src/KappaDuck.Quack/Events/MouseButtonReleasedEvent.cs create mode 100644 src/KappaDuck.Quack/Events/MouseMovedEvent.cs create mode 100644 src/KappaDuck.Quack/Events/MouseWheelEvent.cs create mode 100644 src/KappaDuck.Quack/Input/MouseButton.cs create mode 100644 src/KappaDuck.Quack/Input/MouseButtonState.cs create mode 100644 src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseButtonEvent.cs create mode 100644 src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseMotionEvent.cs create mode 100644 src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseWheelEvent.cs delete mode 100644 src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_QuitEvent.cs create mode 100644 src/KappaDuck.Quack/Interop/SDL/Primitives/SDL_MouseWheelDirection.cs delete mode 100644 tests/Integration.Tests/Events/EventQueueTests.cs create mode 100644 tests/Integration.Tests/SmokeTests.cs diff --git a/analyzers/disabled.editorconfig b/analyzers/disabled.editorconfig index 5664a2c..04d740c 100644 --- a/analyzers/disabled.editorconfig +++ b/analyzers/disabled.editorconfig @@ -1,5 +1,10 @@ is_global = true +# CA1008: Enums should have zero value +# Some enum doesn't need a zero value +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1008 +dotnet_diagnostic.CA1008.severity = none + # CA1033: Interface methods should be callable by child types # Some childs should not have the parent methods # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1033 diff --git a/src/KappaDuck.Quack/Events/CultureChangedEvent.cs b/src/KappaDuck.Quack/Events/CultureChangedEvent.cs index 83db5aa..679d813 100644 --- a/src/KappaDuck.Quack/Events/CultureChangedEvent.cs +++ b/src/KappaDuck.Quack/Events/CultureChangedEvent.cs @@ -4,6 +4,6 @@ namespace KappaDuck.Quack.Events; /// -/// Represents an event where the user's culture preferences has been changed. +/// Raised when the user's culture preferences has been changed. /// public readonly struct CultureChangedEvent : IEvent; diff --git a/src/KappaDuck.Quack/Events/Event.cs b/src/KappaDuck.Quack/Events/Event.cs index 7e4ac2c..1a2926a 100644 --- a/src/KappaDuck.Quack/Events/Event.cs +++ b/src/KappaDuck.Quack/Events/Event.cs @@ -20,6 +20,10 @@ namespace KappaDuck.Quack.Events; private readonly MouseRemovedEvent _mouseRemovedEvent; private readonly KeyPressedEvent _keyPressedEvent; private readonly KeyReleasedEvent _keyReleasedEvent; + private readonly MouseButtonPressedEvent _buttonPressedEvent; + private readonly MouseButtonReleasedEvent _buttonReleasedEvent; + private readonly MouseMovedEvent _mouseMovedEvent; + private readonly MouseWheelEvent _wheelEvent; /// /// Initializes a quit requested event. @@ -111,6 +115,46 @@ public Event(KeyReleasedEvent e) Type = SDL_EventType.KeyUp; } + /// + /// Initializes a mouse button pressed event. + /// + /// The mouse button pressed event. + public Event(MouseButtonPressedEvent e) + { + _buttonPressedEvent = e; + Type = SDL_EventType.MouseButtonDown; + } + + /// + /// Initializes a mouse button released event. + /// + /// The mouse button released event. + public Event(MouseButtonReleasedEvent e) + { + _buttonReleasedEvent = e; + Type = SDL_EventType.MouseButtonUp; + } + + /// + /// Initializes a mouse moved event. + /// + /// The mouse moved event. + public Event(MouseMovedEvent e) + { + _mouseMovedEvent = e; + Type = SDL_EventType.MouseMotion; + } + + /// + /// Initializes a mouse wheel event. + /// + /// The mouse wheel event. + public Event(MouseWheelEvent e) + { + _wheelEvent = e; + Type = SDL_EventType.MouseWheel; + } + internal SDL_EventType Type { get; } /// @@ -133,6 +177,10 @@ public Event(KeyReleasedEvent e) SDL_EventType.MouseRemoved => _mouseRemovedEvent, SDL_EventType.KeyDown => _keyPressedEvent, SDL_EventType.KeyUp => _keyReleasedEvent, + SDL_EventType.MouseButtonDown => _buttonPressedEvent, + SDL_EventType.MouseButtonUp => _buttonReleasedEvent, + SDL_EventType.MouseMotion => _mouseMovedEvent, + SDL_EventType.MouseWheel => _wheelEvent, _ => null }; @@ -279,4 +327,72 @@ public bool TryGetValue(out KeyReleasedEvent e) e = _keyReleasedEvent; return true; } + + /// + /// Attempts to retrieve this event as a . + /// + /// The mouse button pressed event. + /// if this event holds a ; otherwise + public bool TryGetValue(out MouseButtonPressedEvent e) + { + if (Type != SDL_EventType.MouseButtonDown) + { + e = default; + return false; + } + + e = _buttonPressedEvent; + return true; + } + + /// + /// Attempts to retrieve this event as a . + /// + /// The mouse button released event. + /// if this event holds a ; otherwise + public bool TryGetValue(out MouseButtonReleasedEvent e) + { + if (Type != SDL_EventType.MouseButtonUp) + { + e = default; + return false; + } + + e = _buttonReleasedEvent; + return true; + } + + /// + /// Attempts to retrieve this event as a . + /// + /// The mouse moved event. + /// if this event holds a ; otherwise + public bool TryGetValue(out MouseMovedEvent e) + { + if (Type != SDL_EventType.MouseMotion) + { + e = default; + return false; + } + + e = _mouseMovedEvent; + return true; + } + + /// + /// Attempts to retrieve this event as a . + /// + /// The mouse wheel event. + /// if this event holds a ; otherwise + public bool TryGetValue(out MouseWheelEvent e) + { + if (Type != SDL_EventType.KeyUp) + { + e = default; + return false; + } + + e = _wheelEvent; + return true; + } } diff --git a/src/KappaDuck.Quack/Events/EventManager.cs b/src/KappaDuck.Quack/Events/EventManager.cs index 1d7906c..0fe8d86 100644 --- a/src/KappaDuck.Quack/Events/EventManager.cs +++ b/src/KappaDuck.Quack/Events/EventManager.cs @@ -58,8 +58,8 @@ public static void Enable() where TEvent : IEvent /// updates the window's recorded size. This lets you selectively drop events as they arrive. /// /// - /// Only events that would enter the queue are filtered. Events you - /// pass through the filter; events disabled with never reach it. + /// Only events that would enter the queue are filtered; + /// events disabled with never reach it. /// /// /// The filter may run on a background thread, so keep it fast and thread-safe. The exception is @@ -83,7 +83,7 @@ public static EventWatcher Watch(Action callback) { unsafe { - callback(EventType.Convert(*e)); + callback(EventType.Convert(in *e)); } return true; @@ -100,7 +100,7 @@ private static byte OnFilter(void* data, SDL_Event* e) unsafe { SDL_Event native = *e; - Event evt = EventType.Convert(native); + Event evt = EventType.Convert(in native); return filter(evt) ? (byte)1 : (byte)0; } } diff --git a/src/KappaDuck.Quack/Events/EventQueue.cs b/src/KappaDuck.Quack/Events/EventQueue.cs index 33df6b7..90635ee 100644 --- a/src/KappaDuck.Quack/Events/EventQueue.cs +++ b/src/KappaDuck.Quack/Events/EventQueue.cs @@ -61,7 +61,7 @@ public static int Peek(Span events) SDLThrowHelper.ThrowIfNegative(count); for (int i = 0; i < count; i++) - events[i] = EventType.Convert(buffer[i]); + events[i] = EventType.Convert(in buffer[i]); return count; } @@ -96,7 +96,7 @@ public static int Peek(Span events) where TEvent : IEvent SDLThrowHelper.ThrowIfNegative(count); for (int i = 0; i < count; i++) - events[i] = (TEvent)EventType.Convert(buffer[i]).Value!; + events[i] = (TEvent)EventType.Convert(in buffer[i]).Value!; return count; } @@ -116,7 +116,7 @@ public static bool Poll(out Event e) return false; } - e = EventType.Convert(native); + e = EventType.Convert(in native); return true; } @@ -133,44 +133,6 @@ public static bool Poll(out Event e) /// public static void Pump() => SDL3.PumpEvents(); - /// - /// Adds the specified event to the event queue. - /// - /// The event to push onto the queue. - /// if the event was pushed; otherwise, if the event was filtered or the event queue being full. - public static bool Push(Event e) - { - SDL_Event native = EventType.Convert(e); - return SDL3.PushEvent(&native); - } - - /// - /// Adds the events to the event queue. - /// - /// - /// If is empty, it will return 0. - /// - /// The events to push onto the queue. - /// The number of events successfully pushed onto the queue. - /// Thrown when failing to push events. - public static int Push(ReadOnlySpan events) - { - if (events.IsEmpty) - return 0; - - Span buffer = events.Length <= 32 - ? stackalloc SDL_Event[events.Length] - : new SDL_Event[events.Length]; - - for (int i = 0; i < buffer.Length; i++) - buffer[i] = EventType.Convert(events[i]); - - int count = SDL3.PeepEvents(buffer, buffer.Length, SDL_EventAction.Add, EventType.None, EventType.End); - SDLThrowHelper.ThrowIfNegative(count); - - return count; - } - /// /// Runs over the events currently in the queue, keeping those for /// which it returns and removing the rest. @@ -189,7 +151,7 @@ public static void Retain(Predicate match) { unsafe { - return match(EventType.Convert(*e)); + return match(EventType.Convert(in *e)); } }); } @@ -221,7 +183,7 @@ public static int Retrieve(Span events) SDLThrowHelper.ThrowIfNegative(count); for (int i = 0; i < count; i++) - events[i] = EventType.Convert(buffer[i]); + events[i] = EventType.Convert(in buffer[i]); return count; } @@ -256,7 +218,7 @@ public static int Retrieve(Span events) where TEvent : IEvent SDLThrowHelper.ThrowIfNegative(count); for (int i = 0; i < count; i++) - events[i] = (TEvent)EventType.Convert(buffer[i]).Value!; + events[i] = (TEvent)EventType.Convert(in buffer[i]).Value!; return count; } @@ -277,13 +239,13 @@ public static bool Wait(out Event e, TimeSpan? timeout = null) if (!timeout.HasValue || timeout == Timeout.InfiniteTimeSpan) { SDL3.WaitEvent(out native); - e = EventType.Convert(native); + e = EventType.Convert(in native); return e.HasValue; } SDL3.WaitEventTimeout(out native, (int)timeout.Value.TotalMilliseconds); - e = EventType.Convert(native); + e = EventType.Convert(in native); return e.HasValue; } diff --git a/src/KappaDuck.Quack/Events/EventType.cs b/src/KappaDuck.Quack/Events/EventType.cs index 23b350e..24e8917 100644 --- a/src/KappaDuck.Quack/Events/EventType.cs +++ b/src/KappaDuck.Quack/Events/EventType.cs @@ -42,97 +42,37 @@ internal static SDL_EventType Of() where TEvent : IEvent if (type == typeof(KeyReleasedEvent)) return SDL_EventType.KeyUp; + if (type == typeof(MouseButtonPressedEvent)) + return SDL_EventType.MouseButtonDown; + + if (type == typeof(MouseButtonReleasedEvent)) + return SDL_EventType.MouseButtonUp; + + if (type == typeof(MouseMovedEvent)) + return SDL_EventType.MouseMotion; + + if (type == typeof(MouseWheelEvent)) + return SDL_EventType.MouseWheel; + return None; } [SuppressMessage("Style", "IDE0072:Add missing cases", Justification = "The remaining types will be implemented in the future")] - internal static Event Convert(SDL_Event e) => e.Type switch + internal static Event Convert(in SDL_Event e) => e.Type switch { SDL_EventType.Quit => new QuitRequestedEvent(), SDL_EventType.LocaleChanged => new CultureChangedEvent(), SDL_EventType.SystemThemeChanged => new ThemeChangedEvent(), - SDL_EventType.KeyboardAdded => new KeyboardAddedEvent(e.KeyboardDevice.Which), - SDL_EventType.KeyboardRemoved => new KeyboardAddedEvent(e.KeyboardDevice.Which), - SDL_EventType.MouseAdded => new MouseAddedEvent(e.MouseDevice.Which), - SDL_EventType.MouseRemoved => new MouseRemovedEvent(e.MouseDevice.Which), - SDL_EventType.KeyDown => new KeyPressedEvent(e), - SDL_EventType.KeyUp => new KeyReleasedEvent(e), + SDL_EventType.KeyboardAdded => new KeyboardAddedEvent(e.KeyboardDevice), + SDL_EventType.KeyboardRemoved => new KeyboardRemovedEvent(e.KeyboardDevice), + SDL_EventType.MouseAdded => new MouseAddedEvent(e.MouseDevice), + SDL_EventType.MouseRemoved => new MouseRemovedEvent(e.MouseDevice), + SDL_EventType.KeyDown => new KeyPressedEvent(e.Keyboard), + SDL_EventType.KeyUp => new KeyReleasedEvent(e.Keyboard), + SDL_EventType.MouseButtonDown => new MouseButtonPressedEvent(e.Button), + SDL_EventType.MouseButtonUp => new MouseButtonReleasedEvent(e.Button), + SDL_EventType.MouseMotion => new MouseMovedEvent(e.Motion), + SDL_EventType.MouseWheel => new MouseWheelEvent(e.Wheel), _ => default }; - - internal static SDL_Event Convert(Event e) - { - SDL_Event native = new() { Type = e.Type }; - - if (e.Type == SDL_EventType.Quit) - { - native.Quit = new SDL_QuitEvent(); - return native; - } - - if (e.Type == SDL_EventType.KeyboardAdded) - { - e.TryGetValue(out KeyboardAddedEvent keyboardAddedEvent); - native.KeyboardDevice = new SDL_KeyboardDeviceEvent(keyboardAddedEvent.Device.Id, SDL_EventType.KeyboardAdded); - - return native; - } - - if (e.Type == SDL_EventType.KeyboardAdded) - { - e.TryGetValue(out KeyboardRemovedEvent keyboardRemovedEvent); - native.KeyboardDevice = new SDL_KeyboardDeviceEvent(keyboardRemovedEvent.Device.Id, SDL_EventType.KeyboardRemoved); - - return native; - } - - if (e.Type == SDL_EventType.MouseAdded) - { - e.TryGetValue(out MouseAddedEvent mouseAddedEvent); - native.MouseDevice = new SDL_MouseDeviceEvent(mouseAddedEvent.Device.Id, SDL_EventType.MouseAdded); - - return native; - } - - if (e.Type == SDL_EventType.KeyboardAdded) - { - e.TryGetValue(out MouseRemovedEvent mouseRemovedEvent); - native.MouseDevice = new SDL_MouseDeviceEvent(mouseRemovedEvent.Device.Id, SDL_EventType.MouseRemoved); - - return native; - } - - if (e.Type == SDL_EventType.KeyDown) - { - e.TryGetValue(out KeyPressedEvent keyPressedEvent); - native.Keyboard = new SDL_KeyboardEvent() - { - Type = SDL_EventType.KeyDown, - Which = keyPressedEvent.Which, - Scancode = keyPressedEvent.Code, - Key = keyPressedEvent.Key, - Mod = keyPressedEvent.Modifier, - Repeat = keyPressedEvent.Repeat ? (byte)1 : (byte)0 - }; - - return native; - } - - if (e.Type == SDL_EventType.KeyUp) - { - e.TryGetValue(out KeyReleasedEvent keyReleasedEvent); - native.Keyboard = new SDL_KeyboardEvent() - { - Type = SDL_EventType.KeyUp, - Which = keyReleasedEvent.Which, - Scancode = keyReleasedEvent.Code, - Key = keyReleasedEvent.Key, - Mod = keyReleasedEvent.Modifier - }; - - return native; - } - - return native; - } } diff --git a/src/KappaDuck.Quack/Events/KeyPressedEvent.cs b/src/KappaDuck.Quack/Events/KeyPressedEvent.cs index cdcc7d2..8999436 100644 --- a/src/KappaDuck.Quack/Events/KeyPressedEvent.cs +++ b/src/KappaDuck.Quack/Events/KeyPressedEvent.cs @@ -2,26 +2,33 @@ // Licensed under the MIT license. using KappaDuck.Quack.Input; +using KappaDuck.Quack.Input.Devices; using KappaDuck.Quack.Interop.SDL.Primitives.Events; namespace KappaDuck.Quack.Events; /// -/// Represents an event which a key has been pressed. +/// Raised when a key has been pressed. /// public readonly struct KeyPressedEvent { - internal KeyPressedEvent(SDL_Event e) + internal KeyPressedEvent(SDL_KeyboardEvent e) { - Which = e.Keyboard.Which; - Code = e.Keyboard.Scancode; - Key = e.Keyboard.Key; - Modifier = e.Keyboard.Mod; - Repeat = e.Keyboard.Repeat != 0; + WindowId = e.WindowId; + Which = e.Which; + Code = e.Scancode; + Key = e.Key; + Modifier = e.Mod; + Repeat = e.Repeat != 0; } /// - /// Gets the keyboard instance id. + /// Gets the window id on which the key is pressed. + /// + public uint WindowId { get; } + + /// + /// Gets the keyboard instance id which the key pressed. /// public uint Which { get; init; } @@ -44,4 +51,9 @@ internal KeyPressedEvent(SDL_Event e) /// Gets a value indicating whether is a key repeat. /// public bool Repeat { get; init; } + + /// + /// Gets the keyboard device which the key is pressed. + /// + public KeyboardDevice Device => KeyboardDevices.FromId(Which); } diff --git a/src/KappaDuck.Quack/Events/KeyReleasedEvent.cs b/src/KappaDuck.Quack/Events/KeyReleasedEvent.cs index bd906d5..df38212 100644 --- a/src/KappaDuck.Quack/Events/KeyReleasedEvent.cs +++ b/src/KappaDuck.Quack/Events/KeyReleasedEvent.cs @@ -2,25 +2,32 @@ // Licensed under the MIT license. using KappaDuck.Quack.Input; +using KappaDuck.Quack.Input.Devices; using KappaDuck.Quack.Interop.SDL.Primitives.Events; namespace KappaDuck.Quack.Events; /// -/// Represents an event which a key has been released. +/// Raised when a key has been released. /// public readonly struct KeyReleasedEvent { - internal KeyReleasedEvent(SDL_Event e) + internal KeyReleasedEvent(SDL_KeyboardEvent e) { - Which = e.Keyboard.Which; - Code = e.Keyboard.Scancode; - Key = e.Keyboard.Key; - Modifier = e.Keyboard.Mod; + WindowId = e.WindowId; + Which = e.Which; + Code = e.Scancode; + Key = e.Key; + Modifier = e.Mod; } /// - /// Gets the keyboard instance id. + /// Gets the window id on which the key is released. + /// + public uint WindowId { get; } + + /// + /// Gets the keyboard device id which the key released. /// public uint Which { get; init; } @@ -38,4 +45,9 @@ internal KeyReleasedEvent(SDL_Event e) /// Gets the current modifiers. /// public Keymod Modifier { get; init; } + + /// + /// Gets the keyboard device which the key is released. + /// + public KeyboardDevice Device => KeyboardDevices.FromId(Which); } diff --git a/src/KappaDuck.Quack/Events/KeyboardAddedEvent.cs b/src/KappaDuck.Quack/Events/KeyboardAddedEvent.cs index 6a27e12..8d44662 100644 --- a/src/KappaDuck.Quack/Events/KeyboardAddedEvent.cs +++ b/src/KappaDuck.Quack/Events/KeyboardAddedEvent.cs @@ -2,17 +2,24 @@ // Licensed under the MIT license. using KappaDuck.Quack.Input.Devices; +using KappaDuck.Quack.Interop.SDL.Primitives.Events; namespace KappaDuck.Quack.Events; /// -/// Represents an event which a new keyboard device was connected. +/// Raised when a new keyboard device was connected. /// -/// The keyboard id which was added. -public readonly struct KeyboardAddedEvent(uint keyboardId) +public readonly struct KeyboardAddedEvent { + internal KeyboardAddedEvent(SDL_KeyboardDeviceEvent e) => Which = e.Which; + + /// + /// Gets the keyboard device id which was added. + /// + public uint Which { get; } + /// /// Gets the keyboard device which was added. /// - public KeyboardDevice Device => KeyboardDevices.FromId(keyboardId); + public KeyboardDevice Device => KeyboardDevices.FromId(Which); } diff --git a/src/KappaDuck.Quack/Events/KeyboardRemovedEvent.cs b/src/KappaDuck.Quack/Events/KeyboardRemovedEvent.cs index 063fbe1..85af621 100644 --- a/src/KappaDuck.Quack/Events/KeyboardRemovedEvent.cs +++ b/src/KappaDuck.Quack/Events/KeyboardRemovedEvent.cs @@ -2,17 +2,24 @@ // Licensed under the MIT license. using KappaDuck.Quack.Input.Devices; +using KappaDuck.Quack.Interop.SDL.Primitives.Events; namespace KappaDuck.Quack.Events; /// -/// Represents an event which a keyboard device was disconnected. +/// Raised when a keyboard device was disconnected. /// -/// The keyboard id which was removed. -public readonly struct KeyboardRemovedEvent(uint keyboardId) +public readonly struct KeyboardRemovedEvent { + internal KeyboardRemovedEvent(SDL_KeyboardDeviceEvent e) => Which = e.Which; + + /// + /// Gets the keyboard device id which was removed. + /// + public uint Which { get; } + /// /// Gets the keyboard device which was removed. /// - public KeyboardDevice Device => KeyboardDevices.FromId(keyboardId); + public KeyboardDevice Device => KeyboardDevices.FromId(Which); } diff --git a/src/KappaDuck.Quack/Events/MouseAddedEvent.cs b/src/KappaDuck.Quack/Events/MouseAddedEvent.cs index ecd70ba..08c1a5f 100644 --- a/src/KappaDuck.Quack/Events/MouseAddedEvent.cs +++ b/src/KappaDuck.Quack/Events/MouseAddedEvent.cs @@ -2,17 +2,24 @@ // Licensed under the MIT license. using KappaDuck.Quack.Input.Devices; +using KappaDuck.Quack.Interop.SDL.Primitives.Events; namespace KappaDuck.Quack.Events; /// -/// Represents an event which a new mouse device was connected. +/// Raised when a new mouse device was connected. /// -/// The mouse id which was added. -public readonly struct MouseAddedEvent(uint mouseId) +public readonly struct MouseAddedEvent { + internal MouseAddedEvent(SDL_MouseDeviceEvent e) => Which = e.Which; + + /// + /// Gets the mouse device id which was added. + /// + public uint Which { get; } + /// /// Gets the mouse device which was added. /// - public MouseDevice Device => MouseDevices.FromId(mouseId); + public MouseDevice Device => MouseDevices.FromId(Which); } diff --git a/src/KappaDuck.Quack/Events/MouseButtonPressedEvent.cs b/src/KappaDuck.Quack/Events/MouseButtonPressedEvent.cs new file mode 100644 index 0000000..192d8bd --- /dev/null +++ b/src/KappaDuck.Quack/Events/MouseButtonPressedEvent.cs @@ -0,0 +1,54 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +using KappaDuck.Quack.Input; +using KappaDuck.Quack.Input.Devices; +using KappaDuck.Quack.Interop.SDL.Primitives.Events; +using System.Drawing; + +namespace KappaDuck.Quack.Events; + +/// +/// Raised when a mouse button is pressed. +/// +public readonly struct MouseButtonPressedEvent : IEvent +{ + internal MouseButtonPressedEvent(SDL_MouseButtonEvent e) + { + WindowId = e.WindowId; + Which = e.Which; + Button = e.Button; + Position = new PointF(e.X, e.Y); + Clicks = e.Clicks; + } + + /// + /// Gets the window id on which the mouse button pressed. + /// + public uint WindowId { get; } + + /// + /// Gets the mouse id which the mouse button pressed. + /// + public uint Which { get; } + + /// + /// Gets the button that was pressed. + /// + public MouseButton Button { get; } + + /// + /// Gets the cursor position, relative to the top-left of the window, when the button was pressed. + /// + public PointF Position { get; } + + /// + /// Gets the consecutive click count: 1 for a single click, 2 for a double click, and so on. + /// + public int Clicks { get; } + + /// + /// Gets the mouse device which the button was pressed. + /// + public MouseDevice Device => MouseDevices.FromId(Which); +} diff --git a/src/KappaDuck.Quack/Events/MouseButtonReleasedEvent.cs b/src/KappaDuck.Quack/Events/MouseButtonReleasedEvent.cs new file mode 100644 index 0000000..633a0e8 --- /dev/null +++ b/src/KappaDuck.Quack/Events/MouseButtonReleasedEvent.cs @@ -0,0 +1,48 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +using KappaDuck.Quack.Input; +using KappaDuck.Quack.Input.Devices; +using KappaDuck.Quack.Interop.SDL.Primitives.Events; +using System.Drawing; + +namespace KappaDuck.Quack.Events; + +/// +/// Raised when a mouse button is released. +/// +public readonly struct MouseButtonReleasedEvent : IEvent +{ + internal MouseButtonReleasedEvent(SDL_MouseButtonEvent e) + { + WindowId = e.WindowId; + Which = e.Which; + Button = e.Button; + Position = new PointF(e.X, e.Y); + } + + /// + /// Gets the window id on which the mouse button released. + /// + public uint WindowId { get; } + + /// + /// Gets the mouse id which the mouse button released. + /// + public uint Which { get; } + + /// + /// Gets the button that was released. + /// + public MouseButton Button { get; } + + /// + /// Gets the cursor position, relative to the top-left of the window, when the button was released. + /// + public PointF Position { get; } + + /// + /// Gets the mouse device which the button was released. + /// + public MouseDevice Device => MouseDevices.FromId(Which); +} diff --git a/src/KappaDuck.Quack/Events/MouseMovedEvent.cs b/src/KappaDuck.Quack/Events/MouseMovedEvent.cs new file mode 100644 index 0000000..c758089 --- /dev/null +++ b/src/KappaDuck.Quack/Events/MouseMovedEvent.cs @@ -0,0 +1,55 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +using KappaDuck.Quack.Input; +using KappaDuck.Quack.Input.Devices; +using KappaDuck.Quack.Interop.SDL.Primitives.Events; +using System.Drawing; +using System.Numerics; + +namespace KappaDuck.Quack.Events; + +/// +/// Raised when the mouse moves. +/// +public readonly struct MouseMovedEvent : IEvent +{ + internal MouseMovedEvent(SDL_MouseMotionEvent e) + { + WindowId = e.WindowId; + Which = e.Which; + Position = new PointF(e.X, e.Y); + Delta = new Vector2(e.Xrel, e.Yrel); + Buttons = e.State; + } + + /// + /// Gets the window id on which the mouse moved. + /// + public uint WindowId { get; } + + /// + /// Gets the mouse id which moved. + /// + public uint Which { get; } + + /// + /// Gets the cursor position, relative to the top-left of the window. + /// + public PointF Position { get; } + + /// + /// Gets the motion since the previous event. + /// + public Vector2 Delta { get; } + + /// + /// Gets the buttons held while the mouse moved + /// + public MouseButtonState Buttons { get; } + + /// + /// Gets the mouse device which moved. + /// + public MouseDevice Device => MouseDevices.FromId(Which); +} diff --git a/src/KappaDuck.Quack/Events/MouseRemovedEvent.cs b/src/KappaDuck.Quack/Events/MouseRemovedEvent.cs index f18eb3c..e5ee5e8 100644 --- a/src/KappaDuck.Quack/Events/MouseRemovedEvent.cs +++ b/src/KappaDuck.Quack/Events/MouseRemovedEvent.cs @@ -2,17 +2,24 @@ // Licensed under the MIT license. using KappaDuck.Quack.Input.Devices; +using KappaDuck.Quack.Interop.SDL.Primitives.Events; namespace KappaDuck.Quack.Events; /// -/// Represents an event which a mouse device was disconnected. +/// Raised when a mouse device was disconnected. /// -/// The mouse id which was removed. -public readonly struct MouseRemovedEvent(uint mouseId) +public readonly struct MouseRemovedEvent { + internal MouseRemovedEvent(SDL_MouseDeviceEvent e) => Which = e.Which; + + /// + /// Gets the mouse device id which was removed. + /// + public uint Which { get; } + /// /// Gets the mouse device which was removed. /// - public MouseDevice Device => MouseDevices.FromId(mouseId); + public MouseDevice Device => MouseDevices.FromId(Which); } diff --git a/src/KappaDuck.Quack/Events/MouseWheelEvent.cs b/src/KappaDuck.Quack/Events/MouseWheelEvent.cs new file mode 100644 index 0000000..e08c242 --- /dev/null +++ b/src/KappaDuck.Quack/Events/MouseWheelEvent.cs @@ -0,0 +1,52 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +using KappaDuck.Quack.Input.Devices; +using KappaDuck.Quack.Interop.SDL.Primitives; +using KappaDuck.Quack.Interop.SDL.Primitives.Events; +using System.Drawing; +using System.Numerics; + +namespace KappaDuck.Quack.Events; + +/// +/// Raised when the mouse wheel is scrolled. +/// +public readonly struct MouseWheelEvent : IEvent +{ + internal MouseWheelEvent(SDL_MouseWheelEvent e) + { + float sign = e.Direction is SDL_MouseWheelDirection.Flipped ? -1f : 1f; + + WindowId = e.WindowId; + Which = e.Which; + Delta = new Vector2(e.X * sign, e.Y * sign); + Position = new PointF(e.MouseX, e.MouseY); + } + + /// + /// Gets the window id on which the mouse scrolled. + /// + public uint WindowId { get; } + + /// + /// Gets the mouse id which scrolled. + /// + public uint Which { get; } + + /// + /// Gets the scroll amount. Positive Y scrolls away from the user and positive X to the + /// right, regardless of the platform's natural-scrolling setting. + /// + public Vector2 Delta { get; } + + /// + /// Gets the cursor position, relative to the top-left of the window, when the wheel was scrolled. + /// + public PointF Position { get; } + + /// + /// Gets the mouse device which scrolled. + /// + public MouseDevice Device => MouseDevices.FromId(Which); +} diff --git a/src/KappaDuck.Quack/Events/QuitRequestedEvent.cs b/src/KappaDuck.Quack/Events/QuitRequestedEvent.cs index a495f75..bf8e908 100644 --- a/src/KappaDuck.Quack/Events/QuitRequestedEvent.cs +++ b/src/KappaDuck.Quack/Events/QuitRequestedEvent.cs @@ -4,7 +4,7 @@ namespace KappaDuck.Quack.Events; /// -/// Represents a request to quit the application, raised when the user closes the -/// last window or the operating system asks the application to terminate. +/// raised when the user closes the last window or +/// the operating system asks the application to terminate. /// public readonly struct QuitRequestedEvent : IEvent; diff --git a/src/KappaDuck.Quack/Events/ThemeChangedEvent.cs b/src/KappaDuck.Quack/Events/ThemeChangedEvent.cs index 26b8cf2..9555aae 100644 --- a/src/KappaDuck.Quack/Events/ThemeChangedEvent.cs +++ b/src/KappaDuck.Quack/Events/ThemeChangedEvent.cs @@ -4,6 +4,6 @@ namespace KappaDuck.Quack.Events; /// -/// Represents an event where the user's theme has been changed. +/// Raised when the user's theme has been changed. /// public readonly struct ThemeChangedEvent : IEvent; diff --git a/src/KappaDuck.Quack/Input/MouseButton.cs b/src/KappaDuck.Quack/Input/MouseButton.cs new file mode 100644 index 0000000..b7fc710 --- /dev/null +++ b/src/KappaDuck.Quack/Input/MouseButton.cs @@ -0,0 +1,35 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +namespace KappaDuck.Quack.Input; + +/// +/// Represents a mouse button. +/// +public enum MouseButton : byte +{ + /// + /// The left mouse button. + /// + Left = 1, + + /// + /// The middle mouse button, usually the wheel. + /// + Middle = 2, + + /// + /// The right mouse button. + /// + Right = 3, + + /// + /// The first extra mouse button, usually the side button. + /// + X1 = 4, + + /// + /// The second extra mouse button, usually the side button. + /// + X2 = 5 +} diff --git a/src/KappaDuck.Quack/Input/MouseButtonState.cs b/src/KappaDuck.Quack/Input/MouseButtonState.cs new file mode 100644 index 0000000..b195ea6 --- /dev/null +++ b/src/KappaDuck.Quack/Input/MouseButtonState.cs @@ -0,0 +1,41 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +namespace KappaDuck.Quack.Input; + +/// +/// A bitmask of the mouse buttons currently held. +/// +[Flags] +public enum MouseButtonState : uint +{ + /// + /// No button is held. + /// + None = 0, + + /// + /// The left mouse button. + /// + Left = 1 << 0, + + /// + /// The middle mouse button, usually the wheel. + /// + Middle = 1 << 1, + + /// + /// The right mouse button. + /// + Right = 1 << 2, + + /// + /// The first extra mouse button, usually the side button. + /// + X1 = 1 << 3, + + /// + /// The second extra mouse button, usually the side button. + /// + X2 = 1 << 4 +} diff --git a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_Event.cs b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_Event.cs index 862f229..2d9c3af 100644 --- a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_Event.cs +++ b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_Event.cs @@ -4,20 +4,26 @@ namespace KappaDuck.Quack.Interop.SDL.Primitives.Events; [StructLayout(LayoutKind.Explicit, Size = 128)] -internal struct SDL_Event +internal readonly struct SDL_Event { [field: FieldOffset(0)] - internal SDL_EventType Type { get; set; } + internal SDL_EventType Type { get; } [field: FieldOffset(0)] - internal SDL_QuitEvent Quit { get; set; } + internal SDL_KeyboardDeviceEvent KeyboardDevice { get; } [field: FieldOffset(0)] - internal SDL_KeyboardDeviceEvent KeyboardDevice { get; set; } + internal SDL_MouseDeviceEvent MouseDevice { get; } [field: FieldOffset(0)] - internal SDL_MouseDeviceEvent MouseDevice { get; set; } + internal SDL_KeyboardEvent Keyboard { get; } [field: FieldOffset(0)] - internal SDL_KeyboardEvent Keyboard { get; set; } + internal SDL_MouseMotionEvent Motion { get; } + + [field: FieldOffset(0)] + internal SDL_MouseButtonEvent Button { get; } + + [field: FieldOffset(0)] + internal SDL_MouseWheelEvent Wheel { get; } } diff --git a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_KeyboardEvent.cs b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_KeyboardEvent.cs index 8c5a148..61513b6 100644 --- a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_KeyboardEvent.cs +++ b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_KeyboardEvent.cs @@ -8,11 +8,11 @@ namespace KappaDuck.Quack.Interop.SDL.Primitives.Events; [StructLayout(LayoutKind.Sequential)] internal readonly struct SDL_KeyboardEvent { - internal SDL_EventType Type { get; init; } - + private readonly SDL_EventType _type; private readonly uint _reserved; private readonly ulong _timestamp; - private readonly uint _windowId; + + internal uint WindowId { get; } internal uint Which { get; init; } diff --git a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseButtonEvent.cs b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseButtonEvent.cs new file mode 100644 index 0000000..6859cf9 --- /dev/null +++ b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseButtonEvent.cs @@ -0,0 +1,30 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +using KappaDuck.Quack.Input; + +namespace KappaDuck.Quack.Interop.SDL.Primitives.Events; + +[StructLayout(LayoutKind.Sequential)] +internal readonly struct SDL_MouseButtonEvent +{ + private readonly SDL_EventType _type; + private readonly uint _reserved; + private readonly ulong _timestamp; + + internal uint WindowId { get; } + + internal uint Which { get; } + + internal MouseButton Button { get; } + + private readonly byte _down; + + internal byte Clicks { get; } + + private readonly byte _padding; + + internal float X { get; } + + internal float Y { get; } +} diff --git a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseDeviceEvent.cs b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseDeviceEvent.cs index 5926758..f41c855 100644 --- a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseDeviceEvent.cs +++ b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseDeviceEvent.cs @@ -4,11 +4,11 @@ namespace KappaDuck.Quack.Interop.SDL.Primitives.Events; [StructLayout(LayoutKind.Sequential)] -internal readonly struct SDL_MouseDeviceEvent(uint which, SDL_EventType type) +internal readonly struct SDL_MouseDeviceEvent { - private readonly SDL_EventType _type = type; + private readonly SDL_EventType _type; private readonly uint _reserved; private readonly ulong _timestamp; - internal readonly uint Which { get; } = which; + internal readonly uint Which { get; } } diff --git a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseMotionEvent.cs b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseMotionEvent.cs new file mode 100644 index 0000000..947800b --- /dev/null +++ b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseMotionEvent.cs @@ -0,0 +1,28 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +using KappaDuck.Quack.Input; + +namespace KappaDuck.Quack.Interop.SDL.Primitives.Events; + +[StructLayout(LayoutKind.Sequential)] +internal readonly struct SDL_MouseMotionEvent +{ + private readonly SDL_EventType _type; + private readonly uint _reserved; + private readonly ulong _timestamp; + + internal uint WindowId { get; } + + internal uint Which { get; } + + internal MouseButtonState State { get; } + + internal float X { get; } + + internal float Y { get; } + + internal float Xrel { get; } + + internal float Yrel { get; } +} diff --git a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseWheelEvent.cs b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseWheelEvent.cs new file mode 100644 index 0000000..1c81e48 --- /dev/null +++ b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_MouseWheelEvent.cs @@ -0,0 +1,29 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +namespace KappaDuck.Quack.Interop.SDL.Primitives.Events; + +[StructLayout(LayoutKind.Sequential)] +internal readonly struct SDL_MouseWheelEvent +{ + private readonly SDL_EventType _type; + private readonly uint _reserved; + private readonly ulong _timestamp; + + internal uint WindowId { get; } + + internal uint Which { get; } + + internal float X { get; } + + internal float Y { get; } + + internal SDL_MouseWheelDirection Direction { get; } + + internal float MouseX { get; } + + internal float MouseY { get; } + + private readonly int _integerX; + private readonly int _integerY; +} diff --git a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_QuitEvent.cs b/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_QuitEvent.cs deleted file mode 100644 index a07af80..0000000 --- a/src/KappaDuck.Quack/Interop/SDL/Primitives/Events/SDL_QuitEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) KappaDuck. -// Licensed under the MIT license. - -namespace KappaDuck.Quack.Interop.SDL.Primitives.Events; - -[StructLayout(LayoutKind.Sequential)] -internal readonly struct SDL_QuitEvent() -{ - private readonly SDL_EventType _type = SDL_EventType.Quit; - private readonly uint _reserved; - private readonly ulong _timestamp; -} diff --git a/src/KappaDuck.Quack/Interop/SDL/Primitives/SDL_MouseWheelDirection.cs b/src/KappaDuck.Quack/Interop/SDL/Primitives/SDL_MouseWheelDirection.cs new file mode 100644 index 0000000..f151c04 --- /dev/null +++ b/src/KappaDuck.Quack/Interop/SDL/Primitives/SDL_MouseWheelDirection.cs @@ -0,0 +1,10 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +namespace KappaDuck.Quack.Interop.SDL.Primitives; + +internal enum SDL_MouseWheelDirection +{ + Normal = 0, + Flipped = 1 +} diff --git a/src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs b/src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs index c3b7392..96c4801 100644 --- a/src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs +++ b/src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs @@ -8,6 +8,15 @@ namespace KappaDuck.Quack.Interop.SDL; internal static partial class SDL3 { + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_CaptureMouse")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [return: MarshalAs(UnmanagedType.I1)] + internal static partial bool CaptureMouse([MarshalAs(UnmanagedType.I1)] bool enabled); + + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_GetGlobalMouseState")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial MouseButtonState GetGlobalMouseState(out float x, out float y); + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_GetKeyFromScancode")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] internal static partial Key GetKeyFromScancode(Scancode code, Keymod mod, [MarshalAs(UnmanagedType.I1)] bool keyEvents); @@ -50,6 +59,14 @@ internal static partial class SDL3 [return: MarshalUsing(typeof(SDLStringMarshaller))] internal static partial string GetMouseNameById(uint id); + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_GetMouseState")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial MouseButtonState GetMouseState(out float x, out float y); + + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_GetRelativeMouseState")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial MouseButtonState GetRelativeMouseState(out float x, out float y); + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_GetScancodeFromKey")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] internal static partial Scancode GetScancodeFromKey(Key key, Keymod* mod); @@ -70,7 +87,7 @@ internal static partial class SDL3 [LibraryImport(nameof(SDL3), EntryPoint = "SDL_HasMouse")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - [return: MarshalAs(UnmanagedType.U1)] + [return: MarshalAs(UnmanagedType.I1)] internal static partial bool HasMouse(); [LibraryImport(nameof(SDL3), EntryPoint = "SDL_HasScreenKeyboardSupport")] @@ -94,4 +111,9 @@ internal static partial class SDL3 [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] [return: MarshalAs(UnmanagedType.I1)] internal static partial bool SetScancodeName(Scancode code, Span name); + + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_WarpMouseGlobal")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [return: MarshalAs(UnmanagedType.I1)] + internal static partial bool WarpMouseGlobal(float x, float y); } diff --git a/tests/Integration.Tests/Events/EventQueueTests.cs b/tests/Integration.Tests/Events/EventQueueTests.cs deleted file mode 100644 index 141ebfd..0000000 --- a/tests/Integration.Tests/Events/EventQueueTests.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) KappaDuck. -// Licensed under the MIT license. - -using KappaDuck.Quack.Events; - -namespace Integration.Tests.Events; - -[NotInParallel] -internal sealed class EventQueueTests -{ - [Before(Test)] - public void FlushEvents() - { - EventQueue.Pump(); - EventQueue.Flush(); - } - - [Test] - public async Task PeekShouldReturnZeroWhenDestinationIsEmpty() - { - Span events = []; - - int count = EventQueue.Peek(events); - await count.Should().BeZero(); - } - - [Test] - public async Task PeekShouldFillDestination() - { - Span events = new Event[3]; - - EventQueue.Push(new QuitRequestedEvent()); - EventQueue.Push(new QuitRequestedEvent()); - - int count = EventQueue.Peek(events); - - await count.Should().BeEqualTo(2); - } - - [Test] - public async Task PeekOfTypeShouldReturnZeroWhenDestinationIsEmpty() - { - Span events = []; - - int count = EventQueue.Peek(events); - await count.Should().BeZero(); - } - - [Test] - public async Task PeekOfTypeShouldFillDestination() - { - Span events = new QuitRequestedEvent[3]; - - EventQueue.Push(new QuitRequestedEvent()); - EventQueue.Push(new QuitRequestedEvent()); - - int count = EventQueue.Peek(events); - - await count.Should().BeEqualTo(2); - } - - [Test] - public async Task PushShouldReturnZeroWhenDestinationIsEmpty() - { - Span events = []; - - int count = EventQueue.Push(events); - await count.Should().BeZero(); - } - - [Test] - public async Task PushShouldPopulateTheQueue() - { - Span events = [new QuitRequestedEvent()]; - - int count = EventQueue.Push(events); - await count.Should().BeEqualTo(1); - } - - [Test] - public async Task RetrieveShouldReturnZeroWhenDestinationIsEmpty() - { - Span events = []; - - int count = EventQueue.Retrieve(events); - await count.Should().BeZero(); - } - - [Test] - public async Task RetrieveShouldPopulateTheQueue() - { - Span events = new Event[3]; - - EventQueue.Push(new QuitRequestedEvent()); - EventQueue.Push(new QuitRequestedEvent()); - - int count = EventQueue.Retrieve(events); - await count.Should().BeEqualTo(2); - } - - [Test] - public async Task RetrieveOfTypeShouldReturnZeroWhenDestinationIsEmpty() - { - Span events = []; - - int count = EventQueue.Retrieve(events); - await count.Should().BeZero(); - } - - [Test] - public async Task RetrieveOfTypeShouldPopulateTheQueue() - { - Span events = new QuitRequestedEvent[3]; - - EventQueue.Push(new QuitRequestedEvent()); - EventQueue.Push(new QuitRequestedEvent()); - - int count = EventQueue.Retrieve(events); - await count.Should().BeEqualTo(2); - } -} diff --git a/tests/Integration.Tests/SmokeTests.cs b/tests/Integration.Tests/SmokeTests.cs new file mode 100644 index 0000000..2bcfb70 --- /dev/null +++ b/tests/Integration.Tests/SmokeTests.cs @@ -0,0 +1,13 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +namespace Integration.Tests; + +internal sealed class SmokeTests +{ + [Test] + public async Task TrueShouldBeTrue() + { + await true.Should().BeTrue(); + } +} From 1ba581e8d8615a0945b3a1dd43940b549bbf25d5 Mon Sep 17 00:00:00 2001 From: beauchama Date: Fri, 19 Jun 2026 20:48:38 -0400 Subject: [PATCH 2/2] Added Mouse input --- src/KappaDuck.Quack/Input/Mouse.cs | 170 ++++++++++++++++++ src/KappaDuck.Quack/Input/MouseState.cs | 44 +++++ src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs | 15 ++ 3 files changed, 229 insertions(+) create mode 100644 src/KappaDuck.Quack/Input/Mouse.cs create mode 100644 src/KappaDuck.Quack/Input/MouseState.cs diff --git a/src/KappaDuck.Quack/Input/Mouse.cs b/src/KappaDuck.Quack/Input/Mouse.cs new file mode 100644 index 0000000..1348df5 --- /dev/null +++ b/src/KappaDuck.Quack/Input/Mouse.cs @@ -0,0 +1,170 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +using KappaDuck.Quack.Events; +using KappaDuck.Quack.Exceptions; +using KappaDuck.Quack.Geometry; + +namespace KappaDuck.Quack.Input; + +/// +/// Represents a mouse input. +/// +public static class Mouse +{ + /// + /// Gets the asynchronous mouse button state and the desktop-relative platform-cursor position of the mouse. + /// + /// + /// + /// It immediately queries the platform for the current mouse state, more costly than using . + /// + /// + /// In relative mode, the platform-cursor's position usually contradicts the engine-cursor's position as + /// manually calculated from and window's position. + /// + /// + public static MouseState GlobalState + { + get + { + MouseButtonState buttons = SDL3.GetGlobalMouseState(out float x, out float y); + return new MouseState(buttons, new PointF(x, y)); + } + } + + /// + /// Gets the engine cache for synchronous mouse button state and the window-relative position of the mouse. + /// + /// + /// + /// The cache is based on the last pump of the event queue. To query the + /// platform for immediate mouse state, use . + /// + /// + /// In relative mode, the platform-cursor's position usually contradicts the engine-cursor's position as + /// manually calculated from and window's position. + /// + /// + public static MouseState State + { + get + { + MouseButtonState buttons = SDL3.GetMouseState(out float x, out float y); + return new MouseState(buttons, new PointF(x, y)); + } + } + + /// + /// Gets the window-relative position of the cursor, from . + /// + public static PointF Position => State.Position; + + /// + /// Gets or sets a value indicating whether the cursor is visible. + /// + /// Thrown when failed to change the cursor visibility. + public static bool Visible + { + get => SDL3.CursorVisible(); + set => SDLThrowHelper.ThrowIfFailed(value ? SDL3.ShowCursor() : SDL3.HideCursor()); + } + + /// + /// Reads the mouse movement accumulated since the previous call. + /// + /// + /// + /// The movement comes from the engine cache, based on the last pump of the event queue. + /// To query the platform for immediate mouse state, use . + /// + /// + /// Each call consumes the accumulator, so an immediate second call returns near-zero. Read it once per frame. + /// + /// + /// It is useful for reducing overhead by processing relative mouse inputs in one go per-frame + /// instead of individually per-event, at the expense of losing the order between events within the frame + /// (e.g. quickly pressing and releasing a button within the same frame). + /// + /// + /// The movement accumulated since the last call. + public static Vector2 GetRelativeMotion() + { + _ = SDL3.GetRelativeMouseState(out float x, out float y); + return new Vector2(x, y); + } + + /// + /// Capture the mouse and to track input outside the window. + /// + /// + /// + /// Capturing enables your app to obtain mouse events globally, instead of just within your window. + /// Not all video targets support this feature. When capturing is enabled, the current window will get all mouse + /// events, but unlike relative mode, no change is made to the cursor and it is not restrained to your window. + /// + /// + /// This method may also deny mouse input to other windows, both those in your application and others on + /// the system, so you should use this method sparingly and in small bursts. For example, you might want to track + /// the mouse while the user is dragging something, until the user releases a mouse button. + /// It is not recommended that you capture the mouse for long periods of time, such as the entire time + /// your app is running. For that, consider using or , depending on your needs. + /// + /// + /// While captured, mouse events still report coordinates relative to the current (foreground) window, + /// but those coordinates may be outside the bounds of the window (including negative values). + /// Capturing is only allowed for the foreground window. If the window loses focus while capturing, + /// the capture will be disabled automatically. + /// + /// + /// While capturing is enabled, the current window will have the set to . + /// + /// + /// Please note that the engine will attempt to "auto capture" the mouse while the user is pressing a button; + /// this is to try and make mouse behavior more consistent between platforms, and deal with the common case of + /// a user dragging the mouse outside of the window. This means that if you are calling this method only to + /// deal with this situation, you do not have to (although it is safe to do so). + /// + /// + /// value indicating whether to enable or disable mouse capture. + /// Thrown when failed to set mouse capture. + public static void Capture(bool enabled) => SDLThrowHelper.ThrowIfFailed(SDL3.CaptureMouse(enabled)); + + /// + /// Determines whether the specified button is currently pressed in . + /// + /// The button to check. + /// if the button is pressed; otherwise, . + public static bool IsDown(MouseButton button) => State.IsDown(button); + + /// + /// Determines whether the specified button is currently released in . + /// + /// The button to check. + /// if the button is released; otherwise, . + public static bool IsUp(MouseButton button) => State.IsUp(button); + + /// + /// Moves the mouse cursor to the given position in global screen space. + /// + /// + /// It generates a event. + /// It will not move the mouse when used over Microsoft Remote Desktop. + /// + /// The x-coordinate in global screen space. + /// The y-coordinate in global screen space. + /// Thrown when failed to warp the mouse. + public static void Warp(float x, float y) + => SDLThrowHelper.ThrowIfFailed(SDL3.WarpMouseGlobal(x, y)); + + /// + /// Moves the mouse cursor to the given position in global screen space. + /// + /// + /// It generates a event. + /// It will not move the mouse when used over Microsoft Remote Desktop. + /// + /// The position in global screen space. + /// Thrown when failed to warp the mouse. + public static void Warp(PointF position) => Warp(position.X, position.Y); +} diff --git a/src/KappaDuck.Quack/Input/MouseState.cs b/src/KappaDuck.Quack/Input/MouseState.cs new file mode 100644 index 0000000..4eeae63 --- /dev/null +++ b/src/KappaDuck.Quack/Input/MouseState.cs @@ -0,0 +1,44 @@ +// Copyright (c) KappaDuck. +// Licensed under the MIT license. + +using KappaDuck.Quack.Geometry; + +namespace KappaDuck.Quack.Input; + +/// +/// A snapshot of the mouse buttons and cursor position. +/// +public readonly struct MouseState +{ + internal MouseState(MouseButtonState buttons, PointF position) + { + Buttons = buttons; + Position = position; + } + + /// + /// Gets the buttons held in this snapshot. + /// + public MouseButtonState Buttons { get; } + + /// + /// Gets the cursor position captured with this snapshot. + /// + public PointF Position { get; } + + /// + /// Determines whether the given button is held in this snapshot. + /// + /// The button to check. + /// if the button is held; otherwise, . + public bool IsDown(MouseButton button) => (Buttons & ToState(button)) != 0; + + /// + /// Determines whether the given button is released in this snapshot. + /// + /// The button to check. + /// if the button is released; otherwise, . + public bool IsUp(MouseButton button) => !IsDown(button); + + private static MouseButtonState ToState(MouseButton button) => (MouseButtonState)(1 << ((byte)button - 1)); +} diff --git a/src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs b/src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs index 96c4801..b376f52 100644 --- a/src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs +++ b/src/KappaDuck.Quack/Interop/SDL/SDL3.Input.cs @@ -13,6 +13,11 @@ internal static partial class SDL3 [return: MarshalAs(UnmanagedType.I1)] internal static partial bool CaptureMouse([MarshalAs(UnmanagedType.I1)] bool enabled); + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_CursorVisible")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [return: MarshalAs(UnmanagedType.I1)] + internal static partial bool CursorVisible(); + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_GetGlobalMouseState")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] internal static partial MouseButtonState GetGlobalMouseState(out float x, out float y); @@ -90,6 +95,11 @@ internal static partial class SDL3 [return: MarshalAs(UnmanagedType.I1)] internal static partial bool HasMouse(); + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_HideCursor")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [return: MarshalAs(UnmanagedType.I1)] + internal static partial bool HideCursor(); + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_HasScreenKeyboardSupport")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] [return: MarshalAs(UnmanagedType.I1)] @@ -112,6 +122,11 @@ internal static partial class SDL3 [return: MarshalAs(UnmanagedType.I1)] internal static partial bool SetScancodeName(Scancode code, Span name); + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_ShowCursor")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [return: MarshalAs(UnmanagedType.I1)] + internal static partial bool ShowCursor(); + [LibraryImport(nameof(SDL3), EntryPoint = "SDL_WarpMouseGlobal")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] [return: MarshalAs(UnmanagedType.I1)]