;;;; 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 — is-key-down, is-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.fln" const screen-width = 800 const screen-height = 450 const max-input-chars = 9 ;; No +1: see the header comment. There is no NUL to leave room for. once name: [9 u8] once letter-count: i32 once text-box: rl/Rectangle once mouse-on-text: bool once frames-counter: i32 fn main() -> () rl/init-window(screen-width, screen-height, "raylib [text] example - input box") defer rl/close-window() name = array(max-input-chars, u8) letter-count = 0 text-box = rl/Rectangle({.x f32(screen-width) / 2.0 - 100.0, .y 180.0, .width 225.0, .height 50.0}) mouse-on-text = false frames-counter = 0 rl/set-target-fps(60) until rl/window-should-close() ;; Update mouse-on-text = rl/check-collision-point-rec(rl/get-mouse-position(), text-box) if mouse-on-text rl/set-mouse-cursor(: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 -> 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) -> if key >= 32 and key <= 125 and letter-count < max-input-chars name[letter-count] = u8(key) letter-count += 1 if rl/is-key-pressed(:key-backspace) letter-count -= 1 if letter-count < 0 letter-count = 0 else rl/set-mouse-cursor(: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 then frames-counter += 1 else 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(text-box.x), i32(text-box.y), i32(text-box.width), i32(text-box.height), if mouse-on-text then rl/red else rl/darkgray) let typed = str(slice(name, 0, letter-count)) rl/draw-text(typed, i32(text-box.x) + 5, i32(text-box.y) + 8, 40, rl/maroon) ;; The C's TextFormat("INPUT CHARS: %i/%i", ...), reassembled out of ;; examples/digits.fln 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 x += d/draw-piece("INPUT CHARS: ", x, 250, 20, rl/darkgray) x += d/draw-int(letter-count, x, 250, 20, rl/darkgray) x += d/draw-piece("/", x, 250, 20, rl/darkgray) d/draw-int(max-input-chars, x, 250, 20, rl/darkgray) if mouse-on-text if letter-count < max-input-chars ;; Twenty frames on, twenty frames off, at the pen position after ;; whatever has been typed. if frames-counter / 20 % 2 == 0 rl/draw-text("_", i32(text-box.x) + 8 + rl/measure-text(typed, 40), i32(text-box.y) + 12, 40, rl/maroon) else rl/draw-text("Press BACKSPACE to delete chars...", 230, 300, 20, rl/gray)