1 /++ 2 + Authors: Avaxar <avaxar@nekkl.org> 3 + Copyright: Copyright © 2023, Avaxar 4 + License: $(LINK2 https://mit-license.org, MIT License) 5 +/ 6 7 module dsdl2.mouse; 8 @safe: 9 10 import bindbc.sdl; 11 import dsdl2.sdl; 12 import dsdl2.surface; 13 import dsdl2.window; 14 15 import core.memory : GC; 16 import std.bitmanip : bitfields; 17 import std.format : format; 18 19 /++ 20 + Wraps `SDL_GetMouseFocus` which gets the mouse-focused window 21 + 22 + This function is marked as `@system` due to the potential of referencing invalid memory. 23 + 24 + Returns: `dsdl2.Window` proxy of the window with the mouse focus; `null` if no window is focused 25 +/ 26 Window getMouseFocusedWindow() @system { 27 SDL_Window* sdlWindow = SDL_GetMouseFocus(); 28 if (sdlWindow is null) { 29 return null; 30 } 31 32 return new Window(sdlWindow, false); 33 } 34 35 /++ 36 + Wraps `SDL_GetMouseState` which gets the states of the mouse buttons 37 + 38 + Returns: `dsdl2.MouseState` specifying the pressed-states of the mouse buttons 39 +/ 40 MouseState getMouseState() @trusted { 41 return MouseState(SDL_GetMouseState(null, null)); 42 } 43 44 /++ 45 + Wraps `SDL_GetMouseState` which gets the mouse position relative to a focused window 46 + 47 + Returns: coordinate of the mouse in the focused window 48 +/ 49 int[2] getMousePosition() @trusted { 50 int[2] pos = void; 51 SDL_GetMouseState(&pos[0], &pos[1]); 52 return pos; 53 } 54 55 /++ 56 + Wraps `SDL_WarpMouseInWindow` which sets the mouse position relative to a focused window 57 + 58 + Params: 59 + newPosition = new coordinate of the mouse in the focused window 60 +/ 61 void setMousePosition(int[2] newPosition) @trusted { 62 SDL_Window* window = SDL_GetMouseFocus(); 63 if (window !is null) { 64 SDL_WarpMouseInWindow(window, newPosition[0], newPosition[1]); 65 } 66 } 67 68 /++ 69 + Wraps `SDL_GetRelativeMouseMode` which checks whether relative mouse mode is enabled or not 70 + 71 + Returns: `true` if relative mouse mode is enabled, otherwise `false` 72 +/ 73 bool getRelativeMouseMode() @trusted { 74 return SDL_GetRelativeMouseMode() == SDL_TRUE; 75 } 76 77 /++ 78 + Wraps `SDL_SetRelativeMouseMode` which sets relative mouse mode 79 + 80 + Params: 81 + newRelativeMouseMode = `true` to enable relative mouse mode, otherwise `false` 82 + Throws: `dsdl2.SDLException` if failed to toggle relative mouse mode 83 +/ 84 void setRelativeMouseMode(bool newRelativeMouseMode) @trusted { 85 if (SDL_SetRelativeMouseMode(newRelativeMouseMode) != 0) { 86 throw new SDLException; 87 } 88 } 89 90 /++ 91 + Wraps `SDL_GetRelativeMouseState` which gets the relative states of the mouse buttons 92 + 93 + Returns: `dsdl2.MouseState` specifying the relative pressed-states of the mouse buttons 94 +/ 95 MouseState getRelativeMouseState() @trusted { 96 return MouseState(SDL_GetRelativeMouseState(null, null)); 97 } 98 99 /++ 100 + Wraps `SDL_GetRelativeMouseState` which gets the relative mouse position 101 + 102 + Returns: relative coordinate of the mouse 103 +/ 104 int[2] getRelativeMousePosition() @trusted { 105 int[2] pos = void; 106 SDL_GetRelativeMouseState(&pos[0], &pos[1]); 107 return pos; 108 } 109 110 static if (sdlSupport >= SDLSupport.v2_0_4) { 111 /++ 112 + Wraps `SDL_CaptureMouse` (from SDL 2.0.4) which sets mouse capture 113 + 114 + Params: 115 + newMouseCapture = `true` to enable mouse capture, otherwise `false` 116 + Throws: `dsdl2.SDLException` if failed to toggle mouse capture 117 +/ 118 void setMouseCapture(bool newMouseCapture) @trusted 119 in { 120 assert(getVersion() >= Version(2, 0, 4)); 121 } 122 do { 123 if (SDL_CaptureMouse(newMouseCapture) != 0) { 124 throw new SDLException; 125 } 126 } 127 128 /++ 129 + Wraps `SDL_GetGlobalMouseState` (from SDL 2.0.4) which gets the global states of the mouse buttons 130 + 131 + Returns: `dsdl2.MouseState` specifying the global pressed-states of the mouse buttons 132 +/ 133 MouseState getGlobalMouseState() @trusted 134 in { 135 assert(getVersion() >= Version(2, 0, 4)); 136 } 137 do { 138 return MouseState(SDL_GetGlobalMouseState(null, null)); 139 } 140 141 /++ 142 + Wraps `SDL_GetGlobalMouseState` (from SDL 2.0.4) which gets the mouse position globally 143 + 144 + Returns: coordinate of the mouse in the display 145 +/ 146 int[2] getGlobalMousePosition() @trusted 147 in { 148 assert(getVersion() >= Version(2, 0, 4)); 149 } 150 do { 151 int[2] pos = void; 152 SDL_GetGlobalMouseState(&pos[0], &pos[1]); 153 return pos; 154 } 155 156 /++ 157 + Wraps `SDL_WarpMouseGlobal` (from SDL 2.0.4) which sets the mouse position globally in the display 158 + 159 + Params: 160 + newPosition = new coordinate of the mouse in the display 161 +/ 162 void setGlobalMousePosition(int[2] newPosition) @trusted 163 in { 164 assert(getVersion() >= Version(2, 0, 4)); 165 } 166 do { 167 SDL_WarpMouseGlobal(newPosition[0], newPosition[1]); 168 } 169 } 170 171 /++ 172 + D enum that wraps `SDL_BUTTON_*` enumerations 173 +/ 174 enum MouseButton { 175 /++ 176 + Wraps `SDL_BUTTON_*` enumeration constants 177 +/ 178 left = SDL_BUTTON_LEFT, 179 middle = SDL_BUTTON_MIDDLE, /// ditto 180 right = SDL_BUTTON_RIGHT, /// ditto 181 x1 = SDL_BUTTON_X1, /// ditto 182 x2 = SDL_BUTTON_X2 183 } 184 185 static if (sdlSupport >= SDLSupport.v2_0_4) { 186 /++ 187 + D enum that wraps `SDL_MOUSEWHEEL_*` enumerations (from SDL 2.0.4) 188 +/ 189 enum MouseWheel { 190 /++ 191 + Wraps `SDL_MOUSEWHEEL_*` enumeration constants 192 +/ 193 normal = SDL_MOUSEWHEEL_NORMAL, 194 flipped = SDL_MOUSEWHEEL_FLIPPED /// ditto 195 } 196 } 197 198 /++ 199 + D struct that encapsulates mouse button state flags 200 +/ 201 struct MouseState { 202 mixin(bitfields!( 203 bool, "left", 1, 204 bool, "middle", 1, 205 bool, "right", 1, 206 bool, "x1", 1, 207 bool, "x2", 1, 208 bool, "", 3)); 209 210 this() @disable; 211 212 /++ 213 + Constructs a `dsdl2.MouseState` from a vanilla SDL mouse state flag 214 + 215 + Params: 216 + sdlMouseState = the `uint` flag 217 +/ 218 this(uint sdlMouseState) { 219 this.left = (sdlMouseState & SDL_BUTTON_LMASK) != 0; 220 this.middle = (sdlMouseState & SDL_BUTTON_MMASK) != 0; 221 this.right = (sdlMouseState & SDL_BUTTON_RMASK) != 0; 222 this.x1 = (sdlMouseState & SDL_BUTTON_X1MASK) != 0; 223 this.x2 = (sdlMouseState & SDL_BUTTON_X2MASK) != 0; 224 } 225 226 /++ 227 + Constructs a `dsdl2.MouseState` by providing the flags 228 + 229 + Params: 230 + base = base flag to assign (`0` for none) 231 + left = whether the left mouse button is pressed 232 + middle = whether the middle mouse button is pressed 233 + right = whether the right mouse button is pressed 234 + x1 = whether the X1 mouse button is pressed 235 + x2 = whether the X2 mouse button is pressed 236 +/ 237 this(uint base, bool left = false, bool middle = false, bool right = false, bool x1 = false, bool x2 = false) { 238 this(base); 239 this.left = left; 240 this.middle = middle; 241 this.right = right; 242 this.x1 = x1; 243 this.x2 = x2; 244 } 245 246 /++ 247 + Formats the `dsdl2.MouseState` into its construction representation: 248 + `"dsdl2.MouseState(<sdlMouseState>, <left>, <middle>, <right>, <x1>, <x2>)"` 249 + 250 + Returns: the formatted `string` 251 +/ 252 string toString() const { 253 return "dsdl2.MouseState(%d, %s, %s, %s, %s, %s)".format(this.sdlMouseState, this.left, this.middle, 254 this.right, this.x1, this.x2); 255 } 256 257 /++ 258 + Gets the internal flag representation 259 + 260 + Returns: `uint` with the appropriate bitflags toggled 261 +/ 262 uint sdlMouseState() const @property { 263 return (this.left ? SDL_BUTTON_LMASK : 0) 264 | (this.middle ? SDL_BUTTON_MMASK : 0) 265 | (this.right ? SDL_BUTTON_RMASK 266 : 0) 267 | (this.x1 ? SDL_BUTTON_X1MASK : 0) 268 | (this.x2 ? SDL_BUTTON_X2MASK : 0); 269 } 270 } 271 272 /++ 273 + Wraps `SDL_GetCursor` which gets the current set cursor 274 + 275 + Returns: `dsdl2.Cursor` proxy of the set cursor 276 +/ 277 Cursor getCursor() @trusted { 278 return new Cursor(SDL_GetCursor(), false); 279 } 280 281 /++ 282 + Wraps `SDL_GetDefaultCursor` which gets the default cursor 283 + 284 + Returns: `dsdl2.Cursor` proxy of the default cursor 285 +/ 286 Cursor getDefaultCursor() @trusted { 287 return new Cursor(SDL_GetDefaultCursor(), false); 288 } 289 290 /++ 291 + Wraps `SDL_ShowCursor` which sets the visibility of the cursor 292 + 293 + Returns: `true` if cursor is visible, otherwise `false` 294 +/ 295 bool getCursorVisibility() @trusted { 296 return SDL_ShowCursor(SDL_QUERY) != SDL_ENABLE; 297 } 298 299 /++ 300 + Wraps `SDL_ShowCursor` which sets the visibility of the cursor 301 + 302 + Params: 303 + visible = `true` to make cursor visible, otherwise `false` 304 + Throws: `dsdl2.SDLException` if failed to set cursor visibility 305 +/ 306 void setCursorVisibility(bool visible) @trusted { 307 if (SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE) != 0) { 308 throw new SDLException; 309 } 310 } 311 312 /++ 313 + D class that wraps `SDL_Cursor` which sets cursor appearance 314 +/ 315 final class Cursor { 316 static Cursor _multiton(SDL_SystemCursor sdlSystemCursor)() @trusted { 317 static Cursor cursor = null; 318 if (cursor is null) { 319 SDL_Cursor* sdlCursor = SDL_CreateSystemCursor(sdlSystemCursor); 320 if (sdlCursor is null) { 321 throw new SDLException; 322 } 323 324 cursor = new Cursor(sdlCursor); 325 } 326 327 return cursor; 328 } 329 330 /++ 331 + Retrieves one of the `dsdl2.Cursor` system cursor presets from `SDL_SYSTEM_CURSOR_*` enumeration constants 332 +/ 333 static alias systemArrow = _multiton!SDL_SYSTEM_CURSOR_ARROW; /// dutto 334 static alias systemIBeam = _multiton!SDL_SYSTEM_CURSOR_IBEAM; /// ditto 335 static alias systemWait = _multiton!SDL_SYSTEM_CURSOR_WAIT; /// ditto 336 static alias systemCrosshair = _multiton!SDL_SYSTEM_CURSOR_CROSSHAIR; /// ditto 337 static alias systemWaitArrow = _multiton!SDL_SYSTEM_CURSOR_WAITARROW; /// ditto 338 static alias systemSizeNWSE = _multiton!SDL_SYSTEM_CURSOR_SIZENWSE; /// ditto 339 static alias systemSizeNESW = _multiton!SDL_SYSTEM_CURSOR_SIZENESW; /// ditto 340 static alias systemSizeWE = _multiton!SDL_SYSTEM_CURSOR_SIZEWE; /// ditto 341 static alias systemSizeNS = _multiton!SDL_SYSTEM_CURSOR_SIZENS; /// ditto 342 static alias systemSizeAll = _multiton!SDL_SYSTEM_CURSOR_SIZEALL; /// ditto 343 static alias systemNo = _multiton!SDL_SYSTEM_CURSOR_NO; /// ditto 344 static alias systemHand = _multiton!SDL_SYSTEM_CURSOR_HAND; /// ditto 345 346 private bool isOwner = true; 347 private void* userRef = null; 348 349 @system SDL_Cursor* sdlCursor = null; /// Internal `SDL_Cursor` pointer 350 351 /++ 352 + Constructs a `dsdl2.Cursor` from a vanilla `SDL_Cursor*` from bindbc-sdl 353 + 354 + Params: 355 + sdlCursor = the `SDL_Cursor` pointer to manage 356 + isOwner = whether the instance owns the given `SDL_Cursor*` and should destroy it on its own 357 + userRef = optional pointer to maintain reference link, avoiding GC cleanup 358 +/ 359 this(SDL_Cursor* sdlCursor, bool isOwner = true, void* userRef = null) @system 360 in { 361 assert(sdlCursor !is null); 362 } 363 do { 364 this.sdlCursor = sdlCursor; 365 this.isOwner = isOwner; 366 this.userRef = userRef; 367 } 368 369 /++ 370 + Constructs a `dsdl2.Cursor` from a `dsdl2.Surface`, which wraps `SDL_CreateColorCursor` 371 + 372 + Params: 373 + surface = surface image of the cursor 374 + hotPosition = pixel position of the cursor hotspot 375 + Throws: `dsdl2.Exception` if cursor creation failed 376 +/ 377 this(Surface surface, uint[2] hotPosition) @trusted { 378 this.sdlCursor = SDL_CreateColorCursor(surface.sdlSurface, hotPosition[0], hotPosition[1]); 379 if (this.sdlCursor is null) { 380 throw new SDLException; 381 } 382 } 383 384 ~this() @trusted { 385 if (this.isOwner) { 386 SDL_FreeCursor(this.sdlCursor); 387 } 388 } 389 390 @trusted invariant { // @suppress(dscanner.trust_too_much) 391 // Instance might be in an invalid state due to holding a non-owned externally-freed object when 392 // destructed in an unpredictable order. 393 if (!this.isOwner && GC.inFinalizer) { 394 return; 395 } 396 397 assert(this.sdlCursor !is null); 398 } 399 400 /++ 401 + Equality operator overload 402 +/ 403 bool opEquals(const Cursor rhs) const @trusted { 404 return this.sdlCursor is rhs.sdlCursor; 405 } 406 407 /++ 408 + Gets the hash of the `dsdl2.Cursor` 409 + 410 + Returns: unique hash for the instance being the pointer of the internal `SDL_Cursor` pointer 411 +/ 412 override hash_t toHash() const @trusted { 413 return cast(hash_t) this.sdlCursor; 414 } 415 416 /++ 417 + Formats the `dsdl2.Cursor` into its construction representation: `"dsdl2.Cursor(<sdlCursor>)"` 418 + 419 + Returns: the formatted `string` 420 +/ 421 override string toString() const @trusted { 422 return "dsdl2.Cursor(0x%x)".format(this.sdlCursor); 423 } 424 425 /++ 426 + Wraps `SDL_SetCursor` which sets the `dsdl2.Cursor` to be the cursor 427 +/ 428 void set() const @trusted { 429 SDL_SetCursor(cast(SDL_Cursor*) this.sdlCursor); 430 } 431 }