From 25f56c24a83682e7b306dff4c03d161efe66b765 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sun, 13 Sep 2026 17:58:16 +0700 Subject: [PATCH] A begin/end pair that cannot come apart, and what it still cannot promise vendor/raylib/modes.flan: five macros over the five pairs the package binds -- with-drawing, with-mode-2d, with-mode-3d, with-texture-mode, with-scissor-mode. A second file with no declare-c in it, split out on vector.flan's reasoning: raylib.flan is the package's statement about C and nothing here names C, so nothing here can be made wrong by raylib changing. Each expands to (do (begin-... args) body... (end-...)) -- the calls the author used to type, in the order they typed them. No let, no gensym: nothing binds a name, so there is nothing for a caller's name to collide with. What it removes is the End* that is missing, wrong, or no longer beside its Begin*. What it cannot remove is a body leaving through the unwind path: a return or an invoke-restart skips the rest of the do and the End* with it. defer is the obvious fix and is refused inside a loop body, which is where a pair always lives -- checked, not assumed. So sand.flan's discipline stays: keep the restart boundary outside the pair. 35 call sites converted across examples/ and sand.flan. The one left is core-scissor-test.flan, whose Begin and End sit in two separate `when`s with the drawing between them -- a conditional pair is a shape a bracketing macro cannot express. test/programs/rl-with.flan covers with-scissor-mode, which no example can, with a frame function unreachable from main so it needs no libraylib; rl-with-reject.flan is the arity half. --- BUILT.md | 59 ++++++++ examples/core-2d-camera.flan | 61 ++++---- examples/core-3d-picking.flan | 69 +++++---- examples/core-basic-window.flan | 9 +- examples/core-delta-time.flan | 80 +++++------ examples/core-input-gamepad.flan | 168 +++++++++++----------- examples/core-input-gestures-testbed.flan | 60 ++++---- examples/core-input-gestures.flan | 44 +++--- examples/core-input-keys.flan | 9 +- examples/core-input-mouse-wheel.flan | 21 ++- examples/core-input-mouse.flan | 19 ++- examples/core-input-multitouch.flan | 40 +++--- examples/core-input-virtual-controls.flan | 34 +++-- examples/core-scissor-test.flan | 28 ++-- examples/core-window-flags.flan | 86 ++++++----- examples/core-window-should-close.flan | 20 ++- examples/core-world-screen.flan | 53 ++++--- examples/models-box-collisions.flan | 35 ++--- examples/shapes-basic-shapes.flan | 76 +++++----- examples/shapes-collision-area.flan | 52 ++++--- examples/shapes-following-eyes.flan | 22 ++- examples/text-input-box.flan | 62 ++++---- examples/text-writing-anim.flan | 20 ++- examples/textures-fog-of-war.flan | 99 +++++++------ examples/textures-image-generation.flan | 40 +++--- examples/textures-mouse-painting.flan | 118 ++++++++------- sand.flan | 15 +- test/programs/rl-with-reject.flan | 16 +++ test/programs/rl-with.flan | 43 ++++++ test/test_acceptance.ml | 21 +++ vendor/raylib/modes.flan | 125 ++++++++++++++++ 31 files changed, 906 insertions(+), 698 deletions(-) create mode 100644 test/programs/rl-with-reject.flan create mode 100644 test/programs/rl-with.flan create mode 100644 vendor/raylib/modes.flan diff --git a/BUILT.md b/BUILT.md index 2a14e59..21c5f72 100644 --- a/BUILT.md +++ b/BUILT.md @@ -5229,3 +5229,62 @@ route neither covers, and says so rather than claiming to be a design. `test/programs/generics.flan` runs one written body at two key types; `test/programs/generic-map-reject.flan` is the other half, a call site at `f64` refused against the clause. + +## The begin/end pairs are macros now, and the pair is all they can promise + +`vendor/raylib/modes.flan` — a second file in the raylib package with no `declare-c` in it, split out on +`vector.flan`'s reasoning exactly: `raylib.flan` is the package's statement about C and the file the header check +reads signatures out of, and nothing here can be made wrong by raylib changing because nothing here names C. + +Five macros, one per pair the package binds: `with-drawing`, `with-mode-2d`, `with-mode-3d`, `with-texture-mode`, +`with-scissor-mode`. Each expands to `(do (begin-… args) body… (end-…))` — the calls the author used to type, in the +order they used to type them, with the argument evaluated once where it always was. No `let` and no `gensym` +anywhere: nothing binds a name, so there is no name a caller's could collide with, and introducing one in a +deliberately non-hygienic macro that does not need it is a step backwards. NEXT.md called these "three-line macros +once the expander lands" when it rejected `unwind-protect`, and they are. + +They arrive qualified, the rule every declaration in a package follows: the importer writes `(rl/with-drawing …)` +and a bare `(with-drawing …)` is an unknown name. The expansions name `begin-drawing` and friends unqualified and +the expander qualifies them on the way out — `test/programs/pkgs/mac/mac.flan` is that rule's worked example, and +these are its first non-test use. + +**What the macro removes is not what a reader assumes it removes.** It removes *the `End*` is missing, or is the +wrong one, or drifted away from its `Begin*` during an edit* — which is the whole of the class that actually bites, +and `EndMode2D` closing a `BeginMode3D` type-checks fine and corrupts the matrix stack. It does **not** remove a +body that leaves through the unwind path: a `return`, or an `invoke-restart` reaching an enclosing `restart-case`, +skips the rest of the `do` and the `End*` with it, exactly as it skipped a hand-written one. + +`defer` is the obvious fix and is not available. It is a compile-time construct — the forms are copied into the +function's exit paths — so it is refused inside a loop body, and a begin/end pair lives inside the game loop +essentially always. The refusal was checked rather than assumed: + +``` +defer is not allowed inside a loop body — a defer is copied into every exit path of the function, so it +always registers and always runs at function exit. +``` + +Were it permitted there it would be wrong in the worse direction: one `EndDrawing` at function exit for N +`BeginDrawing`s, which is the silent-wrongness class the refusal exists for. A macro expanding to a `defer` would +therefore compile at a function body's top level and be refused in the one place anybody writes it. So the +discipline `sand.flan` already wrote down stays the discipline — **keep the restart boundary outside the pair**, so +choosing `continue` for a frame abandons the update and still reaches the drawing. spec-conditions §5 is the rule +behind that: a transfer runs the intervening frames' `defer`s and moves control, and there are none here to run. +`sand.flan`'s comment says so at the loop, and the macro's header says so at the definition. + +**35 call sites converted** across `examples/` and `sand.flan`: 26 `with-drawing`, 5 `with-texture-mode`, 3 +`with-mode-3d`, 1 `with-mode-2d`. Every begin/end pair in the corpus except one — `examples/core-scissor-test.flan` +turns its scissor on and off from a flag, so its `Begin` and its `End` sit in two separate `when`s with the drawing +between them and there is no body to wrap. That site is left hand-written, and it is the honest illustration of the +macro's limit rather than an oversight: a conditional pair is a shape a bracketing macro cannot express without +duplicating the body. + +`sand.flan` converted its draw pair and stays at parity — `lisp/sand.lisp` writes `rl:with-drawing` there, which is +what makes it a port of the reference version rather than an addition to it. Its header note "no user-written +macros" now reads "no macros of its own", because `rl/with-drawing` is the binding package's, as in the Lisp. + +`with-scissor-mode` is the one no example exercises, so `test/programs/rl-with.flan` does: all five expanded in a +`frame` function deliberately **unreachable from `main`**, which is what lets the case run with no libraylib at all +— `reach.ml` answers the link from the checked program, and `check` still walks every line. Nothing there draws, +because nothing headless can. `rl-with-reject.flan` is the arity half: a macro cannot signal while it expands, so +the prelude's idiom is to answer a symbol that is not a name and reads as the sentence the caller needs +(`with-mode-2d-takes-a-camera-and-a-body`), with the expander's note beneath it naming the macro at the call site. diff --git a/examples/core-2d-camera.flan b/examples/core-2d-camera.flan index 4ba9217..4745777 100644 --- a/examples/core-2d-camera.flan +++ b/examples/core-2d-camera.flan @@ -99,44 +99,39 @@ (set (.rotation camera) 0.0)) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - ;; Everything between these two is in world space and goes through the - ;; camera. The ground and the crosshair lines are drawn far outside the - ;; window on purpose: at zoom 0.1 they still have to reach the edges. - (rl/begin-mode-2d camera) + ;; Everything inside with-mode-2d is in world space and goes through + ;; the camera. The ground and the crosshair lines are drawn far outside + ;; the window on purpose: at zoom 0.1 they still have to reach the edges. + (rl/with-mode-2d camera + (rl/draw-rectangle -6000 320 13000 8000 rl/darkgray) - (rl/draw-rectangle -6000 320 13000 8000 rl/darkgray) + (dotimes [i max-buildings] + (rl/draw-rectangle-rec (at buildings i) (at build-colors i))) - (dotimes [i max-buildings] - (rl/draw-rectangle-rec (at buildings i) (at build-colors i))) + (rl/draw-rectangle-rec player rl/red) - (rl/draw-rectangle-rec player rl/red) + (rl/draw-line (i32 (.x (.target camera))) (* screen-height -10) + (i32 (.x (.target camera))) (* screen-height 10) rl/green) + (rl/draw-line (* screen-width -10) (i32 (.y (.target camera))) + (* screen-width 10) (i32 (.y (.target camera))) rl/green)) - (rl/draw-line (i32 (.x (.target camera))) (* screen-height -10) - (i32 (.x (.target camera))) (* screen-height 10) rl/green) - (rl/draw-line (* screen-width -10) (i32 (.y (.target camera))) - (* screen-width 10) (i32 (.y (.target camera))) rl/green) + ;; And everything after it is in screen space again — the frame drawn + ;; around the window is how the example shows the difference. + (rl/draw-text "SCREEN AREA" 640 10 20 rl/red) - (rl/end-mode-2d) + (rl/draw-rectangle 0 0 screen-width 5 rl/red) + (rl/draw-rectangle 0 5 5 (- screen-height 10) rl/red) + (rl/draw-rectangle (- screen-width 5) 5 5 (- screen-height 10) rl/red) + (rl/draw-rectangle 0 (- screen-height 5) screen-width 5 rl/red) - ;; And everything after it is in screen space again — the frame drawn - ;; around the window is how the example shows the difference. - (rl/draw-text "SCREEN AREA" 640 10 20 rl/red) + (rl/draw-rectangle 10 10 250 113 (rl/fade rl/skyblue 0.5)) + (rl/draw-rectangle-lines 10 10 250 113 rl/blue) - (rl/draw-rectangle 0 0 screen-width 5 rl/red) - (rl/draw-rectangle 0 5 5 (- screen-height 10) rl/red) - (rl/draw-rectangle (- screen-width 5) 5 5 (- screen-height 10) rl/red) - (rl/draw-rectangle 0 (- screen-height 5) screen-width 5 rl/red) - - (rl/draw-rectangle 10 10 250 113 (rl/fade rl/skyblue 0.5)) - (rl/draw-rectangle-lines 10 10 250 113 rl/blue) - - (rl/draw-text "Free 2d camera controls:" 20 20 10 rl/black) - (rl/draw-text "- Right/Left to move Offset" 40 40 10 rl/darkgray) - (rl/draw-text "- Mouse Wheel to Zoom in-out" 40 60 10 rl/darkgray) - (rl/draw-text "- A / S to Rotate" 40 80 10 rl/darkgray) - (rl/draw-text "- R to reset Zoom and Rotation" 40 100 10 rl/darkgray) - - (rl/end-drawing))) + (rl/draw-text "Free 2d camera controls:" 20 20 10 rl/black) + (rl/draw-text "- Right/Left to move Offset" 40 40 10 rl/darkgray) + (rl/draw-text "- Mouse Wheel to Zoom in-out" 40 60 10 rl/darkgray) + (rl/draw-text "- A / S to Rotate" 40 80 10 rl/darkgray) + (rl/draw-text "- R to reset Zoom and Rotation" 40 100 10 rl/darkgray)))) diff --git a/examples/core-3d-picking.flan b/examples/core-3d-picking.flan index f1831ce..3cc1c2e 100644 --- a/examples/core-3d-picking.flan +++ b/examples/core-3d-picking.flan @@ -113,46 +113,41 @@ (set (.hit collision) false))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/begin-mode-3d camera) + (rl/with-mode-3d camera + (if (.hit collision) + (do + (rl/draw-cube cube-position (.x cube-size) (.y cube-size) (.z cube-size) + rl/red) + (rl/draw-cube-wires cube-position (.x cube-size) (.y cube-size) + (.z cube-size) rl/maroon) + ;; A slightly larger wireframe around the selection, which is the + ;; whole visual feedback of the example. + (rl/draw-cube-wires cube-position (+ (.x cube-size) 0.2) + (+ (.y cube-size) 0.2) (+ (.z cube-size) 0.2) + rl/green)) + (do + (rl/draw-cube cube-position (.x cube-size) (.y cube-size) (.z cube-size) + rl/gray) + (rl/draw-cube-wires cube-position (.x cube-size) (.y cube-size) + (.z cube-size) rl/darkgray))) - (if (.hit collision) - (do - (rl/draw-cube cube-position (.x cube-size) (.y cube-size) (.z cube-size) - rl/red) - (rl/draw-cube-wires cube-position (.x cube-size) (.y cube-size) - (.z cube-size) rl/maroon) - ;; A slightly larger wireframe around the selection, which is the - ;; whole visual feedback of the example. - (rl/draw-cube-wires cube-position (+ (.x cube-size) 0.2) - (+ (.y cube-size) 0.2) (+ (.z cube-size) 0.2) - rl/green)) - (do - (rl/draw-cube cube-position (.x cube-size) (.y cube-size) (.z cube-size) - rl/gray) - (rl/draw-cube-wires cube-position (.x cube-size) (.y cube-size) - (.z cube-size) rl/darkgray))) + ;; raylib draws the ray a thousand units long, so it reads as a line + ;; crossing the scene rather than as a segment with an end. + (rl/draw-ray ray rl/maroon) + (rl/draw-grid 10 1.0)) - ;; raylib draws the ray a thousand units long, so it reads as a line - ;; crossing the scene rather than as a segment with an end. - (rl/draw-ray ray rl/maroon) - (rl/draw-grid 10 1.0) + (rl/draw-text "Try clicking on the box with your mouse!" 240 10 20 + rl/darkgray) - (rl/end-mode-3d) + (when (.hit collision) + (rl/draw-text "BOX SELECTED" + (/ (- screen-width (rl/measure-text "BOX SELECTED" 30)) 2) + (i32 (* (f32 screen-height) 0.1)) 30 rl/green)) - (rl/draw-text "Try clicking on the box with your mouse!" 240 10 20 - rl/darkgray) + (rl/draw-text "Right click mouse to toggle camera controls" 10 430 10 + rl/gray) - (when (.hit collision) - (rl/draw-text "BOX SELECTED" - (/ (- screen-width (rl/measure-text "BOX SELECTED" 30)) 2) - (i32 (* (f32 screen-height) 0.1)) 30 rl/green)) - - (rl/draw-text "Right click mouse to toggle camera controls" 10 430 10 - rl/gray) - - (rl/draw-fps 10 10) - - (rl/end-drawing))) + (rl/draw-fps 10 10)))) diff --git a/examples/core-basic-window.flan b/examples/core-basic-window.flan index 811d2f0..99d80ec 100644 --- a/examples/core-basic-window.flan +++ b/examples/core-basic-window.flan @@ -25,8 +25,7 @@ (rl/set-target-fps 60) (until (rl/window-should-close?) - (rl/begin-drawing) - (rl/clear-background rl/raywhite) - (rl/draw-text "Congrats! You created your first window!" 190 200 20 - rl/lightgray) - (rl/end-drawing))) + (rl/with-drawing + (rl/clear-background rl/raywhite) + (rl/draw-text "Congrats! You created your first window!" 190 200 20 + rl/lightgray)))) diff --git a/examples/core-delta-time.flan b/examples/core-delta-time.flan index af61d90..4ae1b4c 100644 --- a/examples/core-delta-time.flan +++ b/examples/core-delta-time.flan @@ -70,50 +70,48 @@ (set (.x frame-circle) 0.0)) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/draw-circle-v delta-circle circle-radius rl/red) - (rl/draw-circle-v frame-circle circle-radius rl/blue) + (rl/draw-circle-v delta-circle circle-radius rl/red) + (rl/draw-circle-v frame-circle circle-radius rl/blue) - ;; "FPS: unlimited (%i)" when the target is 0, "FPS: %i (target: %i)" - ;; otherwise — the C's own branch, reassembled out of literals and - ;; draw-int. `x` walks along the line as each piece is drawn, which is what - ;; the returned width from draw-int is for. - (if (<= current-fps 0) + ;; "FPS: unlimited (%i)" when the target is 0, "FPS: %i (target: %i)" + ;; otherwise — the C's own branch, reassembled out of literals and + ;; draw-int. `x` walks along the line as each piece is drawn, which is what + ;; the returned width from draw-int is for. + (if (<= current-fps 0) + (let [x 10] + (rl/draw-text "FPS: unlimited (" x 10 20 rl/darkgray) + (set x (+ x (rl/measure-text "FPS: unlimited (" 20))) + (set x (+ x (d/draw-int (rl/get-fps) x 10 20 rl/darkgray))) + (rl/draw-text ")" x 10 20 rl/darkgray)) + (let [x 10] + (rl/draw-text "FPS: " x 10 20 rl/darkgray) + (set x (+ x (rl/measure-text "FPS: " 20))) + (set x (+ x (d/draw-int (rl/get-fps) x 10 20 rl/darkgray))) + (rl/draw-text " (target: " x 10 20 rl/darkgray) + (set x (+ x (rl/measure-text " (target: " 20))) + (set x (+ x (d/draw-int current-fps x 10 20 rl/darkgray))) + (rl/draw-text ")" x 10 20 rl/darkgray))) + + ;; The C's line, kept exactly — seconds, labelled ms. (let [x 10] - (rl/draw-text "FPS: unlimited (" x 10 20 rl/darkgray) - (set x (+ x (rl/measure-text "FPS: unlimited (" 20))) - (set x (+ x (d/draw-int (rl/get-fps) x 10 20 rl/darkgray))) - (rl/draw-text ")" x 10 20 rl/darkgray)) + (rl/draw-text "Frame time: " x 30 20 rl/darkgray) + (set x (+ x (rl/measure-text "Frame time: " 20))) + (set x (+ x (d/draw-f32 (rl/get-frame-time) 2 x 30 20 rl/darkgray))) + (rl/draw-text " ms" x 30 20 rl/darkgray)) + ;; And the same number in the unit the label claims, so the file does not + ;; have to be read to notice. (let [x 10] - (rl/draw-text "FPS: " x 10 20 rl/darkgray) - (set x (+ x (rl/measure-text "FPS: " 20))) - (set x (+ x (d/draw-int (rl/get-fps) x 10 20 rl/darkgray))) - (rl/draw-text " (target: " x 10 20 rl/darkgray) - (set x (+ x (rl/measure-text " (target: " 20))) - (set x (+ x (d/draw-int current-fps x 10 20 rl/darkgray))) - (rl/draw-text ")" x 10 20 rl/darkgray))) + (rl/draw-text "(really " x 50 20 rl/lightgray) + (set x (+ x (rl/measure-text "(really " 20))) + (set x (+ x (d/draw-f32 (* (rl/get-frame-time) 1000.0) 2 x 50 20 + rl/lightgray))) + (rl/draw-text " ms)" x 50 20 rl/lightgray)) - ;; The C's line, kept exactly — seconds, labelled ms. - (let [x 10] - (rl/draw-text "Frame time: " x 30 20 rl/darkgray) - (set x (+ x (rl/measure-text "Frame time: " 20))) - (set x (+ x (d/draw-f32 (rl/get-frame-time) 2 x 30 20 rl/darkgray))) - (rl/draw-text " ms" x 30 20 rl/darkgray)) - ;; And the same number in the unit the label claims, so the file does not - ;; have to be read to notice. - (let [x 10] - (rl/draw-text "(really " x 50 20 rl/lightgray) - (set x (+ x (rl/measure-text "(really " 20))) - (set x (+ x (d/draw-f32 (* (rl/get-frame-time) 1000.0) 2 x 50 20 - rl/lightgray))) - (rl/draw-text " ms)" x 50 20 rl/lightgray)) + (rl/draw-text "Use the scroll wheel to change the fps limit, r to reset" + 10 70 20 rl/darkgray) - (rl/draw-text "Use the scroll wheel to change the fps limit, r to reset" - 10 70 20 rl/darkgray) - - (rl/draw-text "FUNC: x += GetFrameTime()*speed" 10 110 20 rl/red) - (rl/draw-text "FUNC: x += speed" 10 260 20 rl/blue) - - (rl/end-drawing))) + (rl/draw-text "FUNC: x += GetFrameTime()*speed" 10 110 20 rl/red) + (rl/draw-text "FUNC: x += speed" 10 260 20 rl/blue)))) diff --git a/examples/core-input-gamepad.flan b/examples/core-input-gamepad.flan index 90a53df..c7c8e6b 100644 --- a/examples/core-input-gamepad.flan +++ b/examples/core-input-gamepad.flan @@ -183,98 +183,96 @@ .width 75.0 .height 24.0})] ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (if (rl/gamepad-available? gamepad) - (do - ;; The C draws `TextFormat("GP%d: %s", gamepad, GetGamepadName(...))`. - ;; The name is unbindable — see the header — so this is the index and - ;; the word the C would have followed it with. - (rl/draw-text "GP" 10 10 10 rl/black) - (let [x (+ 10 (rl/measure-text "GP" 10))] - (rl/draw-text ": CONNECTED (name unbindable)" - (+ x (d/draw-int gamepad x 10 10 rl/black)) - 10 10 rl/black)) + (if (rl/gamepad-available? gamepad) + (do + ;; The C draws `TextFormat("GP%d: %s", gamepad, GetGamepadName(...))`. + ;; The name is unbindable — see the header — so this is the index and + ;; the word the C would have followed it with. + (rl/draw-text "GP" 10 10 10 rl/black) + (let [x (+ 10 (rl/measure-text "GP" 10))] + (rl/draw-text ": CONNECTED (name unbindable)" + (+ x (d/draw-int gamepad x 10 10 rl/black)) + 10 10 rl/black)) - (let [lx (deadzone (rl/get-gamepad-axis-movement gamepad :left-x) - stick-deadzone) - ly (deadzone (rl/get-gamepad-axis-movement gamepad :left-y) - stick-deadzone) - rx (deadzone (rl/get-gamepad-axis-movement gamepad :right-x) - stick-deadzone) - ry (deadzone (rl/get-gamepad-axis-movement gamepad :right-y) - stick-deadzone) - lt (trigger-deadzoned - (rl/get-gamepad-axis-movement gamepad :left-trigger)) - rt (trigger-deadzoned - (rl/get-gamepad-axis-movement gamepad :right-trigger))] + (let [lx (deadzone (rl/get-gamepad-axis-movement gamepad :left-x) + stick-deadzone) + ly (deadzone (rl/get-gamepad-axis-movement gamepad :left-y) + stick-deadzone) + rx (deadzone (rl/get-gamepad-axis-movement gamepad :right-x) + stick-deadzone) + ry (deadzone (rl/get-gamepad-axis-movement gamepad :right-y) + stick-deadzone) + lt (trigger-deadzoned + (rl/get-gamepad-axis-movement gamepad :left-trigger)) + rt (trigger-deadzoned + (rl/get-gamepad-axis-movement gamepad :right-trigger))] - (draw-pad-background) - (draw-pad-buttons) + (draw-pad-background) + (draw-pad-buttons) - (draw-stick 345 260 lx ly - (rl/gamepad-button-down? gamepad :left-thumb)) - (draw-stick 465 260 rx ry - (rl/gamepad-button-down? gamepad :right-thumb)) + (draw-stick 345 260 lx ly + (rl/gamepad-button-down? gamepad :left-thumb)) + (draw-stick 465 260 rx ry + (rl/gamepad-button-down? gamepad :right-thumb)) - ;; The triggers as bars filling upward from a grey track. The +1 - ;; and the halving turn raylib's [-1, 1] into [0, 1]. - (rl/draw-rectangle 151 110 15 70 rl/gray) - (rl/draw-rectangle 644 110 15 70 rl/gray) - (rl/draw-rectangle 151 110 15 (i32 (* (/ (+ 1.0 lt) 2.0) 70.0)) - rl/red) - (rl/draw-rectangle 644 110 15 (i32 (* (/ (+ 1.0 rt) 2.0) 70.0)) - rl/red)) + ;; The triggers as bars filling upward from a grey track. The +1 + ;; and the halving turn raylib's [-1, 1] into [0, 1]. + (rl/draw-rectangle 151 110 15 70 rl/gray) + (rl/draw-rectangle 644 110 15 70 rl/gray) + (rl/draw-rectangle 151 110 15 (i32 (* (/ (+ 1.0 lt) 2.0) 70.0)) + rl/red) + (rl/draw-rectangle 644 110 15 (i32 (* (/ (+ 1.0 rt) 2.0) 70.0)) + rl/red)) - (rl/draw-text "DETECTED AXIS [" 10 50 10 rl/maroon) - (let [x (+ 10 (rl/measure-text "DETECTED AXIS [" 10))] - (rl/draw-text "]:" (+ x (d/draw-int axis-count x 50 10 rl/maroon)) - 50 10 rl/maroon)) + (rl/draw-text "DETECTED AXIS [" 10 50 10 rl/maroon) + (let [x (+ 10 (rl/measure-text "DETECTED AXIS [" 10))] + (rl/draw-text "]:" (+ x (d/draw-int axis-count x 50 10 rl/maroon)) + 50 10 rl/maroon)) - (dotimes [i axis-count] - (rl/draw-text "AXIS " 20 (+ 70 (* 20 i)) 10 rl/darkgray) - (let [x (+ 20 (rl/measure-text "AXIS " 10)) - y (+ 70 (* 20 i))] - (set x (+ x (d/draw-int i x y 10 rl/darkgray))) - (rl/draw-text ": " x y 10 rl/darkgray) - (set x (+ x (rl/measure-text ": " 10))) - ;; The raw reading, not the deadzoned one — the C shows the raw - ;; value here too, which is what makes the deadzone visible as a - ;; difference between this row and the stick. - (d/draw-f32 (axis-at gamepad i) 2 x y 10 - rl/darkgray))) + (dotimes [i axis-count] + (rl/draw-text "AXIS " 20 (+ 70 (* 20 i)) 10 rl/darkgray) + (let [x (+ 20 (rl/measure-text "AXIS " 10)) + y (+ 70 (* 20 i))] + (set x (+ x (d/draw-int i x y 10 rl/darkgray))) + (rl/draw-text ": " x y 10 rl/darkgray) + (set x (+ x (rl/measure-text ": " 10))) + ;; The raw reading, not the deadzoned one — the C shows the raw + ;; value here too, which is what makes the deadzone visible as a + ;; difference between this row and the stick. + (d/draw-f32 (axis-at gamepad i) 2 x y 10 + rl/darkgray))) - ;; Drawn and inert. See the header: SetGamepadVibration is a stub in - ;; this raylib and is not bound. - (rl/draw-rectangle-rec vibrate-rect rl/skyblue) - (rl/draw-text "VIBRATE" (+ (i32 (.x vibrate-rect)) 14) - (+ (i32 (.y vibrate-rect)) 1) 10 rl/darkgray) - (rl/draw-text "(not bound: raylib stub)" 95 - (+ (i32 (.y vibrate-rect)) 7) 10 rl/gray) + ;; Drawn and inert. See the header: SetGamepadVibration is a stub in + ;; this raylib and is not bound. + (rl/draw-rectangle-rec vibrate-rect rl/skyblue) + (rl/draw-text "VIBRATE" (+ (i32 (.x vibrate-rect)) 14) + (+ (i32 (.y vibrate-rect)) 1) 10 rl/darkgray) + (rl/draw-text "(not bound: raylib stub)" 95 + (+ (i32 (.y vibrate-rect)) 7) 10 rl/gray) - ;; -1 when nothing is pressed, which is why the binding answers an - ;; i32 and not a GamepadButton. - ;; draw-int answers the width it drew, so a branch that ends in one - ;; has type i32 while its sibling has type () and the `if` will not - ;; typecheck — "expected i32, found ()". Both arms end in a - ;; draw-text here, which is the tidy way out; where that is awkward a - ;; trailing `(do)` is the other. - (let [b (rl/get-gamepad-button-pressed)] - (if (>= b 0) - (do (rl/draw-text "DETECTED BUTTON: " 10 430 10 rl/red) - (d/draw-int b (+ 10 (rl/measure-text "DETECTED BUTTON: " 10)) - 430 10 rl/red) - (do)) - (rl/draw-text "DETECTED BUTTON: NONE" 10 430 10 rl/gray)))) + ;; -1 when nothing is pressed, which is why the binding answers an + ;; i32 and not a GamepadButton. + ;; draw-int answers the width it drew, so a branch that ends in one + ;; has type i32 while its sibling has type () and the `if` will not + ;; typecheck — "expected i32, found ()". Both arms end in a + ;; draw-text here, which is the tidy way out; where that is awkward a + ;; trailing `(do)` is the other. + (let [b (rl/get-gamepad-button-pressed)] + (if (>= b 0) + (do (rl/draw-text "DETECTED BUTTON: " 10 430 10 rl/red) + (d/draw-int b (+ 10 (rl/measure-text "DETECTED BUTTON: " 10)) + 430 10 rl/red) + (do)) + (rl/draw-text "DETECTED BUTTON: NONE" 10 430 10 rl/gray)))) - (do - (rl/draw-text "GP" 10 10 10 rl/gray) - (let [x (+ 10 (rl/measure-text "GP" 10))] - (rl/draw-text ": NOT DETECTED" - (+ x (d/draw-int gamepad x 10 10 rl/gray)) - 10 10 rl/gray)) - (rl/draw-text "left/right arrows select another pad" 10 30 10 - rl/lightgray))) - - (rl/end-drawing)))) + (do + (rl/draw-text "GP" 10 10 10 rl/gray) + (let [x (+ 10 (rl/measure-text "GP" 10))] + (rl/draw-text ": NOT DETECTED" + (+ x (d/draw-int gamepad x 10 10 rl/gray)) + 10 10 rl/gray)) + (rl/draw-text "left/right arrows select another pad" 10 30 10 + rl/lightgray))))))) diff --git a/examples/core-input-gestures-testbed.flan b/examples/core-input-gestures-testbed.flan index 3721bf9..1ed7eba 100644 --- a/examples/core-input-gestures-testbed.flan +++ b/examples/core-input-gestures-testbed.flan @@ -328,37 +328,35 @@ (set (at touch-positions i) (rl/get-touch-position i))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/draw-text "*" 165 12 10 rl/black) - (rl/draw-text "Example optimized for Web/HTML5\non Smartphones with Touch Screen." - 175 12 10 rl/black) - (rl/draw-text "*" 165 42 10 rl/black) - (rl/draw-text "While running on Desktop Web Browsers,\ninspect and turn on Touch Emulation." - 175 42 10 rl/black) + (rl/draw-text "*" 165 12 10 rl/black) + (rl/draw-text "Example optimized for Web/HTML5\non Smartphones with Touch Screen." + 175 12 10 rl/black) + (rl/draw-text "*" 165 42 10 rl/black) + (rl/draw-text "While running on Desktop Web Browsers,\ninspect and turn on Touch Emulation." + 175 42 10 rl/black) - (draw-last-gesture touch-count) - (draw-log) - (draw-protractor) + (draw-last-gesture touch-count) + (draw-log) + (draw-protractor) - ;; The pointer itself: every live touch, or the mouse when there is no - ;; touchscreen. The halo is the gesture colour faded, which is what - ;; `fade` was bound for. - (unless (= g :none) - (if (> touch-count 0) - (do - (dotimes [i touch-count] - (let [p (at touch-positions i)] - (rl/draw-circle-v p 50.0 (rl/fade gesture-color 0.5)) - (rl/draw-circle-v p 5.0 gesture-color))) - ;; Two fingers: the line between them, thinner while pinching - ;; out, which is the C's only use of the raw 512. - (when (= touch-count 2) - (rl/draw-line-ex (at touch-positions 0) (at touch-positions 1) - (if (= g :pinch-out) 8.0 12.0) gesture-color))) - (let [m (rl/get-mouse-position)] - (rl/draw-circle-v m 35.0 (rl/fade gesture-color 0.5)) - (rl/draw-circle-v m 5.0 gesture-color)))) - - (rl/end-drawing))))) + ;; The pointer itself: every live touch, or the mouse when there is no + ;; touchscreen. The halo is the gesture colour faded, which is what + ;; `fade` was bound for. + (unless (= g :none) + (if (> touch-count 0) + (do + (dotimes [i touch-count] + (let [p (at touch-positions i)] + (rl/draw-circle-v p 50.0 (rl/fade gesture-color 0.5)) + (rl/draw-circle-v p 5.0 gesture-color))) + ;; Two fingers: the line between them, thinner while pinching + ;; out, which is the C's only use of the raw 512. + (when (= touch-count 2) + (rl/draw-line-ex (at touch-positions 0) (at touch-positions 1) + (if (= g :pinch-out) 8.0 12.0) gesture-color))) + (let [m (rl/get-mouse-position)] + (rl/draw-circle-v m 35.0 (rl/fade gesture-color 0.5)) + (rl/draw-circle-v m 5.0 gesture-color))))))))) diff --git a/examples/core-input-gestures.flan b/examples/core-input-gestures.flan index 5f9ac39..dbf7a5b 100644 --- a/examples/core-input-gestures.flan +++ b/examples/core-input-gestures.flan @@ -80,31 +80,29 @@ (set gestures-count 0))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/draw-rectangle-rec touch-area rl/gray) - (rl/draw-rectangle 225 15 (- screen-width 240) (- screen-height 30) - rl/raywhite) + (rl/draw-rectangle-rec touch-area rl/gray) + (rl/draw-rectangle 225 15 (- screen-width 240) (- screen-height 30) + rl/raywhite) - (rl/draw-text "GESTURES TEST AREA" (- screen-width 270) - (- screen-height 40) 20 (rl/fade rl/gray 0.5)) + (rl/draw-text "GESTURES TEST AREA" (- screen-width 270) + (- screen-height 40) 20 (rl/fade rl/gray 0.5)) - (dotimes [i gestures-count] - (if (= 0 (% i 2)) - (rl/draw-rectangle 10 (+ 30 (* 20 i)) 200 20 - (rl/fade rl/lightgray 0.5)) - (rl/draw-rectangle 10 (+ 30 (* 20 i)) 200 20 - (rl/fade rl/lightgray 0.3))) - ;; The newest entry in maroon and the rest in dark grey, as the C - ;; has it. - (rl/draw-text (at gesture-log i) 35 (+ 36 (* 20 i)) 10 - (if (< i (- gestures-count 1)) rl/darkgray rl/maroon))) + (dotimes [i gestures-count] + (if (= 0 (% i 2)) + (rl/draw-rectangle 10 (+ 30 (* 20 i)) 200 20 + (rl/fade rl/lightgray 0.5)) + (rl/draw-rectangle 10 (+ 30 (* 20 i)) 200 20 + (rl/fade rl/lightgray 0.3))) + ;; The newest entry in maroon and the rest in dark grey, as the C + ;; has it. + (rl/draw-text (at gesture-log i) 35 (+ 36 (* 20 i)) 10 + (if (< i (- gestures-count 1)) rl/darkgray rl/maroon))) - (rl/draw-rectangle-lines 10 29 200 (- screen-height 50) rl/gray) - (rl/draw-text "DETECTED GESTURES" 50 15 10 rl/gray) + (rl/draw-rectangle-lines 10 29 200 (- screen-height 50) rl/gray) + (rl/draw-text "DETECTED GESTURES" 50 15 10 rl/gray) - (unless (= current-gesture :none) - (rl/draw-circle-v touch 30.0 rl/maroon)) - - (rl/end-drawing))))) + (unless (= current-gesture :none) + (rl/draw-circle-v touch 30.0 rl/maroon))))))) diff --git a/examples/core-input-keys.flan b/examples/core-input-keys.flan index 635394a..2d86b84 100644 --- a/examples/core-input-keys.flan +++ b/examples/core-input-keys.flan @@ -40,8 +40,7 @@ (when (rl/key-down? :down) (set (.y ball) (+ (.y ball) 2.0))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) - (rl/draw-text "move the ball with arrow keys" 10 10 20 rl/darkgray) - (rl/draw-circle-v ball 50.0 rl/maroon) - (rl/end-drawing))) + (rl/with-drawing + (rl/clear-background rl/raywhite) + (rl/draw-text "move the ball with arrow keys" 10 10 20 rl/darkgray) + (rl/draw-circle-v ball 50.0 rl/maroon)))) diff --git a/examples/core-input-mouse-wheel.flan b/examples/core-input-mouse-wheel.flan index 825fb92..b0bc1c5 100644 --- a/examples/core-input-mouse-wheel.flan +++ b/examples/core-input-mouse-wheel.flan @@ -42,14 +42,13 @@ (set box-y (- box-y (i32 (* (rl/get-mouse-wheel-move) (f32 scroll-speed))))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) - (rl/draw-rectangle (- (/ screen-width 2) 40) box-y 80 80 rl/maroon) - (rl/draw-text "Use mouse wheel to move the cube up and down!" 10 10 20 - rl/gray) - ;; The two halves of the C's one TextFormat call: the literal, measured, - ;; and then the number starting where it ended. - (rl/draw-text "Box position Y: " 10 40 20 rl/lightgray) - (d/draw-int-padded box-y 3 (+ 10 (rl/measure-text "Box position Y: " 20)) - 40 20 rl/lightgray) - (rl/end-drawing))) + (rl/with-drawing + (rl/clear-background rl/raywhite) + (rl/draw-rectangle (- (/ screen-width 2) 40) box-y 80 80 rl/maroon) + (rl/draw-text "Use mouse wheel to move the cube up and down!" 10 10 20 + rl/gray) + ;; The two halves of the C's one TextFormat call: the literal, measured, + ;; and then the number starting where it ended. + (rl/draw-text "Box position Y: " 10 40 20 rl/lightgray) + (d/draw-int-padded box-y 3 (+ 10 (rl/measure-text "Box position Y: " 20)) + 40 20 rl/lightgray)))) diff --git a/examples/core-input-mouse.flan b/examples/core-input-mouse.flan index 206d689..e533488 100644 --- a/examples/core-input-mouse.flan +++ b/examples/core-input-mouse.flan @@ -49,13 +49,12 @@ :else ball-color)) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) - (rl/draw-circle-v ball 40.0 ball-color) - (rl/draw-text "move ball with mouse and click mouse button to change color" - 10 10 20 rl/darkgray) - (rl/draw-text "Press 'H' to toggle cursor visibility" 10 30 20 rl/darkgray) - (if (rl/cursor-hidden?) - (rl/draw-text "CURSOR HIDDEN" 20 60 20 rl/red) - (rl/draw-text "CURSOR VISIBLE" 20 60 20 rl/lime)) - (rl/end-drawing)))) + (rl/with-drawing + (rl/clear-background rl/raywhite) + (rl/draw-circle-v ball 40.0 ball-color) + (rl/draw-text "move ball with mouse and click mouse button to change color" + 10 10 20 rl/darkgray) + (rl/draw-text "Press 'H' to toggle cursor visibility" 10 30 20 rl/darkgray) + (if (rl/cursor-hidden?) + (rl/draw-text "CURSOR HIDDEN" 20 60 20 rl/red) + (rl/draw-text "CURSOR VISIBLE" 20 60 20 rl/lime)))))) diff --git a/examples/core-input-multitouch.flan b/examples/core-input-multitouch.flan index 2bcad18..b964188 100644 --- a/examples/core-input-multitouch.flan +++ b/examples/core-input-multitouch.flan @@ -45,26 +45,24 @@ (set (at touch-positions i) (rl/get-touch-position i))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (dotimes [i count] - (let [p (at touch-positions i)] - ;; raylib reports (0,0) for a slot that is not being touched, so the - ;; C filters on it and so does this. It means a real touch in the - ;; very top-left corner is dropped; that is raylib's ambiguity, not - ;; something the port introduced. - (when (and (> (.x p) 0.0) (> (.y p) 0.0)) - (rl/draw-circle-v p 34.0 rl/orange) - ;; The C's TextFormat("%d", i). This used to need examples/digits.flan - ;; and a table of one-character strings; (string b) makes the - ;; number a string with no instructions, so it is one draw-text and - ;; this file imports nothing but raylib. - (rl/draw-text (string (i64->bytes (i64 i))) - (- (i32 (.x p)) 10) (- (i32 (.y p)) 70) 40 - rl/black)))) + (dotimes [i count] + (let [p (at touch-positions i)] + ;; raylib reports (0,0) for a slot that is not being touched, so the + ;; C filters on it and so does this. It means a real touch in the + ;; very top-left corner is dropped; that is raylib's ambiguity, not + ;; something the port introduced. + (when (and (> (.x p) 0.0) (> (.y p) 0.0)) + (rl/draw-circle-v p 34.0 rl/orange) + ;; The C's TextFormat("%d", i). This used to need examples/digits.flan + ;; and a table of one-character strings; (string b) makes the + ;; number a string with no instructions, so it is one draw-text and + ;; this file imports nothing but raylib. + (rl/draw-text (string (i64->bytes (i64 i))) + (- (i32 (.x p)) 10) (- (i32 (.y p)) 70) 40 + rl/black)))) - (rl/draw-text "touch the screen at multiple locations to get multiple balls" - 10 10 20 rl/darkgray) - - (rl/end-drawing)))) + (rl/draw-text "touch the screen at multiple locations to get multiple balls" + 10 10 20 rl/darkgray))))) diff --git a/examples/core-input-virtual-controls.flan b/examples/core-input-virtual-controls.flan index 5fdff19..8bdbab9 100644 --- a/examples/core-input-virtual-controls.flan +++ b/examples/core-input-virtual-controls.flan @@ -172,24 +172,22 @@ (move-player pressed (rl/get-frame-time)) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/draw-circle-v player 50.0 rl/maroon) + (rl/draw-circle-v player 50.0 rl/maroon) - (dotimes [i button-max] - (rl/draw-circle-v (at button-positions i) button-radius - (if (= i pressed) rl/darkgray rl/black)) - (let [t (at arrow-tris i)] - (rl/draw-triangle (at t 0) (at t 1) (at t 2) - (at label-colors i)))) + (dotimes [i button-max] + (rl/draw-circle-v (at button-positions i) button-radius + (if (= i pressed) rl/darkgray rl/black)) + (let [t (at arrow-tris i)] + (rl/draw-triangle (at t 0) (at t 1) (at t 2) + (at label-colors i)))) - (rl/draw-text "move the player with D-Pad buttons" 10 10 20 rl/darkgray) - ;; Not in the C: which button the search picked, as a number, so the - ;; headless case and the window agree about the same thing. - (rl/draw-text "button: " 10 34 20 rl/lightgray) - (rl/draw-text (string (i64->bytes (i64 pressed))) - (+ 10 (rl/measure-text "button: " 20)) 34 20 - rl/lightgray) - - (rl/end-drawing)))) + (rl/draw-text "move the player with D-Pad buttons" 10 10 20 rl/darkgray) + ;; Not in the C: which button the search picked, as a number, so the + ;; headless case and the window agree about the same thing. + (rl/draw-text "button: " 10 34 20 rl/lightgray) + (rl/draw-text (string (i64->bytes (i64 pressed))) + (+ 10 (rl/measure-text "button: " 20)) 34 20 + rl/lightgray))))) diff --git a/examples/core-scissor-test.flan b/examples/core-scissor-test.flan index f0672e4..262afb3 100644 --- a/examples/core-scissor-test.flan +++ b/examples/core-scissor-test.flan @@ -51,22 +51,20 @@ (set (.y scissor) (- (f32 (rl/get-mouse-y)) (/ (.height scissor) 2.0))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (when scissor-mode - (rl/begin-scissor-mode (i32 (.x scissor)) (i32 (.y scissor)) - (i32 (.width scissor)) (i32 (.height scissor)))) + (when scissor-mode + (rl/begin-scissor-mode (i32 (.x scissor)) (i32 (.y scissor)) + (i32 (.width scissor)) (i32 (.height scissor)))) - ;; A full-screen rectangle and a line of text. Only the part inside the - ;; scissor rectangle reaches the framebuffer, which is the whole example. - (rl/draw-rectangle 0 0 (rl/get-screen-width) (rl/get-screen-height) rl/red) - (rl/draw-text "Move the mouse around to reveal this text!" 190 200 20 - rl/lightgray) + ;; A full-screen rectangle and a line of text. Only the part inside the + ;; scissor rectangle reaches the framebuffer, which is the whole example. + (rl/draw-rectangle 0 0 (rl/get-screen-width) (rl/get-screen-height) rl/red) + (rl/draw-text "Move the mouse around to reveal this text!" 190 200 20 + rl/lightgray) - (when scissor-mode (rl/end-scissor-mode)) + (when scissor-mode (rl/end-scissor-mode)) - (rl/draw-rectangle-lines-ex scissor 1.0 rl/black) - (rl/draw-text "Press S to toggle scissor test" 10 10 20 rl/black) - - (rl/end-drawing))) + (rl/draw-rectangle-lines-ex scissor 1.0 rl/black) + (rl/draw-text "Press S to toggle scissor test" 10 10 20 rl/black)))) diff --git a/examples/core-window-flags.flan b/examples/core-window-flags.flan index 4bedaaa..819fea0 100644 --- a/examples/core-window-flags.flan +++ b/examples/core-window-flags.flan @@ -127,53 +127,51 @@ (set (.y ball-speed) (* (.y ball-speed) -1.0))) ;; Draw - (rl/begin-drawing) + (rl/with-drawing - ;; A transparent framebuffer wants a transparent clear; anything else - ;; paints over the desktop the flag exists to show. - (if (rl/window-state? rl/flag-window-transparent) - (rl/clear-background rl/blank) - (rl/clear-background rl/raywhite)) + ;; A transparent framebuffer wants a transparent clear; anything else + ;; paints over the desktop the flag exists to show. + (if (rl/window-state? rl/flag-window-transparent) + (rl/clear-background rl/blank) + (rl/clear-background rl/raywhite)) - (rl/draw-circle-v ball-pos ball-radius rl/maroon) - (rl/draw-rectangle-lines-ex - (rl/Rectangle {.x 0.0 .y 0.0 - .width (f32 (rl/get-screen-width)) - .height (f32 (rl/get-screen-height))}) - 4.0 rl/raywhite) + (rl/draw-circle-v ball-pos ball-radius rl/maroon) + (rl/draw-rectangle-lines-ex + (rl/Rectangle {.x 0.0 .y 0.0 + .width (f32 (rl/get-screen-width)) + .height (f32 (rl/get-screen-height))}) + 4.0 rl/raywhite) - (rl/draw-circle-v (rl/get-mouse-position) 10.0 rl/darkblue) + (rl/draw-circle-v (rl/get-mouse-position) 10.0 rl/darkblue) - (rl/draw-fps 10 10) + (rl/draw-fps 10 10) - ;; The C's TextFormat("Screen Size: [%i, %i]", ...), reassembled out of - ;; literals and examples/digits.flan the way the other ports do it: `x` - ;; walks along the line, and draw-int hands back the width it drew. - (let [x 10] - (rl/draw-text "Screen Size: [" x 40 10 rl/green) - (set x (+ x (rl/measure-text "Screen Size: [" 10))) - (set x (+ x (d/draw-int (rl/get-screen-width) x 40 10 rl/green))) - (rl/draw-text ", " x 40 10 rl/green) - (set x (+ x (rl/measure-text ", " 10))) - (set x (+ x (d/draw-int (rl/get-screen-height) x 40 10 rl/green))) - (rl/draw-text "]" x 40 10 rl/green)) - (rl/draw-text "Following flags can be set after window creation:" - 10 60 10 rl/gray) - (draw-flag "[F] FLAG_FULLSCREEN_MODE:" rl/flag-fullscreen-mode 80) - (draw-flag "[R] FLAG_WINDOW_RESIZABLE:" rl/flag-window-resizable 100) - (draw-flag "[D] FLAG_WINDOW_UNDECORATED:" rl/flag-window-undecorated 120) - (draw-flag "[H] FLAG_WINDOW_HIDDEN:" rl/flag-window-hidden 140) - (draw-flag "[N] FLAG_WINDOW_MINIMIZED:" rl/flag-window-minimized 160) - (draw-flag "[M] FLAG_WINDOW_MAXIMIZED:" rl/flag-window-maximized 180) - (draw-flag "[U] FLAG_WINDOW_UNFOCUSED:" rl/flag-window-unfocused 200) - (draw-flag "[T] FLAG_WINDOW_TOPMOST:" rl/flag-window-topmost 220) - (draw-flag "[A] FLAG_WINDOW_ALWAYS_RUN:" rl/flag-window-always-run 240) - (draw-flag "[V] FLAG_VSYNC_HINT:" rl/flag-vsync-hint 260) + ;; The C's TextFormat("Screen Size: [%i, %i]", ...), reassembled out of + ;; literals and examples/digits.flan the way the other ports do it: `x` + ;; walks along the line, and draw-int hands back the width it drew. + (let [x 10] + (rl/draw-text "Screen Size: [" x 40 10 rl/green) + (set x (+ x (rl/measure-text "Screen Size: [" 10))) + (set x (+ x (d/draw-int (rl/get-screen-width) x 40 10 rl/green))) + (rl/draw-text ", " x 40 10 rl/green) + (set x (+ x (rl/measure-text ", " 10))) + (set x (+ x (d/draw-int (rl/get-screen-height) x 40 10 rl/green))) + (rl/draw-text "]" x 40 10 rl/green)) + (rl/draw-text "Following flags can be set after window creation:" + 10 60 10 rl/gray) + (draw-flag "[F] FLAG_FULLSCREEN_MODE:" rl/flag-fullscreen-mode 80) + (draw-flag "[R] FLAG_WINDOW_RESIZABLE:" rl/flag-window-resizable 100) + (draw-flag "[D] FLAG_WINDOW_UNDECORATED:" rl/flag-window-undecorated 120) + (draw-flag "[H] FLAG_WINDOW_HIDDEN:" rl/flag-window-hidden 140) + (draw-flag "[N] FLAG_WINDOW_MINIMIZED:" rl/flag-window-minimized 160) + (draw-flag "[M] FLAG_WINDOW_MAXIMIZED:" rl/flag-window-maximized 180) + (draw-flag "[U] FLAG_WINDOW_UNFOCUSED:" rl/flag-window-unfocused 200) + (draw-flag "[T] FLAG_WINDOW_TOPMOST:" rl/flag-window-topmost 220) + (draw-flag "[A] FLAG_WINDOW_ALWAYS_RUN:" rl/flag-window-always-run 240) + (draw-flag "[V] FLAG_VSYNC_HINT:" rl/flag-vsync-hint 260) - (rl/draw-text "Following flags can only be set before window creation:" - 10 300 10 rl/gray) - (draw-flag "FLAG_WINDOW_HIGHDPI:" rl/flag-window-highdpi 320) - (draw-flag "FLAG_WINDOW_TRANSPARENT:" rl/flag-window-transparent 340) - (draw-flag "FLAG_MSAA_4X_HINT:" rl/flag-msaa-4x-hint 360) - - (rl/end-drawing))) + (rl/draw-text "Following flags can only be set before window creation:" + 10 300 10 rl/gray) + (draw-flag "FLAG_WINDOW_HIGHDPI:" rl/flag-window-highdpi 320) + (draw-flag "FLAG_WINDOW_TRANSPARENT:" rl/flag-window-transparent 340) + (draw-flag "FLAG_MSAA_4X_HINT:" rl/flag-msaa-4x-hint 360)))) diff --git a/examples/core-window-should-close.flan b/examples/core-window-should-close.flan index 946b427..1a636d4 100644 --- a/examples/core-window-should-close.flan +++ b/examples/core-window-should-close.flan @@ -54,15 +54,13 @@ (when (rl/key-pressed? :n) (set exit-requested false)))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (if exit-requested - (do - (rl/draw-rectangle 0 100 screen-width 200 rl/black) - (rl/draw-text "Are you sure you want to exit program? [Y/N]" 40 180 30 - rl/white)) - (rl/draw-text "Try to close the window to get confirmation message!" - 120 200 20 rl/lightgray)) - - (rl/end-drawing))) + (if exit-requested + (do + (rl/draw-rectangle 0 100 screen-width 200 rl/black) + (rl/draw-text "Are you sure you want to exit program? [Y/N]" 40 180 30 + rl/white)) + (rl/draw-text "Try to close the window to get confirmation message!" + 120 200 20 rl/lightgray))))) diff --git a/examples/core-world-screen.flan b/examples/core-world-screen.flan index f0aab54..66f143c 100644 --- a/examples/core-world-screen.flan +++ b/examples/core-world-screen.flan @@ -80,35 +80,32 @@ camera)] ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/begin-mode-3d camera) - (rl/draw-cube cube 2.0 2.0 2.0 rl/red) - (rl/draw-cube-wires cube 2.0 2.0 2.0 rl/maroon) - (rl/draw-grid 10 1.0) - (rl/end-mode-3d) + (rl/with-mode-3d camera + (rl/draw-cube cube 2.0 2.0 2.0 rl/red) + (rl/draw-cube-wires cube 2.0 2.0 2.0 rl/maroon) + (rl/draw-grid 10 1.0)) - ;; And the 2D half, drawn after end-mode-3d and therefore on top of the - ;; cube whatever the depth buffer says — which is the second half of - ;; what the example demonstrates. - (rl/draw-text "Enemy: 100 / 100" - (- (i32 (.x label-pos)) - (/ (rl/measure-text "Enemy: 100/100" 20) 2)) - (i32 (.y label-pos)) 20 rl/black) + ;; And the 2D half, drawn after end-mode-3d and therefore on top of the + ;; cube whatever the depth buffer says — which is the second half of + ;; what the example demonstrates. + (rl/draw-text "Enemy: 100 / 100" + (- (i32 (.x label-pos)) + (/ (rl/measure-text "Enemy: 100/100" 20) 2)) + (i32 (.y label-pos)) 20 rl/black) - (let [x 10] - (rl/draw-text "Cube position in screen space coordinates: [" x 10 20 - rl/lime) - (set x (+ x (rl/measure-text - "Cube position in screen space coordinates: [" 20))) - (set x (+ x (d/draw-int (i32 (.x label-pos)) x 10 20 rl/lime))) - (rl/draw-text ", " x 10 20 rl/lime) - (set x (+ x (rl/measure-text ", " 20))) - (set x (+ x (d/draw-int (i32 (.y label-pos)) x 10 20 rl/lime))) - (rl/draw-text "]" x 10 20 rl/lime)) + (let [x 10] + (rl/draw-text "Cube position in screen space coordinates: [" x 10 20 + rl/lime) + (set x (+ x (rl/measure-text + "Cube position in screen space coordinates: [" 20))) + (set x (+ x (d/draw-int (i32 (.x label-pos)) x 10 20 rl/lime))) + (rl/draw-text ", " x 10 20 rl/lime) + (set x (+ x (rl/measure-text ", " 20))) + (set x (+ x (d/draw-int (i32 (.y label-pos)) x 10 20 rl/lime))) + (rl/draw-text "]" x 10 20 rl/lime)) - (rl/draw-text "Text 2d should be always on top of the cube" 10 40 20 - rl/gray) - - (rl/end-drawing)))) + (rl/draw-text "Text 2d should be always on top of the cube" 10 40 20 + rl/gray))))) diff --git a/examples/models-box-collisions.flan b/examples/models-box-collisions.flan index 1f0e2f2..fbf024e 100644 --- a/examples/models-box-collisions.flan +++ b/examples/models-box-collisions.flan @@ -106,29 +106,24 @@ (set player-color (if hit rl/red rl/green))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/begin-mode-3d camera) + (rl/with-mode-3d camera + (rl/draw-cube enemy-box-pos (.x enemy-box-size) (.y enemy-box-size) + (.z enemy-box-size) rl/gray) + (rl/draw-cube-wires enemy-box-pos (.x enemy-box-size) (.y enemy-box-size) + (.z enemy-box-size) rl/darkgray) - (rl/draw-cube enemy-box-pos (.x enemy-box-size) (.y enemy-box-size) - (.z enemy-box-size) rl/gray) - (rl/draw-cube-wires enemy-box-pos (.x enemy-box-size) (.y enemy-box-size) - (.z enemy-box-size) rl/darkgray) + ;; draw-sphere is draw-sphere-ex at 16 rings and 16 slices; the wires form + ;; takes them because the tessellation is only visible in the wireframe. + (rl/draw-sphere enemy-sphere-pos enemy-sphere-size rl/gray) + (rl/draw-sphere-wires enemy-sphere-pos enemy-sphere-size 16 16 rl/darkgray) - ;; draw-sphere is draw-sphere-ex at 16 rings and 16 slices; the wires form - ;; takes them because the tessellation is only visible in the wireframe. - (rl/draw-sphere enemy-sphere-pos enemy-sphere-size rl/gray) - (rl/draw-sphere-wires enemy-sphere-pos enemy-sphere-size 16 16 rl/darkgray) + (rl/draw-cube-v player-position player-size player-color) - (rl/draw-cube-v player-position player-size player-color) + (rl/draw-grid 10 1.0)) - (rl/draw-grid 10 1.0) + (rl/draw-text "Move player with arrow keys to collide" 220 40 20 rl/gray) - (rl/end-mode-3d) - - (rl/draw-text "Move player with arrow keys to collide" 220 40 20 rl/gray) - - (rl/draw-fps 10 10) - - (rl/end-drawing))) + (rl/draw-fps 10 10)))) diff --git a/examples/shapes-basic-shapes.flan b/examples/shapes-basic-shapes.flan index 1f6c667..4ddd6c9 100644 --- a/examples/shapes-basic-shapes.flan +++ b/examples/shapes-basic-shapes.flan @@ -54,49 +54,47 @@ (set rotation (+ rotation 0.2)) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/draw-text "some basic shapes available on raylib" 20 20 20 rl/darkgray) + (rl/draw-text "some basic shapes available on raylib" 20 20 20 rl/darkgray) - ;; Circles. The gradient one takes an inner and an outer colour and is the - ;; first call in the corpus to pass two Colors at once. - (rl/draw-circle col-left 120 35.0 rl/darkblue) - (rl/draw-circle-gradient col-left 220 60.0 rl/green rl/skyblue) - (rl/draw-circle-lines col-left 340 80.0 rl/darkblue) + ;; Circles. The gradient one takes an inner and an outer colour and is the + ;; first call in the corpus to pass two Colors at once. + (rl/draw-circle col-left 120 35.0 rl/darkblue) + (rl/draw-circle-gradient col-left 220 60.0 rl/green rl/skyblue) + (rl/draw-circle-lines col-left 340 80.0 rl/darkblue) - ;; Rectangles. draw-rectangle-gradient-h runs the colour left to right; - ;; the -v form runs it top to bottom and the -ex form takes all four - ;; corners. The C uses the horizontal one here. - (rl/draw-rectangle (- col-mid 60) 100 120 60 rl/red) - (rl/draw-rectangle-gradient-h (- col-mid 90) 170 180 130 rl/maroon rl/gold) - ;; raylib draws this with quads internally rather than with lines, which - ;; is why its thickness does not follow the line width anywhere. - (rl/draw-rectangle-lines (- col-mid 40) 320 80 60 rl/orange) + ;; Rectangles. draw-rectangle-gradient-h runs the colour left to right; + ;; the -v form runs it top to bottom and the -ex form takes all four + ;; corners. The C uses the horizontal one here. + (rl/draw-rectangle (- col-mid 60) 100 120 60 rl/red) + (rl/draw-rectangle-gradient-h (- col-mid 90) 170 180 130 rl/maroon rl/gold) + ;; raylib draws this with quads internally rather than with lines, which + ;; is why its thickness does not follow the line width anywhere. + (rl/draw-rectangle-lines (- col-mid 40) 320 80 60 rl/orange) - ;; Triangles. raylib wants the three vertices in counter-clockwise order - ;; and draws nothing at all for a clockwise one, so the order here is not - ;; cosmetic. - (rl/draw-triangle (rl/Vector2 {.x 600.0 .y 80.0}) - (rl/Vector2 {.x 540.0 .y 150.0}) - (rl/Vector2 {.x 660.0 .y 150.0}) - rl/violet) + ;; Triangles. raylib wants the three vertices in counter-clockwise order + ;; and draws nothing at all for a clockwise one, so the order here is not + ;; cosmetic. + (rl/draw-triangle (rl/Vector2 {.x 600.0 .y 80.0}) + (rl/Vector2 {.x 540.0 .y 150.0}) + (rl/Vector2 {.x 660.0 .y 150.0}) + rl/violet) - (rl/draw-triangle-lines (rl/Vector2 {.x 600.0 .y 160.0}) - (rl/Vector2 {.x 580.0 .y 230.0}) - (rl/Vector2 {.x 620.0 .y 230.0}) - rl/darkblue) + (rl/draw-triangle-lines (rl/Vector2 {.x 600.0 .y 160.0}) + (rl/Vector2 {.x 580.0 .y 230.0}) + (rl/Vector2 {.x 620.0 .y 230.0}) + rl/darkblue) - ;; Polygons, all three at the same centre and the same rotation, at three - ;; radii, so the filled hexagon sits inside the two outlines. - (let [centre (rl/Vector2 {.x (f32 col-right) .y 330.0})] - (rl/draw-poly centre 6 80.0 rotation rl/brown) - (rl/draw-poly-lines centre 6 90.0 rotation rl/brown) - (rl/draw-poly-lines-ex centre 6 85.0 rotation 6.0 rl/beige)) + ;; Polygons, all three at the same centre and the same rotation, at three + ;; radii, so the filled hexagon sits inside the two outlines. + (let [centre (rl/Vector2 {.x (f32 col-right) .y 330.0})] + (rl/draw-poly centre 6 80.0 rotation rl/brown) + (rl/draw-poly-lines centre 6 90.0 rotation rl/brown) + (rl/draw-poly-lines-ex centre 6 85.0 rotation 6.0 rl/beige)) - ;; The C draws every LINES-based shape together so raylib can batch them - ;; into one pass. This last line is part of that comment and not an - ;; afterthought, so it stays where the C has it. - (rl/draw-line 18 42 (- screen-width 18) 42 rl/black) - - (rl/end-drawing))) + ;; The C draws every LINES-based shape together so raylib can batch them + ;; into one pass. This last line is part of that comment and not an + ;; afterthought, so it stays where the C has it. + (rl/draw-line 18 42 (- screen-width 18) 42 rl/black)))) diff --git a/examples/shapes-collision-area.flan b/examples/shapes-collision-area.flan index 4fdf73d..48f14da 100644 --- a/examples/shapes-collision-area.flan +++ b/examples/shapes-collision-area.flan @@ -93,37 +93,35 @@ (when (rl/key-pressed? :space) (set paused (not paused))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/draw-rectangle 0 0 screen-width screen-upper-limit - (if collision rl/red rl/black)) + (rl/draw-rectangle 0 0 screen-width screen-upper-limit + (if collision rl/red rl/black)) - (rl/draw-rectangle-rec box-a rl/gold) - (rl/draw-rectangle-rec box-b rl/blue) + (rl/draw-rectangle-rec box-a rl/gold) + (rl/draw-rectangle-rec box-b rl/blue) - (when collision - ;; The overlap itself, drawn over both boxes. This patch landing - ;; anywhere other than where the gold and the blue cross is what a - ;; permuted Rectangle would look like. - (rl/draw-rectangle-rec box-collision rl/lime) + (when collision + ;; The overlap itself, drawn over both boxes. This patch landing + ;; anywhere other than where the gold and the blue cross is what a + ;; permuted Rectangle would look like. + (rl/draw-rectangle-rec box-collision rl/lime) - (rl/draw-text "COLLISION!" - (- (/ (rl/get-screen-width) 2) - (/ (rl/measure-text "COLLISION!" 20) 2)) - (- (/ screen-upper-limit 2) 10) 20 rl/black) + (rl/draw-text "COLLISION!" + (- (/ (rl/get-screen-width) 2) + (/ (rl/measure-text "COLLISION!" 20) 2)) + (- (/ screen-upper-limit 2) 10) 20 rl/black) - ;; The C's TextFormat("Collision Area: %i", ...). Both dimensions are - ;; truncated to int before multiplying, as the C's two casts do. - (let [x (- (/ (rl/get-screen-width) 2) 100) - y (+ screen-upper-limit 10)] - (set x (+ x (d/draw-piece "Collision Area: " x y 20 rl/black))) - (d/draw-int (* (i32 (.width box-collision)) (i32 (.height box-collision))) - x y 20 rl/black))) + ;; The C's TextFormat("Collision Area: %i", ...). Both dimensions are + ;; truncated to int before multiplying, as the C's two casts do. + (let [x (- (/ (rl/get-screen-width) 2) 100) + y (+ screen-upper-limit 10)] + (set x (+ x (d/draw-piece "Collision Area: " x y 20 rl/black))) + (d/draw-int (* (i32 (.width box-collision)) (i32 (.height box-collision))) + x y 20 rl/black))) - (rl/draw-text "Press SPACE to PAUSE/RESUME" 20 (- screen-height 35) 20 - rl/lightgray) + (rl/draw-text "Press SPACE to PAUSE/RESUME" 20 (- screen-height 35) 20 + rl/lightgray) - (rl/draw-fps 10 10) - - (rl/end-drawing))) + (rl/draw-fps 10 10)))) diff --git a/examples/shapes-following-eyes.flan b/examples/shapes-following-eyes.flan index 71da0d8..8da5506 100644 --- a/examples/shapes-following-eyes.flan +++ b/examples/shapes-following-eyes.flan @@ -95,18 +95,16 @@ (set iris-right (track mouse sclera-right))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - ;; White of the eye, iris, pupil — in that order, each over the last. - (rl/draw-circle-v sclera-left sclera-radius rl/lightgray) - (rl/draw-circle-v iris-left iris-radius rl/brown) - (rl/draw-circle-v iris-left 10.0 rl/black) + ;; White of the eye, iris, pupil — in that order, each over the last. + (rl/draw-circle-v sclera-left sclera-radius rl/lightgray) + (rl/draw-circle-v iris-left iris-radius rl/brown) + (rl/draw-circle-v iris-left 10.0 rl/black) - (rl/draw-circle-v sclera-right sclera-radius rl/lightgray) - (rl/draw-circle-v iris-right iris-radius rl/darkgreen) - (rl/draw-circle-v iris-right 10.0 rl/black) + (rl/draw-circle-v sclera-right sclera-radius rl/lightgray) + (rl/draw-circle-v iris-right iris-radius rl/darkgreen) + (rl/draw-circle-v iris-right 10.0 rl/black) - (rl/draw-fps 10 10) - - (rl/end-drawing))) + (rl/draw-fps 10 10)))) diff --git a/examples/text-input-box.flan b/examples/text-input-box.flan index 35f58c3..c5c34df 100644 --- a/examples/text-input-box.flan +++ b/examples/text-input-box.flan @@ -112,40 +112,38 @@ (set frames-counter 0)) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/draw-text "PLACE MOUSE OVER INPUT BOX!" 240 140 20 rl/gray) + (rl/draw-text "PLACE MOUSE OVER INPUT BOX!" 240 140 20 rl/gray) - (rl/draw-rectangle-rec text-box rl/lightgray) - (rl/draw-rectangle-lines (i32 (.x text-box)) (i32 (.y text-box)) - (i32 (.width text-box)) (i32 (.height text-box)) - (if mouse-on-text rl/red rl/darkgray)) + (rl/draw-rectangle-rec text-box rl/lightgray) + (rl/draw-rectangle-lines (i32 (.x text-box)) (i32 (.y text-box)) + (i32 (.width text-box)) (i32 (.height text-box)) + (if mouse-on-text rl/red rl/darkgray)) - (let [typed (string (slice name 0 letter-count))] - (rl/draw-text typed (+ (i32 (.x text-box)) 5) (+ (i32 (.y text-box)) 8) - 40 rl/maroon) + (let [typed (string (slice name 0 letter-count))] + (rl/draw-text typed (+ (i32 (.x text-box)) 5) (+ (i32 (.y text-box)) 8) + 40 rl/maroon) - ;; The C's TextFormat("INPUT CHARS: %i/%i", ...), reassembled out of - ;; examples/digits.flan the way the other ports do it. `typed` is a view - ;; of `name` and not of the runtime's shared format buffer, so it - ;; survives the two numbers being formatted — which a value from - ;; i64->bytes would not. - (let [x 315] - (set x (+ x (d/draw-piece "INPUT CHARS: " x 250 20 rl/darkgray))) - (set x (+ x (d/draw-int letter-count x 250 20 rl/darkgray))) - (set x (+ x (d/draw-piece "/" x 250 20 rl/darkgray))) - (d/draw-int max-input-chars x 250 20 rl/darkgray)) + ;; The C's TextFormat("INPUT CHARS: %i/%i", ...), reassembled out of + ;; examples/digits.flan the way the other ports do it. `typed` is a view + ;; of `name` and not of the runtime's shared format buffer, so it + ;; survives the two numbers being formatted — which a value from + ;; i64->bytes would not. + (let [x 315] + (set x (+ x (d/draw-piece "INPUT CHARS: " x 250 20 rl/darkgray))) + (set x (+ x (d/draw-int letter-count x 250 20 rl/darkgray))) + (set x (+ x (d/draw-piece "/" x 250 20 rl/darkgray))) + (d/draw-int max-input-chars x 250 20 rl/darkgray)) - (when mouse-on-text - (if (< letter-count max-input-chars) - ;; Twenty frames on, twenty frames off, at the pen position after - ;; whatever has been typed. - (when (= (% (/ frames-counter 20) 2) 0) - (rl/draw-text "_" - (+ (i32 (.x text-box)) 8 (rl/measure-text typed 40)) - (+ (i32 (.y text-box)) 12) 40 rl/maroon)) - (rl/draw-text "Press BACKSPACE to delete chars..." 230 300 20 - rl/gray)))) - - (rl/end-drawing))) + (when mouse-on-text + (if (< letter-count max-input-chars) + ;; Twenty frames on, twenty frames off, at the pen position after + ;; whatever has been typed. + (when (= (% (/ frames-counter 20) 2) 0) + (rl/draw-text "_" + (+ (i32 (.x text-box)) 8 (rl/measure-text typed 40)) + (+ (i32 (.y text-box)) 12) 40 rl/maroon)) + (rl/draw-text "Press BACKSPACE to delete chars..." 230 300 20 + rl/gray))))))) diff --git a/examples/text-writing-anim.flan b/examples/text-writing-anim.flan index 3d67c54..0aeab0a 100644 --- a/examples/text-writing-anim.flan +++ b/examples/text-writing-anim.flan @@ -61,16 +61,14 @@ (when (rl/key-pressed? :enter) (set frames-counter 0)) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - ;; One character every ten frames. The clamp is the whole difference from - ;; the C — see the header comment. - (let [b (bytes message) - n (min (i32 (len b)) (/ frames-counter 10))] - (rl/draw-text (string (slice b 0 n)) 210 160 20 rl/maroon)) + ;; One character every ten frames. The clamp is the whole difference from + ;; the C — see the header comment. + (let [b (bytes message) + n (min (i32 (len b)) (/ frames-counter 10))] + (rl/draw-text (string (slice b 0 n)) 210 160 20 rl/maroon)) - (rl/draw-text "PRESS [ENTER] to RESTART!" 240 260 20 rl/lightgray) - (rl/draw-text "HOLD [SPACE] to SPEED UP!" 239 300 20 rl/lightgray) - - (rl/end-drawing))) + (rl/draw-text "PRESS [ENTER] to RESTART!" 240 260 20 rl/lightgray) + (rl/draw-text "HOLD [SPACE] to SPEED UP!" 239 300 20 rl/lightgray)))) diff --git a/examples/textures-fog-of-war.flan b/examples/textures-fog-of-war.flan index d17879f..50e9897 100644 --- a/examples/textures-fog-of-war.flan +++ b/examples/textures-fog-of-war.flan @@ -138,60 +138,57 @@ ;; Draw the fog into its own little target first, at one pixel per tile. ;; blank is alpha 0, so a tile that is neither unseen nor remembered ;; leaves nothing behind and the map below shows through unmodified. - (rl/begin-texture-mode fog-of-war) - (rl/clear-background rl/blank) - (dotimes [y tiles-y] - (dotimes [x tiles-x] - ;; A tile in view (1) draws nothing at all and stays transparent. - (let [f (at tile-fog (+ (* y tiles-x) x))] - (when (!= f 1) - (rl/draw-rectangle x y 1 1 - (if (= f 0) rl/black (rl/fade rl/black 0.8))))))) - (rl/end-texture-mode) + (rl/with-texture-mode fog-of-war + (rl/clear-background rl/blank) + (dotimes [y tiles-y] + (dotimes [x tiles-x] + ;; A tile in view (1) draws nothing at all and stays transparent. + (let [f (at tile-fog (+ (* y tiles-x) x))] + (when (!= f 1) + (rl/draw-rectangle x y 1 1 + (if (= f 0) rl/black (rl/fade rl/black 0.8)))))))) - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - ;; The map itself, in full size. - (dotimes [y tiles-y] - (dotimes [x tiles-x] - (rl/draw-rectangle (* x map-tile-size) (* y map-tile-size) - map-tile-size map-tile-size - (if (= (at tile-ids (+ (* y tiles-x) x)) 0) - rl/blue - (rl/fade rl/blue 0.9))) - (rl/draw-rectangle-lines (* x map-tile-size) (* y map-tile-size) - map-tile-size map-tile-size - (rl/fade rl/darkblue 0.5)))) + ;; The map itself, in full size. + (dotimes [y tiles-y] + (dotimes [x tiles-x] + (rl/draw-rectangle (* x map-tile-size) (* y map-tile-size) + map-tile-size map-tile-size + (if (= (at tile-ids (+ (* y tiles-x) x)) 0) + rl/blue + (rl/fade rl/blue 0.9))) + (rl/draw-rectangle-lines (* x map-tile-size) (* y map-tile-size) + map-tile-size map-tile-size + (rl/fade rl/darkblue 0.5)))) - (rl/draw-rectangle-v player-position - (rl/Vector2 {.x (f32 player-size) .y (f32 player-size)}) - rl/red) + (rl/draw-rectangle-v player-position + (rl/Vector2 {.x (f32 player-size) .y (f32 player-size)}) + rl/red) - ;; The fog, stretched over the whole map. The negative source height is - ;; the flip — see the header comment. - (rl/draw-texture-pro - (.texture fog-of-war) - (rl/Rectangle {.x 0.0 .y 0.0 - .width (f32 (.width (.texture fog-of-war))) - .height (- 0.0 (f32 (.height (.texture fog-of-war))))}) - (rl/Rectangle {.x 0.0 .y 0.0 - .width (f32 (* tiles-x map-tile-size)) - .height (f32 (* tiles-y map-tile-size))}) - (rl/Vector2 {.x 0.0 .y 0.0}) - 0.0 - rl/white) + ;; The fog, stretched over the whole map. The negative source height is + ;; the flip — see the header comment. + (rl/draw-texture-pro + (.texture fog-of-war) + (rl/Rectangle {.x 0.0 .y 0.0 + .width (f32 (.width (.texture fog-of-war))) + .height (- 0.0 (f32 (.height (.texture fog-of-war))))}) + (rl/Rectangle {.x 0.0 .y 0.0 + .width (f32 (* tiles-x map-tile-size)) + .height (f32 (* tiles-y map-tile-size))}) + (rl/Vector2 {.x 0.0 .y 0.0}) + 0.0 + rl/white) - ;; The C's TextFormat("Current tile: [%i,%i]", ...). Each number is drawn - ;; before the next is formatted, which examples/digits.flan requires: - ;; both share one static buffer in the runtime. - (let [x 10] - (set x (+ x (d/draw-piece "Current tile: [" x 10 20 rl/raywhite))) - (set x (+ x (d/draw-int player-tile-x x 10 20 rl/raywhite))) - (set x (+ x (d/draw-piece "," x 10 20 rl/raywhite))) - (set x (+ x (d/draw-int player-tile-y x 10 20 rl/raywhite))) - (d/draw-piece "]" x 10 20 rl/raywhite)) + ;; The C's TextFormat("Current tile: [%i,%i]", ...). Each number is drawn + ;; before the next is formatted, which examples/digits.flan requires: + ;; both share one static buffer in the runtime. + (let [x 10] + (set x (+ x (d/draw-piece "Current tile: [" x 10 20 rl/raywhite))) + (set x (+ x (d/draw-int player-tile-x x 10 20 rl/raywhite))) + (set x (+ x (d/draw-piece "," x 10 20 rl/raywhite))) + (set x (+ x (d/draw-int player-tile-y x 10 20 rl/raywhite))) + (d/draw-piece "]" x 10 20 rl/raywhite)) - (rl/draw-text "ARROW KEYS to move" 10 (- screen-height 25) 20 rl/raywhite) - - (rl/end-drawing))) + (rl/draw-text "ARROW KEYS to move" 10 (- screen-height 25) 20 rl/raywhite)))) diff --git a/examples/textures-image-generation.flan b/examples/textures-image-generation.flan index af9616e..516d370 100644 --- a/examples/textures-image-generation.flan +++ b/examples/textures-image-generation.flan @@ -104,27 +104,25 @@ (set current-texture (% (+ current-texture 1) num-textures))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-drawing + (rl/clear-background rl/raywhite) - (rl/draw-texture (at textures current-texture) 0 0 rl/white) + (rl/draw-texture (at textures current-texture) 0 0 rl/white) - (rl/draw-rectangle 30 400 325 30 (rl/fade rl/skyblue 0.5)) - (rl/draw-rectangle-lines 30 400 325 30 (rl/fade rl/white 0.5)) - (rl/draw-text "MOUSE LEFT BUTTON to CYCLE PROCEDURAL TEXTURES" - 40 410 10 rl/white) + (rl/draw-rectangle 30 400 325 30 (rl/fade rl/skyblue 0.5)) + (rl/draw-rectangle-lines 30 400 325 30 (rl/fade rl/white 0.5)) + (rl/draw-text "MOUSE LEFT BUTTON to CYCLE PROCEDURAL TEXTURES" + 40 410 10 rl/white) - ;; The C's switch. Each label is positioned so its right edge lands in the - ;; same place, which is why the x differs per line. - (cond - (= current-texture 0) (rl/draw-text "VERTICAL GRADIENT" 560 10 20 rl/raywhite) - (= current-texture 1) (rl/draw-text "HORIZONTAL GRADIENT" 540 10 20 rl/raywhite) - (= current-texture 2) (rl/draw-text "DIAGONAL GRADIENT" 540 10 20 rl/raywhite) - (= current-texture 3) (rl/draw-text "RADIAL GRADIENT" 580 10 20 rl/lightgray) - (= current-texture 4) (rl/draw-text "SQUARE GRADIENT" 580 10 20 rl/lightgray) - (= current-texture 5) (rl/draw-text "CHECKED" 680 10 20 rl/raywhite) - (= current-texture 6) (rl/draw-text "WHITE NOISE" 640 10 20 rl/red) - (= current-texture 7) (rl/draw-text "PERLIN NOISE" 640 10 20 rl/red) - :else (rl/draw-text "CELLULAR" 670 10 20 rl/raywhite)) - - (rl/end-drawing))) + ;; The C's switch. Each label is positioned so its right edge lands in the + ;; same place, which is why the x differs per line. + (cond + (= current-texture 0) (rl/draw-text "VERTICAL GRADIENT" 560 10 20 rl/raywhite) + (= current-texture 1) (rl/draw-text "HORIZONTAL GRADIENT" 540 10 20 rl/raywhite) + (= current-texture 2) (rl/draw-text "DIAGONAL GRADIENT" 540 10 20 rl/raywhite) + (= current-texture 3) (rl/draw-text "RADIAL GRADIENT" 580 10 20 rl/lightgray) + (= current-texture 4) (rl/draw-text "SQUARE GRADIENT" 580 10 20 rl/lightgray) + (= current-texture 5) (rl/draw-text "CHECKED" 680 10 20 rl/raywhite) + (= current-texture 6) (rl/draw-text "WHITE NOISE" 640 10 20 rl/red) + (= current-texture 7) (rl/draw-text "PERLIN NOISE" 640 10 20 rl/red) + :else (rl/draw-text "CELLULAR" 670 10 20 rl/raywhite))))) diff --git a/examples/textures-mouse-painting.flan b/examples/textures-mouse-painting.flan index 03cb3b2..dad18e6 100644 --- a/examples/textures-mouse-painting.flan +++ b/examples/textures-mouse-painting.flan @@ -114,9 +114,8 @@ (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) + (rl/with-texture-mode target + (rl/clear-background (at colors 0))) ;; 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 @@ -148,21 +147,19 @@ 2.0 50.0)) (when (rl/key-pressed? :c) - (rl/begin-texture-mode target) - (rl/clear-background (at colors 0)) - (rl/end-texture-mode)) + (rl/with-texture-mode target + (rl/clear-background (at colors 0)))) ;; 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)) + (rl/with-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))))) ;; Right button erases, which is painting in the clear colour. The ;; selected swatch is parked while the button is held so the toolbar @@ -173,11 +170,10 @@ (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)) + (rl/with-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))))) (when (and (rl/mouse-button-released? :right) mouse-was-pressed) (set color-selected color-selected-prev) (set mouse-was-pressed false))) @@ -201,55 +197,53 @@ (set save-message-counter 0))) ;; Draw - (rl/begin-drawing) - (rl/clear-background rl/raywhite) + (rl/with-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 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 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) + ;; 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) + (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))) + (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)) + (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)) + (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)))) + (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)))))) diff --git a/sand.flan b/sand.flan index deea0c2..f7363ce 100644 --- a/sand.flan +++ b/sand.flan @@ -15,7 +15,10 @@ ;;;; offers. ;;;; ;;;; Note what it deliberately does not use: no Vec, no Map, no generics, no -;;;; user-written macros, no allocator other than the stack and static storage. +;;;; macros of its own, no allocator other than the stack and static storage. +;;;; It does call rl/with-drawing, which is the raylib package's macro over +;;;; the BeginDrawing/EndDrawing pair — the parity rule is what the reference +;;;; versions do, and lisp/sand.lisp writes rl:with-drawing there. ;;;; ;;;; Notation reminders (see plan.org and spec-memory.md): ;;;; [n T] fixed array, length n, element T — a VALUE, copies @@ -191,11 +194,13 @@ ;; leaves one CL-style escape hatch: choose `continue' in the editor to ;; abandon this frame and return to the next one with the game still live. ;; Keep drawing outside it. Skipping between BeginDrawing and EndDrawing - ;; would leave raylib's frame unbalanced. + ;; would leave raylib's frame unbalanced, and rl/with-drawing does not + ;; change that — it guarantees the two calls stay together and stay + ;; matched, not that a transfer out of the body reaches the second one. + ;; The restart being out here is still what makes `continue' safe. (restart-case (do (agent/poll) (game-update)) (continue [] (do))) - (rl/begin-drawing) - (game-draw) - (rl/end-drawing))) + (rl/with-drawing + (game-draw)))) diff --git a/test/programs/rl-with-reject.flan b/test/programs/rl-with-reject.flan new file mode 100644 index 0000000..e6e2715 --- /dev/null +++ b/test/programs/rl-with-reject.flan @@ -0,0 +1,16 @@ +;;;; with-mode-2d without a camera. +;;;; +;;;; The prelude's idiom for a macro's own arity check, which clamp and unless +;;;; and into all use: answer a symbol that is not a name, spelled as the +;;;; sentence the author needed to read. There is no way for a macro to signal +;;;; at expansion time, so the message travels as the unknown function — and +;;;; what makes it land usefully is the note the expander adds beneath it, +;;;; naming the macro and the call site. +;;;; +;;;; Being refused is the whole test; this is never built. + +(import rl "vendor:raylib") + +(defn main [] i32 + (rl/with-mode-2d) + 0) diff --git a/test/programs/rl-with.flan b/test/programs/rl-with.flan new file mode 100644 index 0000000..fb2c42e --- /dev/null +++ b/test/programs/rl-with.flan @@ -0,0 +1,43 @@ +;;;; The raylib package's five begin/end macros, expanded. +;;;; +;;;; They cannot be *run* here. Every one of them brackets calls that need a +;;;; window and a GL context, so a headless case can only assert what the +;;;; expansion is, not what it draws — which is the whole of what a macro +;;;; over a pair can get wrong. The four examples/ uses of with-drawing and +;;;; friends already prove they compile; what this adds is with-scissor-mode, +;;;; which no example can use (examples/core-scissor-test.flan turns its +;;;; scissor on and off from a flag, so its Begin and its End sit inside two +;;;; separate `when`s and there is no body to wrap). The arity refusals are +;;;; rl-with-reject.flan, beside this. +;;;; +;;;; [frame] is deliberately not reached from [main]. lib/reach.ml answers the +;;;; link from the checked program, so an unreachable frame means this case +;;;; needs no libraylib to run and goes in the suite unconditionally — while +;;;; [check] still walks every line of it, which is where a wrong expansion +;;;; would show. + +(import rl "vendor:raylib") + +(defn frame [cam rl/Camera2D cam3 rl/Camera3D target rl/RenderTexture2D] () + (rl/with-drawing + (rl/clear-background rl/raywhite) + + (rl/with-mode-2d cam + (rl/draw-rectangle 0 0 10 10 rl/red)) + + (rl/with-mode-3d cam3 + (rl/draw-fps 0 0)) + + (rl/with-scissor-mode 10 10 100 100 + (rl/draw-rectangle 0 0 320 240 rl/blue) + (rl/draw-text "clipped" 20 20 10 rl/black)) + + (rl/draw-fps 10 10)) + + ;; Off-screen, outside the frame, which is where a render texture belongs. + (rl/with-texture-mode target + (rl/clear-background rl/blank))) + +(defn main [] i32 + (println "expanded") + 0) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index 8395b0b..b8c2c95 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -884,6 +884,16 @@ let () = outputs ~opt:"-O0" "raylib, bindings generated from the header, -O0" "programs/raylib-imported.flan" out; + (* The raylib package's begin/end macros — vendor/raylib/modes.flan. + Nothing here draws: every one of the five brackets calls that want a + window and a GL context, so what can be asserted headless is that the + expansion compiles, which is the whole of what a macro over a pair can + get wrong. The program keeps the frame function unreachable from main + on purpose, so reach.ml links no libraylib and this runs unconditionally + alongside the cases that need one. See the file's header. *) + outputs "raylib begin/end macros expand" "programs/rl-with.flan" + "expanded\n"; + (* raylib's Image family, headless, and the strongest FFI case here: an Image is pixels in RAM, so raylib *computes* with it rather than storing and returning it. @@ -1376,6 +1386,17 @@ let () = name m needle end in + (* A package macro's arity check. A macro cannot signal while it expands, + so the prelude's idiom — clamp's, unless's, into's — is to answer a + symbol that is not a name and reads as the sentence the caller needs. + What makes that usable rather than baffling is the expander's note + beneath it, naming rl/with-mode-2d at the call site rather than + pointing into the package — that half is rendering and is not asserted + here. *) + refuses "with-mode-2d written without a camera" + "programs/rl-with-reject.flan" + "with-mode-2d-takes-a-camera-and-a-body"; + (* Visibility: main is not a name a package offers, and saying so is the point — "unknown name sand/main" would be true and useless. *) (* Generics, at the definition rather than at a call site. Both of these diff --git a/vendor/raylib/modes.flan b/vendor/raylib/modes.flan new file mode 100644 index 0000000..4459f71 --- /dev/null +++ b/vendor/raylib/modes.flan @@ -0,0 +1,125 @@ +;;;; The begin/end pairs, as macros that cannot come apart. +;;;; +;;;; raylib has five pairs this package binds — drawing, 2D camera, 3D camera, +;;;; render texture, scissor — and every one of them is the same hazard: two +;;;; calls that have to bracket a body, written by hand, with nothing checking +;;;; that the second one is there or that it is the matching one. An +;;;; EndDrawing that is missing hangs the frame; an EndMode2D that should have +;;;; been EndMode3D corrupts the matrix stack; a pair that drifted apart during +;;;; an edit does either. NEXT.md put `unwind-protect` for this and rejected +;;;; it, with the note that these are "three-line macros once the expander +;;;; lands". They are, and this is them. +;;;; +;;;; (rl/with-drawing +;;;; (rl/clear-background rl/raywhite) +;;;; (rl/draw-fps 10 10)) +;;;; +;;;; expands to exactly what the caller used to type, in the order they used +;;;; to type it: +;;;; +;;;; (do (rl/begin-drawing) +;;;; (rl/clear-background rl/raywhite) +;;;; (rl/draw-fps 10 10) +;;;; (rl/end-drawing)) +;;;; +;;;; The argument-taking ones put the argument where it always went, evaluated +;;;; once, in place. There is no `let` and no `gensym` anywhere below: nothing +;;;; here binds a name, so there is no name for a caller's to collide with, +;;;; and introducing one in a deliberately non-hygienic macro that does not +;;;; need it would be a step backwards. +;;;; +;;;; ── What this does not fix, and why it cannot ─────────────────────── +;;;; +;;;; The bug class removed is "the End* is missing, wrong, or no longer +;;;; beside its Begin*". The bug class NOT removed is a body that leaves +;;;; through the unwind path instead of the bottom: a `return`, or an +;;;; `invoke-restart` reaching an enclosing `restart-case`. Both of those skip +;;;; the rest of the `do` and the `End*` with it, exactly as they would skip a +;;;; hand-written one. +;;;; +;;;; The obvious fix is `defer`, and it is not available here. `defer` is a +;;;; compile-time construct — the forms are copied into the function's exit +;;;; paths — so BUILT.md refuses it inside a loop or a branch, and a +;;;; begin/end pair lives inside the game loop essentially always. Were it +;;;; permitted there it would be wrong in the worse direction: one EndDrawing +;;;; at function exit for N BeginDrawings. A macro expanding to a defer would +;;;; therefore compile at a function body's top level and be refused in the +;;;; one place anybody writes it, which is a worse thing to hand somebody than +;;;; a macro that is honest about its extent. +;;;; +;;;; So the discipline sand.flan already writes down stays the discipline: +;;;; keep the restart boundary OUTSIDE the pair, so choosing `continue` for a +;;;; frame abandons the update and still reaches the drawing. spec-conditions +;;;; §5 is the rule behind that — a transfer runs the intervening frames' +;;;; defers and moves control, and there are no defers here to run. +;;;; +;;;; ── Why this is a file of its own ─────────────────────────────────── +;;;; +;;;; vector.flan's reasoning, unchanged: the split is on `declare-c`. +;;;; raylib.flan is the package's statement about C, it is the file the header +;;;; check reads hand-written signatures out of, and a wrong line in it stops +;;;; the build. There is not one `declare-c` below — every macro here expands +;;;; into names raylib.flan already declares — so there is nothing for the +;;;; header check to read and nothing raylib can make wrong. A package is a +;;;; directory, so this is another .flan beside the others and is qualified +;;;; `rl/` like the rest of it. +;;;; +;;;; The names come out qualified: the importer writes `(rl/with-drawing …)` +;;;; and a bare `(with-drawing …)` is an unknown name there, the rule every +;;;; declaration in this package follows. The expansions name this package's +;;;; own functions unqualified and the expander qualifies them on the way out +;;;; — see test/programs/pkgs/mac/mac.flan, which is that rule's worked +;;;; example. +;;;; +;;;; Each macro answers the value of its `End*` call, which is (). A pair was +;;;; never an expression worth reading anyway. + +;; The frame. Everything drawn lands on the back buffer; end-drawing swaps it +;; and waits out the frame time set by set-target-fps. +(defmacro with-drawing [args] + (if (< (len args) 1) + `(with-drawing-takes-a-body) + `(do (begin-drawing) + ~@args + (end-drawing)))) + +;; The 2D camera. The argument is a Camera2D value, evaluated once where it +;; always was. Remember that a fresh (Camera2D {}) has zoom 0.0 and is not +;; usable as an identity — raylib.flan says so beside the struct. +(defmacro with-mode-2d [args] + (if (< (len args) 2) + `(with-mode-2d-takes-a-camera-and-a-body) + `(do (begin-mode-2d ~(at args 0)) + ~@(form-rest args 1) + (end-mode-2d)))) + +;; The 3D camera. Same shape, same argument-once rule, and the pair matters +;; more here than anywhere: ending a 3D mode with end-mode-2d type-checks +;; fine and leaves the projection matrix wrong for everything after it. +(defmacro with-mode-3d [args] + (if (< (len args) 2) + `(with-mode-3d-takes-a-camera-and-a-body) + `(do (begin-mode-3d ~(at args 0)) + ~@(form-rest args 1) + (end-mode-3d)))) + +;; Render to a texture instead of the screen. The target's texture comes out +;; of the GPU upside down, so drawing it back wants a negative source height — +;; that correction is the caller's and is deliberately not hidden here, since +;; it belongs with the draw and not with the mode. +(defmacro with-texture-mode [args] + (if (< (len args) 2) + `(with-texture-mode-takes-a-target-and-a-body) + `(do (begin-texture-mode ~(at args 0)) + ~@(form-rest args 1) + (end-texture-mode)))) + +;; Clip to a rectangle, in screen pixels with y down from the top. Four +;; scalars rather than a Rectangle, because that is what BeginScissorMode +;; takes and this file is not the place to invent a second spelling. +(defmacro with-scissor-mode [args] + (if (< (len args) 5) + `(with-scissor-mode-takes-x-y-width-height-and-a-body) + `(do (begin-scissor-mode ~(at args 0) ~(at args 1) ~(at args 2) ~(at args 3)) + ~@(form-rest args 4) + (end-scissor-mode))))