flan/examples/text-input-box.flan
Joseph Ferano d7ceec448e Ten more raylib examples, and the three small structs 3D needed
Three shapes, two text, three textures, one models and one core, picked for
binding surface rather than for how they look.

shapes-basic-shapes brings in six draw families nothing had called — the
circle and rectangle gradients, the triangles, all three poly draws — and is
the first call in the corpus to pass two Colors or three Vector2s at once.
shapes-collision-area is get-collision-rec, the only binding that takes two
Rectangles and answers a third, on a frame path. shapes-following-eyes is the
raymath gap measured rather than worked around: every line of it is vector
arithmetic written without a vector library, the way the C writes it.

text-input-box drains get-char-pressed's queue, which no example had read,
and needed a MouseCursor defenum for set-mouse-cursor. text-writing-anim
replaces TextSubtext — unbindable, it answers a pointer into a rotating
static buffer — with (string (slice b 0 n)), which is the same operation
without the shared state.

textures-image-generation runs nine Gen* calls and the
gen/upload/unload-image path, all procedural, no file on disk.
textures-fog-of-war needed a TextureFilter defenum: the smooth fog edge is
entirely :bilinear on a 25x15 render texture, and it is also the first
draw-texture-pro with a negative source height. textures-mouse-painting is
the same render texture used as a document rather than as scratch, plus the
round trip back off the GPU — load-image-from-texture, image-flip-vertical,
export-image — which nothing had run.

models-box-collisions is the counterexample to "a models example is a binding
exercise": nothing in it is a Model, and one BoundingBox defstruct un-refuses
four functions. core-3d-picking is the only caller anywhere for Ray and
RayCollision, and picking is the inverse of the get-world-to-screen the
corpus already had.

Added to vendor/raylib: defstructs BoundingBox, Ray and RayCollision;
defenums MouseCursor and TextureFilter with their mapping lines in bindings;
hand-written declare-c for SetMouseCursor, SetTextureFilter, DrawCubeV,
DrawSphere, DrawSphereWires, DrawRay and GetScreenToWorldRay, each excluded
from the generated half on the rule bindings already states. generated.flan
regenerated against raylib 5.5: 272 declarations, 117 refused, every
defstruct, hand-written declare-c and mapped constant agreeing with the
header.
2026-09-13 14:42:49 +07:00

141 lines
6.0 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
;;;; answers 0. 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. It is a plain i32 out of the
;;;; generated bindings; nothing needed adding for it.
;;;;
;;;; 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. raylib answers 0 when it is empty, and
;; 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.
(let [key (rl/get-char-pressed)]
(while (> key 0)
;; 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.
(when (and (>= key 32) (<= key 125) (< letter-count max-input-chars))
(set (at name letter-count) (u8 key))
(set letter-count (+ letter-count 1)))
(set key (rl/get-char-pressed))))
(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)))