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 }