;;;; 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/begin-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)))) (rl/end-drawing)))