flan/examples/core-input-gestures.flan
Joseph Ferano 25f56c24a8 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.
2026-09-13 18:00:35 +07:00

109 lines
4.7 KiB
Plaintext

;;;; raylib [core] example - input gestures
;;;;
;;;; examples/core/core_input_gestures.c. Needed `fade`, now bound.
;;;;
;;;; The C keeps `char gestureStrings[20][32]` and TextCopy's a name into the
;;;; next slot. Flan has no mutable character buffer and no way to copy into
;;;; one, but it does not need either: the names are compile-time literals, so
;;;; the log is a `[20 string]` and a slot holds the literal itself. That is
;;;; strictly better than the C — no truncation at 32 bytes, no copy — and it
;;;; is only possible because every string that ever goes into the log is
;;;; known at compile time. A log of strings the program had BUILT could not be
;;;; written at all.
;;;;
;;;; The C's `switch (currentGesture)` over the ten gesture values is a `cond`
;;;; of keyword equalities. `get-gesture-detected` answers an `rl/Gesture` and
;;;; `(= g :tap)` compares against a member by name, checked at compile time —
;;;; so a typo here is an error and the C's `default: break` has nothing to
;;;; catch.
;;;;
;;;; A slot that has not been written yet holds a zero-length string, because a
;;;; `defvar` with no initialiser is all-bytes-zero and a string is ptr+len —
;;;; a null pointer with a length of 0. draw-text draws nothing for it. That is
;;;; the C's `{ "" }` initialiser arriving by a different route, and it is why
;;;; resetting the log below only has to reset the counter.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst max-gesture-strings 20)
(defvar gesture-log [max-gesture-strings string])
(defvar gestures-count i32)
(defvar current-gesture rl/Gesture)
(defvar last-gesture rl/Gesture)
;; The C's switch, as a function. `:else` is its `default:` — an unnamed
;; gesture logs the empty string, which draws nothing, which is what falling
;; through the C's switch without a TextCopy leaves in the slot.
(defn gesture-name [g rl/Gesture] string
(cond
(= g :tap) "GESTURE TAP"
(= g :double-tap) "GESTURE DOUBLETAP"
(= g :hold) "GESTURE HOLD"
(= g :drag) "GESTURE DRAG"
(= g :swipe-right) "GESTURE SWIPE RIGHT"
(= g :swipe-left) "GESTURE SWIPE LEFT"
(= g :swipe-up) "GESTURE SWIPE UP"
(= g :swipe-down) "GESTURE SWIPE DOWN"
(= g :pinch-in) "GESTURE PINCH IN"
(= g :pinch-out) "GESTURE PINCH OUT"
:else ""))
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [core] example - input gestures")
(defer (rl/close-window))
(rl/set-target-fps 60)
(let [touch-area (rl/Rectangle {.x 220.0 .y 10.0
.width (- (f32 screen-width) 230.0)
.height (- (f32 screen-height) 20.0)})]
(until (rl/window-should-close?)
;; Update
(set last-gesture current-gesture)
(set current-gesture (rl/get-gesture-detected))
(let [touch (rl/get-touch-position 0)]
(when (and (rl/collision-point-rec? touch touch-area)
(not (= current-gesture :none))
(not (= current-gesture last-gesture)))
(set (at gesture-log gestures-count) (gesture-name current-gesture))
(set gestures-count (+ gestures-count 1))
;; Full: start over. The stale slots below the counter are never
;; drawn, so they do not have to be cleared the way the C clears
;; them — the loop below is bounded by gestures-count.
(when (>= gestures-count max-gesture-strings)
(set gestures-count 0)))
;; Draw
(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-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)))
(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)))))))