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.sdl; 8 @safe: 9 10 import bindbc.sdl; 11 12 import std.conv : to; 13 import std.format : format; 14 import std.string : toStringz; 15 16 /++ 17 + SDL exception generated from `SDL_GetError()` or dsdl2-specific exceptions 18 +/ 19 final class SDLException : Exception { 20 this(string msg, string file = __FILE__, size_t line = __LINE__) { 21 super(msg, file, line); 22 } 23 24 this(string file = __FILE__, size_t line = __LINE__) @trusted { 25 super(SDL_GetError().to!string, file, line); 26 } 27 } 28 29 version (BindSDL_Static) { 30 } 31 else { 32 /++ 33 + Loads the SDL2 shared dynamic library, which wraps bindbc-sdl's `loadSDL` function 34 + 35 + Unless if bindbc-sdl is on static mode (by adding a `BindSDL_Static` version), this function will exist and must 36 + be called before any calls are made to the library. Otherwise, a segfault will happen upon any function calls. 37 + 38 + Params: 39 + libName = name or path to look the SDL2 SO/DLL for, otherwise `null` for default searching path 40 + Throws: `dsdl2.SDLException` if failed to find the library 41 +/ 42 void loadSO(string libName = null) @trusted { 43 SDLSupport current = libName is null ? loadSDL() : loadSDL(libName.toStringz()); 44 if (current == sdlSupport) { 45 return; 46 } 47 48 Version wanted = Version(sdlSupport); 49 if (current == SDLSupport.badLibrary) { 50 import std.stdio : writeln; 51 52 writeln("WARNING: dsdl2 expects SDL ", wanted.format(), ", but got ", getVersion().format(), "."); 53 } 54 else if (current == SDLSupport.noLibrary) { 55 throw new SDLException("No SDL2 library found, especially of version " ~ wanted.format(), 56 __FILE__, __LINE__); 57 } 58 } 59 } 60 61 private uint toSDLInitFlags(bool timer, bool audio, bool video, bool joystick, bool haptic, bool gameController, 62 bool events, bool everything, bool noParachute, bool sensor) 63 in { 64 static if (sdlSupport < SDLSupport.v2_0_9) { 65 assert(sensor == false); 66 } 67 else { 68 if (sensor) { 69 assert(getVersion() >= Version(2, 0, 9)); 70 } 71 } 72 } 73 do { 74 uint flags = 0; 75 76 flags |= timer ? SDL_INIT_TIMER : 0; 77 flags |= audio ? SDL_INIT_AUDIO : 0; 78 flags |= video ? SDL_INIT_VIDEO : 0; 79 flags |= joystick ? SDL_INIT_JOYSTICK : 0; 80 flags |= haptic ? SDL_INIT_HAPTIC : 0; 81 flags |= gameController ? SDL_INIT_GAMECONTROLLER : 0; 82 flags |= events ? SDL_INIT_EVENTS : 0; 83 flags |= everything ? SDL_INIT_EVERYTHING : 0; 84 flags |= noParachute ? SDL_INIT_NOPARACHUTE : 0; 85 86 static if (sdlSupport >= SDLSupport.v2_0_9) { 87 flags |= sensor ? SDL_INIT_SENSOR : 0; 88 } 89 90 return flags; 91 } 92 93 /++ 94 + Wraps `SDL_Init` which initializes selected subsystems 95 + 96 + Params: 97 + timer = selects the `SDL_INIT_TIMER` subsystem 98 + audio = selects the `SDL_INIT_AUDIO` subsystem 99 + video = selects the `SDL_INIT_VIDEO` subsystem 100 + joystick = selects the `SDL_INIT_JOYSTICK` subsystem 101 + haptic = selects the `SDL_INIT_HAPTIC` subsystem 102 + gameController = selects the `SDL_INIT_GAMECONTROLLER` subsystem 103 + events = selects the `SDL_INIT_EVENTS` subsystem 104 + everything = selects the `SDL_INIT_EVERYTHING` subsystem 105 + noParachute = selects the `SDL_INIT_NOPARACHUTE` subsystem 106 + sensor = selects the `SDL_INIT_SENSOR` subsystem (from SDL 2.0.9) 107 + Throws: `dsdl2.SDLException` if any selected subsystem failed to initialize 108 + Example: 109 + --- 110 + dsdl2.init(everything : true); 111 + --- 112 +/ 113 void init(bool timer = false, bool audio = false, bool video = false, bool joystick = false, bool haptic = false, 114 bool gameController = false, bool events = false, bool everything = false, bool noParachute = false, 115 bool sensor = false) @trusted { 116 uint flags = toSDLInitFlags(timer, audio, video, joystick, haptic, gameController, events, everything, 117 noParachute, sensor); 118 119 if (SDL_Init(flags) != 0) { 120 throw new SDLException; 121 } 122 } 123 124 version (unittest) { 125 static this() { 126 version (BindSDL_Static) { 127 } 128 else { 129 loadSO(); 130 } 131 } 132 } 133 134 /++ 135 + Wraps `SDL_Quit` which entirely deinitializes SDL2 136 +/ 137 void quit() @trusted { 138 SDL_Quit(); 139 } 140 141 version (unittest) { 142 static ~this() { 143 quit(); 144 } 145 } 146 147 /++ 148 + Wraps `SDL_QuitSubSystem` which deinitializes specified subsystems 149 + 150 + Params: 151 + timer = selects the `SDL_INIT_TIMER` subsystem 152 + audio = selects the `SDL_INIT_AUDIO` subsystem 153 + video = selects the `SDL_INIT_VIDEO` subsystem 154 + joystick = selects the `SDL_INIT_JOYSTICK` subsystem 155 + haptic = selects the `SDL_INIT_HAPTIC` subsystem 156 + gameController = selects the `SDL_INIT_GAMECONTROLLER` subsystem 157 + events = selects the `SDL_INIT_EVENTS` subsystem 158 + everything = selects the `SDL_INIT_EVERYTHING` subsystem 159 + noParachute = selects the `SDL_INIT_NOPARACHUTE` subsystem 160 + sensor = selects the `SDL_INIT_SENSOR` subsystem (from SDL 2.0.9) 161 +/ 162 void quit(bool timer = false, bool audio = false, bool video = false, bool joystick = false, bool haptic = false, 163 bool gameController = false, bool events = false, bool everything = false, bool noParachute = false, 164 bool sensor = false) @trusted { 165 uint flags = toSDLInitFlags(timer, audio, video, joystick, haptic, gameController, events, everything, 166 noParachute, sensor); 167 168 SDL_QuitSubSystem(flags); 169 } 170 171 /++ 172 + Wraps `SDL_WasInit` which checks whether particular subsystem(s) is already initialized 173 + 174 + Params: 175 + timer = selects the `SDL_INIT_TIMER` subsystem 176 + audio = selects the `SDL_INIT_AUDIO` subsystem 177 + video = selects the `SDL_INIT_VIDEO` subsystem 178 + joystick = selects the `SDL_INIT_JOYSTICK` subsystem 179 + haptic = selects the `SDL_INIT_HAPTIC` subsystem 180 + gameController = selects the `SDL_INIT_GAMECONTROLLER` subsystem 181 + events = selects the `SDL_INIT_EVENTS` subsystem 182 + everything = selects the `SDL_INIT_EVERYTHING` subsystem 183 + noParachute = selects the `SDL_INIT_NOPARACHUTE` subsystem 184 + sensor = selects the `SDL_INIT_SENSOR` subsystem (from SDL 2.0.9) 185 + 186 + Returns: `true` if the selected subsystem(s) is initialized, otherwise `false` 187 + Example: 188 + --- 189 + dsdl2.init(); 190 + assert(dsdl2.wasInit(video : true) == true); 191 + --- 192 +/ 193 bool wasInit(bool timer = false, bool audio = false, bool video = false, bool joystick = false, bool haptic = false, 194 bool gameController = false, bool events = false, bool everything = false, bool noParachute = false, 195 bool sensor = false) @trusted { 196 uint flags = toSDLInitFlags(timer, audio, video, joystick, haptic, gameController, events, everything, 197 noParachute, sensor); 198 199 return SDL_WasInit(flags) != 0; 200 } 201 202 /++ 203 + D struct that wraps `SDL_version` containing version information 204 + 205 + Example: 206 + --- 207 + import std.stdio; 208 + writeln("We're currently using SDL version ", dsdl2.getVersion().format()); 209 + --- 210 +/ 211 struct Version { 212 SDL_version sdlVersion; /// Internal `SDL_version` struct 213 214 this() @disable; 215 216 /++ 217 + Constructs a `dsdl2.Version` from a vanilla `SDL_version` from bindbc-sdl 218 + 219 + Params: 220 + sdlVersion = the `SDL_version` struct 221 +/ 222 this(SDL_version sdlVersion) { 223 this.sdlVersion = sdlVersion; 224 } 225 226 /++ 227 + Constructs a `dsdl2.Version` by feeding in `major`, `minor`, and `patch` version numbers 228 + 229 + Params: 230 + major = major version number 231 + minor = minor version number 232 + patch = patch verion number 233 +/ 234 this(ubyte major, ubyte minor, ubyte patch = 0) { 235 this.sdlVersion.major = major; 236 this.sdlVersion.minor = minor; 237 this.sdlVersion.patch = patch; 238 } 239 240 /++ 241 + Compares two `dsdl2.Version`s from chronology 242 +/ 243 int opCmp(Version other) const { 244 if (this.major != other.major) { 245 return this.major - other.major; 246 } 247 else if (this.minor != other.minor) { 248 return this.minor - other.minor; 249 } 250 else { 251 return this.patch - other.patch; 252 } 253 } 254 /// 255 unittest { 256 assert(dsdl2.Version(2, 0, 0) == dsdl2.Version(2, 0, 0)); 257 assert(dsdl2.Version(2, 0, 1) > dsdl2.Version(2, 0, 0)); 258 assert(dsdl2.Version(2, 0, 1) < dsdl2.Version(2, 0, 2)); 259 assert(dsdl2.Version(2, 0, 2) >= dsdl2.Version(2, 0, 1)); 260 assert(dsdl2.Version(2, 0, 2) <= dsdl2.Version(2, 0, 2)); 261 } 262 263 /++ 264 + Formats the `dsdl2.Version` into its construction representation: `"dsdl2.Version(<major>, <minor>, <patch>)"` 265 + 266 + Returns: the formatted `string` 267 +/ 268 string toString() const { 269 return "dsdl2.Version(%d, %d, %d)".format(this.major, this.minor, this.patch); 270 } 271 272 /++ 273 + Proxy to the major version value of the `dsdl2.Version` 274 + 275 + Returns: major version value value of the `dsdl2.Version` 276 +/ 277 ref inout(ubyte) major() return inout @property { 278 return this.sdlVersion.major; 279 } 280 281 /++ 282 + Proxy to the minor version value of the `dsdl2.Version` 283 + 284 + Returns: minor version value value of the `dsdl2.Version` 285 +/ 286 ref inout(ubyte) minor() return inout @property { 287 return this.sdlVersion.minor; 288 } 289 290 /++ 291 + Proxy to the patch version value of the `dsdl2.Version` 292 + 293 + Returns: patch version value value of the `dsdl2.Version` 294 +/ 295 ref inout(ubyte) patch() return inout @property { 296 return this.sdlVersion.patch; 297 } 298 299 /++ 300 + Gets the static array representation of the `dsdl2.Version` 301 + 302 + Returns: `major`, `minor`, `patch` as an array 303 +/ 304 ubyte[3] array() const @property { 305 return [ 306 this.sdlVersion.major, this.sdlVersion.minor, this.sdlVersion.patch 307 ]; 308 } 309 310 /++ 311 + Formats the `dsdl2.Version` into `string`: `"<major>.<minor>.<patch>"` 312 + 313 + Returns: the formatted `string` 314 +/ 315 string format() const { 316 return "%d.%d.%d".format(this.major, this.minor, this.patch); 317 } 318 } 319 /// 320 unittest { 321 auto minimumVersion = dsdl2.Version(2, 0, 0); 322 auto ourVersion = dsdl2.getVersion(); 323 assert(ourVersion >= minimumVersion); 324 } 325 /// 326 unittest { 327 assert(dsdl2.Version(2, 0, 2) > dsdl2.Version(2, 0, 0)); 328 assert(dsdl2.Version(2, 2, 0) >= dsdl2.Version(2, 0, 2)); 329 assert(dsdl2.Version(3, 0, 0) >= dsdl2.Version(2, 2, 0)); 330 } 331 332 /++ 333 + Wraps `SDL_GetVersion` which gets the version of the linked SDL2 library 334 + 335 + Returns: `dsdl2.Version` of the linked SDL2 library 336 +/ 337 Version getVersion() @trusted { 338 Version ver = void; 339 SDL_GetVersion(&ver.sdlVersion); 340 return ver; 341 } 342 343 /++ 344 + Wraps `SDL_GetRevision` which returns the revision string of the linked SDL2 library 345 + 346 + Returns: `string` of the revision code 347 +/ 348 string getRevision() @trusted { 349 return SDL_GetRevision().to!string; 350 } 351 352 /++ 353 + D enum that wraps `SDL_HintPriority` 354 +/ 355 enum HintPriority : SDL_HintPriority { 356 /++ 357 + Wraps `SDL_HINT_*` enumeration constants 358 +/ 359 default_ = SDL_HINT_DEFAULT, 360 normal = SDL_HINT_NORMAL, /// ditto 361 override_ = SDL_HINT_OVERRIDE /// ditto 362 } 363 364 /++ 365 + Wraps `SDL_SetHintWithPriority` which provides giving a hint to SDL2 in runtime 366 + 367 + Params: 368 + name = name of the hint 369 + value = value to set the hint as 370 + priority = priority of the hint configuration (by default, `dsdl2.HintPriority.normal`) 371 + 372 + Returns: `true` if the hint was set, `false` otherwise 373 +/ 374 bool setHint(string name, string value, HintPriority priority = HintPriority.normal) @trusted { 375 return SDL_SetHintWithPriority(name.toStringz(), value.toStringz(), priority) == SDL_TRUE; 376 } 377 378 static if (sdlSupport >= SDLSupport.v2_26) { 379 /++ 380 + Wraps `SDL_ResetHints` (from SDL 2.26) which resets any user-set hints given to SDL2 to default 381 +/ 382 void resetHints() @trusted 383 in { 384 assert(getVersion() >= Version(2, 26)); 385 } 386 do { 387 SDL_ResetHints(); 388 } 389 } 390 391 /++ 392 + Wraps `SDL_GetHint` which gets the value of a specified user-set hint 393 + 394 + Params: 395 + name = name of the hint 396 + Returns: value of the given `name` of the hint 397 +/ 398 string getHint(string name) @trusted { 399 if (const(char)* hint = SDL_GetHint(name.toStringz())) { 400 return hint.to!string; 401 } 402 else { 403 return ""; 404 } 405 } 406 407 static if (sdlSupport >= SDLSupport.v2_0_5) { 408 /++ 409 + Wraps `SDL_GetHintBoolean` (from SDL 2.0.5) which gets the value of a specified user-set hint as a `bool` 410 + 411 + Params: 412 + name = name of the hint 413 + defaultValue = default returned value if the hint wasn't set 414 + Returns: `bool` value of the given `name` of the hint or `defaultValue` if the hint wasn't set 415 +/ 416 bool getHintBool(string name, bool defaultValue = false) @trusted 417 in { 418 assert(getVersion() >= Version(2, 0, 5)); 419 } 420 do { 421 return SDL_GetHintBoolean(name.toStringz(), defaultValue) == SDL_TRUE; 422 } 423 } 424 425 /++ 426 + Wraps `SDL_GetTicks` or `SDL_GetTicks64` on SDL 2.0.18 which gets the time since the SDL library was initialized 427 + in milliseconds 428 + 429 + Returns: milliseconds since the initialization of the SDL library 430 +/ 431 ulong getTicks() @trusted 432 in { 433 static if (sdlSupport >= SDLSupport.v2_0_18) { 434 assert(getVersion() >= Version(2, 0, 18)); 435 } 436 } 437 do { 438 static if (sdlSupport >= SDLSupport.v2_0_18) { 439 return SDL_GetTicks64(); 440 } 441 else { 442 return SDL_GetTicks(); 443 } 444 }