diff --git a/Source/Ultraviolet.SDL2/Shared/Input/SDL2MouseDevice.cs b/Source/Ultraviolet.SDL2/Shared/Input/SDL2MouseDevice.cs index 4591cf185..d75e78860 100644 --- a/Source/Ultraviolet.SDL2/Shared/Input/SDL2MouseDevice.cs +++ b/Source/Ultraviolet.SDL2/Shared/Input/SDL2MouseDevice.cs @@ -110,12 +110,51 @@ public override void Update(UltravioletTime time) } - /// - /// Gets the mouse cursor's position within the specified window. - /// - /// The window to evaluate. - /// The cursor's compositor-space position within the specified - /// window, or if the cursor is outside of the window. + /// + public override void WarpToWindow(IUltravioletWindow window, Int32 x, Int32 y) + { + Contract.EnsureNotDisposed(this, Disposed); + Contract.Require(window, nameof(window)); + + window.WarpMouseWithinWindow(x, y); + } + + /// + public override void WarpToWindowCenter(IUltravioletWindow window) + { + Contract.EnsureNotDisposed(this, Disposed); + Contract.Require(window, nameof(window)); + + var size = window.ClientSize; + window.WarpMouseWithinWindow(size.Width / 2, size.Height / 2); + } + + /// + public override void WarpToPrimaryWindow(Int32 x, Int32 y) + { + Contract.EnsureNotDisposed(this, Disposed); + + var primary = Ultraviolet.GetPlatform().Windows.GetPrimary(); + if (primary == null) + throw new InvalidOperationException(UltravioletStrings.NoPrimaryWindow); + + primary.WarpMouseWithinWindow(x, y); + } + + /// + public override void WarpToPrimaryWindowCenter() + { + Contract.EnsureNotDisposed(this, Disposed); + + var primary = Ultraviolet.GetPlatform().Windows.GetPrimary(); + if (primary == null) + throw new InvalidOperationException(UltravioletStrings.NoPrimaryWindow); + + var size = primary.ClientSize; + primary.WarpMouseWithinWindow(size.Width / 2, size.Height / 2); + } + + /// public override Point2? GetPositionInWindow(IUltravioletWindow window) { Contract.Require(window, nameof(window)); @@ -177,6 +216,30 @@ public override Boolean IsButtonDoubleClicked(MouseButton button) return (buttonStateDoubleClicks & SDL_BUTTON(button)) != 0; } + /// + public override Boolean GetIsRelativeModeEnabled() + { + Contract.EnsureNotDisposed(this, Disposed); + + return SDL_GetRelativeMouseMode(); + } + + /// + public override Boolean SetIsRelativeModeEnabled(Boolean enabled) + { + Contract.EnsureNotDisposed(this, Disposed); + + var result = SDL_SetRelativeMouseMode(enabled); + if (result == -1) + return false; + + if (result < 0) + throw new SDL2Exception(); + + relativeMode = enabled; + return true; + } + /// public override IUltravioletWindow Window => window; @@ -284,8 +347,16 @@ private void OnMouseMotion(ref SDL_MouseMotionEvent evt) if (!Ultraviolet.GetInput().EmulateMouseWithTouchInput && evt.which == SDL_TOUCH_MOUSEID) return; - SetMousePosition(evt.windowID, evt.x, evt.y); - OnMoved(window, evt.x, evt.y, evt.xrel, evt.yrel); + if (relativeMode) + { + SetMousePosition(evt.windowID, evt.x, evt.y); + OnMoved(window, evt.x, evt.y, evt.xrel, evt.yrel); + } + else + { + SetMousePosition(evt.windowID, evt.x, evt.y); + OnMoved(window, evt.x, evt.y, evt.xrel, evt.yrel); + } } /// @@ -404,5 +475,6 @@ private void SetMousePositionFromDevicePosition(UInt32 windowID) private UInt32 buttonStateClicks; private UInt32 buttonStateDoubleClicks; private Boolean ignoredFirstMouseMotionEvent; + private Boolean relativeMode; } } diff --git a/Source/Ultraviolet.SDL2/Shared/Native/SDLNative.cs b/Source/Ultraviolet.SDL2/Shared/Native/SDLNative.cs index 2b04fd9d9..b9607c095 100644 --- a/Source/Ultraviolet.SDL2/Shared/Native/SDLNative.cs +++ b/Source/Ultraviolet.SDL2/Shared/Native/SDLNative.cs @@ -1322,6 +1322,39 @@ public unsafe static partial class SDLNative private static readonly SDL_freeDelegate pSDL_free = lib.LoadFunction("SDL_free"); public static void SDL_free(IntPtr mem) => pSDL_free(mem); #endif + +#if ANDROID || IOS + [DllImport(LIBRARY, EntryPoint="SDL_GetRelativeMouseMode", CallingConvention = CallingConvention.Cdecl)] + public static extern Boolean SDL_GetRelativeMouseMode(); +#else + [MonoNativeFunctionWrapper] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate Boolean SDL_GetRelativeMouseModeDelegate(); + private static readonly SDL_GetRelativeMouseModeDelegate pSDL_GetRelativeMouseMode = lib.LoadFunction("SDL_GetRelativeMouseMode"); + public static Boolean SDL_GetRelativeMouseMode() => pSDL_GetRelativeMouseMode(); +#endif + +#if ANDROID || IOS + [DllImport(LIBRARY, EntryPoint="SDL_SetRelativeMouseMode", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 SDL_SetRelativeMouseMode(Boolean enabled); +#else + [MonoNativeFunctionWrapper] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate Int32 SDL_SetRelativeMouseModeDelegate(Boolean enabled); + private static readonly SDL_SetRelativeMouseModeDelegate pSDL_SetRelativeMouseMode = lib.LoadFunction("SDL_SetRelativeMouseMode"); + public static Int32 SDL_SetRelativeMouseMode(Boolean enabled) => pSDL_SetRelativeMouseMode(enabled); +#endif + +#if ANDROID || IOS + [DllImport(LIBRARY, EntryPoint="SDL_WarpMouseInWindow", CallingConvention = CallingConvention.Cdecl)] + public static extern void SDL_WarpMouseInWindow(IntPtr window, Int32 x, Int32 y); +#else + [MonoNativeFunctionWrapper] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void SDL_WarpMouseInWindowDelegate(IntPtr window, Int32 x, Int32 y); + private static readonly SDL_WarpMouseInWindowDelegate pSDL_WarpMouseInWindow = lib.LoadFunction("SDL_WarpMouseInWindow"); + public static void SDL_WarpMouseInWindow(IntPtr window, Int32 x, Int32 y) => pSDL_WarpMouseInWindow(window, x, y); +#endif } #pragma warning restore 1591 } \ No newline at end of file diff --git a/Source/Ultraviolet.SDL2/Shared/Platform/SDL2UltravioletWindow.cs b/Source/Ultraviolet.SDL2/Shared/Platform/SDL2UltravioletWindow.cs index 28c51196d..71055a936 100644 --- a/Source/Ultraviolet.SDL2/Shared/Platform/SDL2UltravioletWindow.cs +++ b/Source/Ultraviolet.SDL2/Shared/Platform/SDL2UltravioletWindow.cs @@ -129,6 +129,14 @@ void IMessageSubscriber.ReceiveMessage(UltravioletMessageI } } + /// + public void WarpMouseWithinWindow(Int32 x, Int32 y) + { + Contract.EnsureNotDisposed(this, Disposed); + + SDL_WarpMouseInWindow(ptr, x, y); + } + /// public void SetFullscreenDisplayMode(DisplayMode displayMode) { diff --git a/Source/Ultraviolet.SDL2/Shared/SDL2UltravioletContext.cs b/Source/Ultraviolet.SDL2/Shared/SDL2UltravioletContext.cs index 35bd92dc9..a1ae5b96b 100644 --- a/Source/Ultraviolet.SDL2/Shared/SDL2UltravioletContext.cs +++ b/Source/Ultraviolet.SDL2/Shared/SDL2UltravioletContext.cs @@ -96,7 +96,10 @@ protected Boolean InitSDL(UltravioletConfiguration configuration) SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS; if (Platform == UltravioletPlatform.Windows) - SDL_SetHint(SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING, "1"); + { + if (!SDL_SetHint(SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING, "1")) + throw new SDL2Exception(); + } return SDL_Init(sdlFlags) == 0; } diff --git a/Source/Ultraviolet/Shared/Input/MouseDevice.cs b/Source/Ultraviolet/Shared/Input/MouseDevice.cs index 49129fe49..0e55a90aa 100644 --- a/Source/Ultraviolet/Shared/Input/MouseDevice.cs +++ b/Source/Ultraviolet/Shared/Input/MouseDevice.cs @@ -48,6 +48,32 @@ public MouseDevice(UltravioletContext uv) } + /// + /// Sets the mouse cursor's position to the specified point within the specified window. + /// + /// The window within which to place the mouse cursor. + /// The x-coordinate within at which to position the cursor. + /// The y-coordinate within at which to position the cursor. + public abstract void WarpToWindow(IUltravioletWindow window, Int32 x, Int32 y); + + /// + /// Sets the mouse cursor's position to the center of the specified window. + /// + /// The window within which to place the mouse cursor. + public abstract void WarpToWindowCenter(IUltravioletWindow window); + + /// + /// Sets the mouse cursor's position to the specified point within the application's primary window. + /// + /// The x-coordinate within the primary window at which to position the cursor. + /// The y-coordinate within the primary window at which to position the cursor. + public abstract void WarpToPrimaryWindow(Int32 x, Int32 y); + + /// + /// Sets the mouse cursor's position to the center of the application's primary window. + /// + public abstract void WarpToPrimaryWindowCenter(); + /// /// Gets the mouse cursor's position within the specified window. /// @@ -70,6 +96,21 @@ public MouseDevice(UltravioletContext uv) /// if the button was double clicked this frame; otherwise, . public abstract Boolean IsButtonDoubleClicked(MouseButton button); + /// + /// Gets a value indicating whether relative mouse mode is currently enabled. + /// + /// if relative mouse mode is currently enabled; otherwise, . + public abstract Boolean GetIsRelativeModeEnabled(); + + /// + /// Enables or disables relative mouse mode. In relative mode, the cursor is hidden, and the driver will try + /// to report continuous motion in the current window. Only relative motion events will be delivered, and + /// the mouse position will not change. + /// + /// to enable relative mode; otherwise, . + /// if relative mode was enabled or disabled; if relative mode is not supported. + public abstract Boolean SetIsRelativeModeEnabled(Boolean enabled); + /// /// Gets the window that currently contains the mouse cursor. /// diff --git a/Source/Ultraviolet/Shared/Platform/IUltravioletWindow.cs b/Source/Ultraviolet/Shared/Platform/IUltravioletWindow.cs index 89f46e0bb..b00f05735 100644 --- a/Source/Ultraviolet/Shared/Platform/IUltravioletWindow.cs +++ b/Source/Ultraviolet/Shared/Platform/IUltravioletWindow.cs @@ -21,6 +21,13 @@ namespace Ultraviolet.Platform /// public interface IUltravioletWindow { + /// + /// Warps the cursor to the specified position within this window. + /// + /// The x-coordinate within the window to which the mouse will be warped. + /// The y-coordinate within the window to which the mouse will be warped. + void WarpMouseWithinWindow(Int32 x, Int32 y); + /// /// Sets the window's fullscreen display mode. ///