flan/examples/text-input-box.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

150 lines
6.6 KiB
Plaintext

;;;; raylib [text] example - input box
;;;;
;;;; examples/text/text_input_box.c. Two things in this file exist nowhere
;;;; else in the corpus.
;;;;
;;;; The first is get-char-pressed. Everything ported so far reads input as
;;;; state — key-down?, key-pressed?, the mouse position — and this is the
;;;; only example that drains a *queue*: raylib buffers the characters the
;;;; platform produced since the last frame, already through the keyboard
;;;; layout and the dead keys, and hands them back one at a time until it is
;;;; empty. The loop that empties it is the point of the example, and a
;;;; program that read the queue once per frame instead would silently drop
;;;; the second character of a fast pair.
;;;;
;;;; raylib says "empty" by answering 0, and rl/get-char-pressed is now a
;;;; Flan wrapper that says it with None instead — see raylib.flan, "Draining
;;;; raylib's two input queues". What that buys is visible below: the C
;;;; shape, which this file had, reads the queue in *two* places, once to
;;;; prime the loop and once at the bottom of the body, and a `> 0` in
;;;; between that a reader has to know raylib's convention to trust. The
;;;; Option shape reads it in one place, and the case where there is no
;;;; character is a branch the checker knows about rather than a comparison.
;;;;
;;;; The second is set-mouse-cursor, which needed a new defenum. raylib's
;;;; header says `int cursor` and means one of eleven MOUSE_CURSOR_ values, so
;;;; the Flan face is `MouseCursor` — hand-written in raylib.flan beside the
;;;; other cursor calls, excluded from the generated half, and mapped in
;;;; `bindings` so the eleven members are checked against the header. See the
;;;; comment there: this call is made on every frame off a hover test, and the
;;;; failure an i32 would allow is not a crash but a pointer in the wrong
;;;; shape, which nothing reports.
;;;;
;;;; The text buffer, and what it says about strings. The C keeps a
;;;; `char name[MAX_INPUT_CHARS + 1]` and maintains the NUL itself; here it is
;;;; a `[9 u8]` with no terminator, and every use is `(string (slice name 0
;;;; letter-count))`. A Flan string is a pointer and a length, and the shim
;;;; that calls DrawText copies it and NUL-terminates the copy
;;;; (lib/shim.ml) — so the extra byte the C needs has no counterpart here and
;;;; the buffer is exactly as long as the characters it can hold. The two
;;;; draws and the measure all take the same slice, so they cannot disagree
;;;; about where the text ends.
;;;;
;;;; Dropped from the C: its IsAnyKeyPressed helper, which is defined at the
;;;; bottom of the file and called from nowhere.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst max-input-chars 9)
;; No +1: see the header comment. There is no NUL to leave room for.
(defvar name [9 u8])
(defvar letter-count i32)
(defvar text-box rl/Rectangle)
(defvar mouse-on-text bool)
(defvar frames-counter i32)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [text] example - input box")
(defer (rl/close-window))
(set name (array max-input-chars u8))
(set letter-count 0)
(set text-box (rl/Rectangle {.x (- (/ (f32 screen-width) 2.0) 100.0)
.y 180.0 .width 225.0 .height 50.0}))
(set mouse-on-text false)
(set frames-counter 0)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(set mouse-on-text
(rl/collision-point-rec? (rl/get-mouse-position) text-box))
(if mouse-on-text
(do
(rl/set-mouse-cursor :ibeam)
;; Drain the character queue: more than one character can arrive in
;; a single frame — holding a key with the platform's repeat on is
;; the ordinary way that happens — and None is the end of it.
(let [draining true]
(while draining
(match (rl/get-char-pressed)
None (set draining false)
;; 32..125 is the printable ASCII range the C accepts. Anything
;; outside it — an accented letter, a control character — is
;; dropped rather than stored, because the buffer is bytes and a
;; codepoint above 127 would need more than one of them.
(Some key)
(when (and (>= key 32) (<= key 125)
(< letter-count max-input-chars))
(set (at name letter-count) (u8 key))
(set letter-count (+ letter-count 1))))))
(when (rl/key-pressed? :backspace)
(set letter-count (- letter-count 1))
(when (< letter-count 0) (set letter-count 0))))
(rl/set-mouse-cursor :default))
;; The blink phase only runs while the box has focus, so the underscore is
;; always visible on the frame the pointer arrives.
(if mouse-on-text
(set frames-counter (+ frames-counter 1))
(set frames-counter 0))
;; Draw
(rl/with-drawing
(rl/clear-background rl/raywhite)
(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))
(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))
(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)))))))