Three shapes, two text, three textures, one models and one core, picked for binding surface rather than for how they look. shapes-basic-shapes brings in six draw families nothing had called — the circle and rectangle gradients, the triangles, all three poly draws — and is the first call in the corpus to pass two Colors or three Vector2s at once. shapes-collision-area is get-collision-rec, the only binding that takes two Rectangles and answers a third, on a frame path. shapes-following-eyes is the raymath gap measured rather than worked around: every line of it is vector arithmetic written without a vector library, the way the C writes it. text-input-box drains get-char-pressed's queue, which no example had read, and needed a MouseCursor defenum for set-mouse-cursor. text-writing-anim replaces TextSubtext — unbindable, it answers a pointer into a rotating static buffer — with (string (slice b 0 n)), which is the same operation without the shared state. textures-image-generation runs nine Gen* calls and the gen/upload/unload-image path, all procedural, no file on disk. textures-fog-of-war needed a TextureFilter defenum: the smooth fog edge is entirely :bilinear on a 25x15 render texture, and it is also the first draw-texture-pro with a negative source height. textures-mouse-painting is the same render texture used as a document rather than as scratch, plus the round trip back off the GPU — load-image-from-texture, image-flip-vertical, export-image — which nothing had run. models-box-collisions is the counterexample to "a models example is a binding exercise": nothing in it is a Model, and one BoundingBox defstruct un-refuses four functions. core-3d-picking is the only caller anywhere for Ray and RayCollision, and picking is the inverse of the get-world-to-screen the corpus already had. Added to vendor/raylib: defstructs BoundingBox, Ray and RayCollision; defenums MouseCursor and TextureFilter with their mapping lines in bindings; hand-written declare-c for SetMouseCursor, SetTextureFilter, DrawCubeV, DrawSphere, DrawSphereWires, DrawRay and GetScreenToWorldRay, each excluded from the generated half on the rule bindings already states. generated.flan regenerated against raylib 5.5: 272 declarations, 117 refused, every defstruct, hand-written declare-c and mapped constant agreeing with the header.
256 lines
10 KiB
Plaintext
256 lines
10 KiB
Plaintext
;;;; raylib [textures] example - mouse painting
|
|
;;;;
|
|
;;;; examples/textures/textures_mouse_painting.c. The one example in the
|
|
;;;; category that takes a picture back OFF the GPU and writes it to disk, and
|
|
;;;; that round trip is why it is here:
|
|
;;;;
|
|
;;;; load-image-from-texture → image-flip-vertical → export-image
|
|
;;;;
|
|
;;;; Nothing in the corpus had run it. image-from-image has a headless
|
|
;;;; acceptance case and sand.flan loads images the other way, but the path
|
|
;;;; that reads a render target's pixels back into CPU memory, corrects for
|
|
;;;; GL's bottom-up rows and encodes a PNG had never been exercised at all.
|
|
;;;; All three came out of the generated half of the bindings; nothing needed
|
|
;;;; adding for this file.
|
|
;;;;
|
|
;;;; The flip is the same fact as the negative source height in
|
|
;;;; examples/textures-fog-of-war.flan, met from the other side: on screen the
|
|
;;;; canvas is drawn with a negative-height source rectangle so GL's bottom-up
|
|
;;;; rows come out the right way up, and on save there is no source rectangle
|
|
;;;; to negate, so the pixels are turned over in memory instead. A program
|
|
;;;; that did one and not the other would show a correct picture and save an
|
|
;;;; upside-down one — which is exactly the bug the C's comment warns about.
|
|
;;;;
|
|
;;;; The other thing this puts under load is the render texture as *persistent
|
|
;;;; state*. The fog-of-war target is rebuilt from nothing every frame; this
|
|
;;;; one is the document — it is cleared once before the loop and every stroke
|
|
;;;; after that accumulates in it. Between the two the corpus has both ways a
|
|
;;;; render texture gets used.
|
|
;;;;
|
|
;;;; Two deviations from the C, both in the same place and both deliberate.
|
|
;;;; The C computes colorMouseHover with a `for` whose `else` clause resets it
|
|
;;;; on every miss and whose `break` leaves it set on a hit, so the value that
|
|
;;;; survives depends on the loop exiting early — it works, but only because
|
|
;;;; of the break. Here the reset is before the loop and the loop only ever
|
|
;;;; sets it, which is the same result said plainly. And "no colour hovered"
|
|
;;;; is -1 in both, tested with `>= 0` before it is used as an index.
|
|
|
|
(import rl "vendor:raylib")
|
|
|
|
(defconst screen-width 800)
|
|
(defconst screen-height 450)
|
|
|
|
(defconst max-colors-count 23)
|
|
|
|
;; The palette strip along the top. colors[0] is also the canvas's clear
|
|
;; colour and the "eraser", which is why raywhite is first and not simply
|
|
;; another entry.
|
|
(defvar colors [23 rl/Color])
|
|
(defvar colors-recs [23 rl/Rectangle])
|
|
|
|
(defvar color-selected i32)
|
|
(defvar color-selected-prev i32)
|
|
(defvar color-mouse-hover i32)
|
|
(defvar brush-size f32)
|
|
(defvar mouse-was-pressed bool)
|
|
|
|
(defvar btn-save-rec rl/Rectangle)
|
|
(defvar btn-save-mouse-hover bool)
|
|
(defvar show-save-message bool)
|
|
(defvar save-message-counter i32)
|
|
|
|
(defvar target rl/RenderTexture2D)
|
|
|
|
(defn main [] ()
|
|
(rl/init-window screen-width screen-height
|
|
"raylib [textures] example - mouse painting")
|
|
(defer (rl/close-window))
|
|
|
|
(set colors (array max-colors-count rl/Color))
|
|
(set (at colors 0) rl/raywhite)
|
|
(set (at colors 1) rl/yellow)
|
|
(set (at colors 2) rl/gold)
|
|
(set (at colors 3) rl/orange)
|
|
(set (at colors 4) rl/pink)
|
|
(set (at colors 5) rl/red)
|
|
(set (at colors 6) rl/maroon)
|
|
(set (at colors 7) rl/green)
|
|
(set (at colors 8) rl/lime)
|
|
(set (at colors 9) rl/darkgreen)
|
|
(set (at colors 10) rl/skyblue)
|
|
(set (at colors 11) rl/blue)
|
|
(set (at colors 12) rl/darkblue)
|
|
(set (at colors 13) rl/purple)
|
|
(set (at colors 14) rl/violet)
|
|
(set (at colors 15) rl/darkpurple)
|
|
(set (at colors 16) rl/beige)
|
|
(set (at colors 17) rl/brown)
|
|
(set (at colors 18) rl/darkbrown)
|
|
(set (at colors 19) rl/lightgray)
|
|
(set (at colors 20) rl/gray)
|
|
(set (at colors 21) rl/darkgray)
|
|
(set (at colors 22) rl/black)
|
|
|
|
;; 30 wide with a 2-pixel gap, starting 10 from the left.
|
|
(set colors-recs (array max-colors-count rl/Rectangle))
|
|
(dotimes [i max-colors-count]
|
|
(set (at colors-recs i)
|
|
(rl/Rectangle {.x (+ 10.0 (* 32.0 (f32 i)))
|
|
.y 10.0 .width 30.0 .height 30.0})))
|
|
|
|
(set color-selected 0)
|
|
(set color-selected-prev 0)
|
|
(set color-mouse-hover 0)
|
|
(set brush-size 20.0)
|
|
(set mouse-was-pressed false)
|
|
|
|
(set btn-save-rec (rl/Rectangle {.x 750.0 .y 10.0 .width 40.0 .height 30.0}))
|
|
(set btn-save-mouse-hover false)
|
|
(set show-save-message false)
|
|
(set save-message-counter 0)
|
|
|
|
;; The canvas. It is the document, not a scratch buffer: nothing clears it
|
|
;; again until the user presses C.
|
|
(set target (rl/load-render-texture screen-width screen-height))
|
|
(defer (rl/unload-render-texture target))
|
|
|
|
(rl/begin-texture-mode target)
|
|
(rl/clear-background (at colors 0))
|
|
(rl/end-texture-mode)
|
|
|
|
;; 120 and not 60: the stroke is a circle stamped at the mouse position once
|
|
;; per frame, so the gaps between stamps during a fast drag are a function
|
|
;; of the frame rate. The C's comment says as much.
|
|
(rl/set-target-fps 120)
|
|
|
|
(until (rl/window-should-close?)
|
|
;; Update
|
|
(let [mouse-pos (rl/get-mouse-position)]
|
|
(if (rl/key-pressed? :right)
|
|
(set color-selected (+ color-selected 1))
|
|
(when (rl/key-pressed? :left)
|
|
(set color-selected (- color-selected 1))))
|
|
(set color-selected (clamp color-selected 0 (- max-colors-count 1)))
|
|
|
|
;; Which swatch the pointer is over, or -1. See the header comment: the
|
|
;; reset is here rather than in an else branch inside the loop.
|
|
(set color-mouse-hover -1)
|
|
(dotimes [i max-colors-count]
|
|
(when (rl/collision-point-rec? mouse-pos (at colors-recs i))
|
|
(set color-mouse-hover i)
|
|
(break)))
|
|
|
|
(when (and (>= color-mouse-hover 0) (rl/mouse-button-pressed? :left))
|
|
(set color-selected color-mouse-hover)
|
|
(set color-selected-prev color-selected))
|
|
|
|
(set brush-size (clamp (+ brush-size (* (rl/get-mouse-wheel-move) 5.0))
|
|
2.0 50.0))
|
|
|
|
(when (rl/key-pressed? :c)
|
|
(rl/begin-texture-mode target)
|
|
(rl/clear-background (at colors 0))
|
|
(rl/end-texture-mode))
|
|
|
|
;; Paint. The gesture test is what makes the example work on a
|
|
;; touchscreen, where there is no mouse button to hold.
|
|
(when (or (rl/mouse-button-down? :left)
|
|
(= (rl/get-gesture-detected) :drag))
|
|
(rl/begin-texture-mode target)
|
|
;; Above y=50 is the palette strip, and a stroke there would paint
|
|
;; under the toolbar where it could never be seen.
|
|
(when (> (.y mouse-pos) 50.0)
|
|
(rl/draw-circle (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
|
|
brush-size (at colors color-selected)))
|
|
(rl/end-texture-mode))
|
|
|
|
;; Right button erases, which is painting in the clear colour. The
|
|
;; selected swatch is parked while the button is held so the toolbar
|
|
;; shows what is being drawn, and restored on release.
|
|
(if (rl/mouse-button-down? :right)
|
|
(do
|
|
(when (not mouse-was-pressed)
|
|
(set color-selected-prev color-selected)
|
|
(set color-selected 0))
|
|
(set mouse-was-pressed true)
|
|
(rl/begin-texture-mode target)
|
|
(when (> (.y mouse-pos) 50.0)
|
|
(rl/draw-circle (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
|
|
brush-size (at colors 0)))
|
|
(rl/end-texture-mode))
|
|
(when (and (rl/mouse-button-released? :right) mouse-was-pressed)
|
|
(set color-selected color-selected-prev)
|
|
(set mouse-was-pressed false)))
|
|
|
|
(set btn-save-mouse-hover (rl/collision-point-rec? mouse-pos btn-save-rec))
|
|
|
|
;; The round trip. See the header comment for why the flip is here and
|
|
;; not on the draw.
|
|
(when (or (and btn-save-mouse-hover (rl/mouse-button-released? :left))
|
|
(rl/key-pressed? :s))
|
|
(let [image (rl/load-image-from-texture (.texture target))]
|
|
(rl/image-flip-vertical (addr image))
|
|
(rl/export-image image "my_amazing_texture_painting.png")
|
|
(rl/unload-image image))
|
|
(set show-save-message true))
|
|
|
|
(when show-save-message
|
|
(set save-message-counter (+ save-message-counter 1))
|
|
(when (> save-message-counter 240)
|
|
(set show-save-message false)
|
|
(set save-message-counter 0)))
|
|
|
|
;; Draw
|
|
(rl/begin-drawing)
|
|
(rl/clear-background rl/raywhite)
|
|
|
|
;; The canvas, flipped by the negative source height.
|
|
(rl/draw-texture-rec (.texture target)
|
|
(rl/Rectangle {.x 0.0 .y 0.0
|
|
.width (f32 (.width (.texture target)))
|
|
.height (- 0.0 (f32 (.height (.texture target))))})
|
|
(rl/Vector2 {.x 0.0 .y 0.0})
|
|
rl/white)
|
|
|
|
;; The brush preview, drawn on the screen and not into the canvas.
|
|
(when (> (.y mouse-pos) 50.0)
|
|
(if (rl/mouse-button-down? :right)
|
|
(rl/draw-circle-lines (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
|
|
brush-size rl/gray)
|
|
(rl/draw-circle (rl/get-mouse-x) (rl/get-mouse-y)
|
|
brush-size (at colors color-selected))))
|
|
|
|
;; The toolbar, over the canvas.
|
|
(rl/draw-rectangle 0 0 (rl/get-screen-width) 50 rl/raywhite)
|
|
(rl/draw-line 0 50 (rl/get-screen-width) 50 rl/lightgray)
|
|
|
|
(dotimes [i max-colors-count]
|
|
(rl/draw-rectangle-rec (at colors-recs i) (at colors i)))
|
|
;; The first swatch is raywhite on raywhite, so it needs an outline to
|
|
;; be visible at all.
|
|
(rl/draw-rectangle-lines 10 10 30 30 rl/lightgray)
|
|
|
|
(when (>= color-mouse-hover 0)
|
|
(rl/draw-rectangle-rec (at colors-recs color-mouse-hover)
|
|
(rl/fade rl/white 0.6)))
|
|
|
|
(let [r (at colors-recs color-selected)]
|
|
(rl/draw-rectangle-lines-ex
|
|
(rl/Rectangle {.x (- (.x r) 2.0) .y (- (.y r) 2.0)
|
|
.width (+ (.width r) 4.0) .height (+ (.height r) 4.0)})
|
|
2.0 rl/black))
|
|
|
|
(rl/draw-rectangle-lines-ex btn-save-rec 2.0
|
|
(if btn-save-mouse-hover rl/red rl/black))
|
|
(rl/draw-text "SAVE!" 755 20 10
|
|
(if btn-save-mouse-hover rl/red rl/black))
|
|
|
|
(when show-save-message
|
|
(rl/draw-rectangle 0 0 (rl/get-screen-width) (rl/get-screen-height)
|
|
(rl/fade rl/raywhite 0.8))
|
|
(rl/draw-rectangle 0 150 (rl/get-screen-width) 80 rl/black)
|
|
(rl/draw-text "IMAGE SAVED: my_amazing_texture_painting.png"
|
|
150 180 20 rl/raywhite))
|
|
|
|
(rl/end-drawing))))
|