1 module fluid.backend.raylib5; 2 3 version (Have_raylib_d): 4 5 debug (Fluid_BuildMessages) { 6 pragma(msg, "Fluid: Building with Raylib 5 support"); 7 } 8 9 import raylib; 10 11 import std.range; 12 import std.algorithm; 13 14 import fluid.backend; 15 import fluid.backend : MouseButton, KeyboardKey, GamepadButton; 16 17 public import raylib : Vector2, Rectangle, Color; 18 19 20 @safe: 21 22 23 class Raylib5Backend : FluidBackend { 24 25 private { 26 27 TextureReaper _reaper; 28 FluidMouseCursor lastMouseCursor; 29 Rectangle drawArea; 30 float _scale = 1; 31 32 } 33 34 @trusted { 35 36 bool isPressed(MouseButton button) const 37 => isScroll(button) 38 ? isScrollPressed(button) 39 : IsMouseButtonPressed(button.toRaylib); 40 bool isReleased(MouseButton button) const 41 => isScroll(button) 42 ? isScrollPressed(button) 43 : IsMouseButtonReleased(button.toRaylib); 44 bool isDown(MouseButton button) const 45 => isScroll(button) 46 ? isScrollPressed(button) 47 : IsMouseButtonDown(button.toRaylib); 48 bool isUp(MouseButton button) const 49 => isScroll(button) 50 ? !isScrollPressed(button) 51 : IsMouseButtonUp(button.toRaylib); 52 53 bool isPressed(KeyboardKey key) const => IsKeyPressed(key.toRaylib); 54 bool isReleased(KeyboardKey key) const => IsKeyReleased(key.toRaylib); 55 bool isDown(KeyboardKey key) const => IsKeyDown(key.toRaylib); 56 bool isUp(KeyboardKey key) const => IsKeyUp(key.toRaylib); 57 bool isRepeated(KeyboardKey key) const => IsKeyPressedRepeat(key.toRaylib); 58 59 dchar inputCharacter() => cast(dchar) GetCharPressed(); 60 61 int isPressed(GamepadButton button) const { 62 auto btn = button.toRaylib; 63 return 1 + cast(int) iota(0, 4).countUntil!(a => IsGamepadButtonPressed(a, btn)); 64 } 65 66 int isReleased(GamepadButton button) const { 67 auto btn = button.toRaylib; 68 return 1 + cast(int) iota(0, 4).countUntil!(a => IsGamepadButtonReleased(a, btn)); 69 } 70 71 int isDown(GamepadButton button) const { 72 auto btn = button.toRaylib; 73 return 1 + cast(int) iota(0, 4).countUntil!(a => IsGamepadButtonDown(a, btn)); 74 } 75 76 int isUp(GamepadButton button) const { 77 auto btn = button.toRaylib; 78 return 1 + cast(int) iota(0, 4).countUntil!(a => IsGamepadButtonUp(a, btn)); 79 } 80 81 int isRepeated(GamepadButton button) const 82 => 0; 83 84 } 85 86 private bool isScrollPressed(MouseButton btn) const @trusted { 87 88 const wheelMove = GetMouseWheelMoveV; 89 90 switch (btn) { 91 case btn.scrollUp: return wheelMove.y > 0; 92 case btn.scrollDown: return wheelMove.y < 0; 93 case btn.scrollLeft: return wheelMove.x > 0; 94 case btn.scrollRight: return wheelMove.x < 0; 95 default: assert(false); 96 } 97 98 } 99 100 Vector2 mousePosition(Vector2 position) @trusted { 101 102 auto positionRay = toRaylibCoords(position); 103 SetMousePosition(cast(int) positionRay.x, cast(int) positionRay.y); 104 return position; 105 106 } 107 108 Vector2 mousePosition() const @trusted { 109 110 return toFluidCoords(GetMousePosition); 111 112 } 113 114 float deltaTime() const @trusted { 115 116 return GetFrameTime; 117 118 } 119 120 bool hasJustResized() const @trusted { 121 122 // TODO detect and react to DPI changes 123 return IsWindowResized; 124 125 } 126 127 Vector2 windowSize(Vector2 size) @trusted { 128 129 auto sizeRay = toRaylibCoords(size); 130 SetWindowSize(cast(int) sizeRay.x, cast(int) sizeRay.y); 131 return size; 132 133 } 134 135 Vector2 windowSize() const @trusted { 136 137 return toFluidCoords(GetScreenWidth, GetScreenHeight); 138 139 } 140 141 float scale() const { 142 143 return _scale; 144 145 } 146 147 float scale(float value) { 148 149 return _scale = value; 150 151 } 152 153 Vector2 dpi() const @trusted { 154 155 static Vector2 value; 156 157 if (value == value.init) { 158 159 value = GetWindowScaleDPI; 160 value.x *= 96; 161 value.y *= 96; 162 163 } 164 165 return value * _scale; 166 167 } 168 169 Vector2 toRaylibCoords(Vector2 position) const @trusted { 170 171 return Vector2(position.x * hidpiScale.x, position.y * hidpiScale.y); 172 173 } 174 175 Rectangle toRaylibCoords(Rectangle rec) const @trusted { 176 177 return Rectangle( 178 rec.x * hidpiScale.x, 179 rec.y * hidpiScale.y, 180 rec.width * hidpiScale.x, 181 rec.height * hidpiScale.y, 182 ); 183 184 } 185 186 Vector2 toFluidCoords(Vector2 position) const @trusted { 187 188 return Vector2(position.x / hidpiScale.x, position.y / hidpiScale.y); 189 190 } 191 192 Vector2 toFluidCoords(float x, float y) const @trusted { 193 194 return Vector2(x / hidpiScale.x, y / hidpiScale.y); 195 196 } 197 198 Rectangle toFluidCoords(Rectangle rec) const @trusted { 199 200 return Rectangle( 201 rec.x / hidpiScale.x, 202 rec.y / hidpiScale.y, 203 rec.width / hidpiScale.x, 204 rec.height / hidpiScale.y, 205 ); 206 207 } 208 209 Rectangle area(Rectangle rect) @trusted { 210 211 auto rectRay = toRaylibCoords(rect); 212 213 BeginScissorMode( 214 cast(int) rectRay.x, 215 cast(int) rectRay.y, 216 cast(int) rectRay.width, 217 cast(int) rectRay.height, 218 ); 219 220 return drawArea = rect; 221 222 } 223 224 Rectangle area() const { 225 226 if (drawArea is drawArea.init) 227 return Rectangle(0, 0, windowSize.tupleof); 228 else 229 return drawArea; 230 231 } 232 233 void restoreArea() @trusted { 234 235 EndScissorMode(); 236 drawArea = drawArea.init; 237 238 } 239 240 FluidMouseCursor mouseCursor(FluidMouseCursor cursor) @trusted { 241 242 // Hide the cursor if requested 243 if (cursor.system == cursor.system.none) { 244 HideCursor(); 245 } 246 247 // Show the cursor 248 else { 249 SetMouseCursor(cursor.system.toRaylib); 250 ShowCursor(); 251 } 252 return lastMouseCursor = cursor; 253 254 } 255 256 FluidMouseCursor mouseCursor() const { 257 258 return lastMouseCursor; 259 260 } 261 262 TextureReaper* reaper() return scope { 263 264 return &_reaper; 265 266 } 267 268 fluid.backend.Texture loadTexture(fluid.backend.Image image) @system { 269 270 return fromRaylib(LoadTextureFromImage(image.toRaylib)); 271 272 } 273 274 fluid.backend.Texture loadTexture(string filename) @system { 275 276 import std.string; 277 278 return fromRaylib(LoadTexture(filename.toStringz)); 279 280 } 281 282 fluid.backend.Texture fromRaylib(raylib.Texture texture) { 283 284 fluid.backend.Texture result; 285 result.id = texture.id; 286 result.tombstone = reaper.makeTombstone(this, result.id); 287 result.width = texture.width; 288 result.height = texture.height; 289 return result; 290 291 } 292 293 /// Destroy a texture 294 void unloadTexture(uint id) @system { 295 296 if (!__ctfe && IsWindowReady && id != 0) { 297 298 rlUnloadTexture(id); 299 300 } 301 302 } 303 304 void drawLine(Vector2 start, Vector2 end, Color color) @trusted { 305 306 DrawLineV(toRaylibCoords(start), toRaylibCoords(end), color); 307 308 } 309 310 void drawTriangle(Vector2 a, Vector2 b, Vector2 c, Color color) @trusted { 311 312 DrawTriangle(toRaylibCoords(a), toRaylibCoords(b), toRaylibCoords(c), color); 313 314 } 315 316 void drawRectangle(Rectangle rectangle, Color color) @trusted { 317 318 DrawRectangleRec(toRaylibCoords(rectangle), color); 319 320 } 321 322 void drawTexture(fluid.backend.Texture texture, Rectangle rectangle, Color tint, string alt = "") 323 in (false) 324 do { 325 326 // TODO filtering? 327 drawTexture(texture, rectangle, tint, alt, true); 328 329 } 330 331 void drawTextureAlign(fluid.backend.Texture texture, Rectangle rectangle, Color tint, string alt = "") 332 in (false) 333 do { 334 335 drawTexture(texture, rectangle, tint, alt, true); 336 337 } 338 339 protected @trusted 340 void drawTexture(fluid.backend.Texture texture, Rectangle destination, Color tint, string alt, bool alignPixels) 341 do { 342 343 import std.math; 344 345 destination = toRaylibCoords(destination); 346 347 // Align texture to pixel boundaries 348 if (alignPixels) { 349 destination.x = floor(destination.x); 350 destination.y = floor(destination.y); 351 } 352 353 const dpi = this.dpi; 354 const source = Rectangle(0, 0, texture.width, texture.height); 355 356 DrawTexturePro(texture.toRaylib, source, destination, Vector2(0, 0), 0, tint); 357 358 } 359 360 } 361 362 /// Get the Raylib enum for a mouse cursor. 363 raylib.MouseCursor toRaylib(FluidMouseCursor.SystemCursors cursor) { 364 365 with (raylib.MouseCursor) 366 with (FluidMouseCursor.SystemCursors) 367 switch (cursor) { 368 369 default: 370 case none: 371 case systemDefault: 372 return MOUSE_CURSOR_DEFAULT; 373 374 case pointer: 375 return MOUSE_CURSOR_POINTING_HAND; 376 377 case crosshair: 378 return MOUSE_CURSOR_CROSSHAIR; 379 380 case text: 381 return MOUSE_CURSOR_IBEAM; 382 383 case allScroll: 384 return MOUSE_CURSOR_RESIZE_ALL; 385 386 case resizeEW: 387 return MOUSE_CURSOR_RESIZE_EW; 388 389 case resizeNS: 390 return MOUSE_CURSOR_RESIZE_NS; 391 392 case resizeNESW: 393 return MOUSE_CURSOR_RESIZE_NESW; 394 395 case resizeNWSE: 396 return MOUSE_CURSOR_RESIZE_NWSE; 397 398 case notAllowed: 399 return MOUSE_CURSOR_NOT_ALLOWED; 400 401 } 402 403 } 404 405 /// Get the Raylib enum for a keyboard key. 406 raylib.KeyboardKey toRaylib(KeyboardKey key) { 407 408 return cast(raylib.KeyboardKey) key; 409 410 } 411 412 /// Get the Raylib enum for a mouse button. 413 raylib.MouseButton toRaylib(MouseButton button) { 414 415 with (raylib.MouseButton) 416 with (MouseButton) 417 final switch (button) { 418 case scrollLeft: 419 case scrollRight: 420 case scrollUp: 421 case scrollDown: 422 case none: assert(false); 423 case left: return MOUSE_BUTTON_LEFT; 424 case right: return MOUSE_BUTTON_RIGHT; 425 case middle: return MOUSE_BUTTON_MIDDLE; 426 case extra1: return MOUSE_BUTTON_SIDE; 427 case extra2: return MOUSE_BUTTON_EXTRA; 428 case forward: return MOUSE_BUTTON_FORWARD; 429 case back: return MOUSE_BUTTON_BACK; 430 } 431 432 } 433 434 /// Get the Raylib enum for a keyboard key. 435 raylib.GamepadButton toRaylib(GamepadButton button) { 436 437 return cast(raylib.GamepadButton) button; 438 439 } 440 441 /// Convert image to a Raylib image. Do not call `UnloadImage` on the result. 442 raylib.Image toRaylib(fluid.backend.Image image) @trusted { 443 444 raylib.Image result; 445 result.data = cast(void*) image.pixels.ptr; 446 result.width = image.width; 447 result.height = image.height; 448 result.format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 449 result.mipmaps = 1; 450 451 return result; 452 453 } 454 455 /// Convert a fluid texture to a Raylib texture. 456 raylib.Texture toRaylib(fluid.backend.Texture texture) @trusted { 457 458 raylib.Texture result; 459 result.id = texture.id; 460 result.width = texture.width; 461 result.height = texture.height; 462 result.format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 463 result.mipmaps = 1; 464 465 return result; 466 467 }