The first ten of raylib's core list, ported. Seven new bindings and the named colour palette; nothing else was added, because a binding called by nothing is the same as not having bound it. The gaps they found are the point. No number reaches draw-text: i64->bytes answers [u8], draw-text wants a string, and nothing bridges — five of the ten wanted TextFormat and got a glyph table instead. And an enum parameter cannot be driven by a loop variable: the index is an i32, the parameter is an enum, neither converts, and a second declare-c with an i32 face is refused because one C function gets one binding. Two correct rules that compose into a wall. None of the gaps expected blocked anything: no generics, no allocator, no Vec, no escaping closure, no block-scoped defer. These are input-and-draw programs over fixed-size state, which is the shape the language has.
128 lines
5.5 KiB
Plaintext
128 lines
5.5 KiB
Plaintext
;;;; Drawing a number, because the language cannot make one into a string.
|
|
;;;;
|
|
;;;; Five of the ten ported examples call raylib's `TextFormat` to put a number
|
|
;;;; on the screen. Flan has no string formatting and no way to reach it:
|
|
;;;;
|
|
;;;; - `i64->bytes` is a builtin and answers a `[u8]`;
|
|
;;;; - `draw-text` takes a `string`;
|
|
;;;; - nothing converts a `[u8]` into a `string`. A string is a compile-time
|
|
;;;; literal or a parameter, and there is no allocator to build one in.
|
|
;;;;
|
|
;;;; `TextFormat` itself is not bindable either, and not because of the FFI
|
|
;;;; rules: it is variadic, so its signature is not a signature — declaring it
|
|
;;;; with fixed arguments would be a claim about the ABI that is false on every
|
|
;;;; target at once, and it returns a `char *` into a rotating static buffer,
|
|
;;;; which `declare-c` refuses by name anyway ("a string only crosses as a
|
|
;;;; parameter — a C function that *returns* one returns something Flan has no
|
|
;;;; owner for").
|
|
;;;;
|
|
;;;; So a number reaches the screen one digit at a time, each digit drawn as a
|
|
;;;; one-character `string` out of the table below. sand.flan already does this
|
|
;;;; for a single digit with `draw-text-codepoint`; this is the same trick
|
|
;;;; generalised, in one place, so the gap shows up in the report as one gap
|
|
;;;; rather than as five separate improvisations.
|
|
;;;;
|
|
;;;; Everything here needs a window: `measure-text` answers 0 for every string
|
|
;;;; until init-window has loaded the default font, and a zero advance would
|
|
;;;; stack every digit on top of the first.
|
|
|
|
(import rl "vendor:raylib")
|
|
|
|
;; A `[10 string]` — a fixed array whose element type is `string`. That works,
|
|
;; which is worth recording: a string is ptr+len and the array is ten of those
|
|
;; laid out flat, with the bytes themselves in the module's constant data.
|
|
(defconst digit-glyphs [10 string]
|
|
["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"])
|
|
|
|
(defconst minus-glyph "-")
|
|
(defconst dot-glyph ".")
|
|
|
|
;; One glyph, and how far the pen moved. raylib's default font is not
|
|
;; monospaced — "1" is narrower than "8" — so the advance is measured rather
|
|
;; than assumed, which is also what keeps the spacing identical to what
|
|
;; draw-text would have produced for the whole string at once.
|
|
(defn draw-glyph [g string x i32 y i32 size i32 color rl/Color] i32
|
|
(rl/draw-text g x y size color)
|
|
(rl/measure-text g size))
|
|
|
|
;; How many decimal digits `n` has, for n >= 0. 0 has one.
|
|
(defn digit-count [n i32] i32
|
|
(let [d 1
|
|
r (/ n 10)]
|
|
(while (> r 0)
|
|
(set d (+ d 1))
|
|
(set r (/ r 10)))
|
|
d))
|
|
|
|
(defn pow10 [e i32] i32
|
|
(let [p 1]
|
|
(dotimes [i e]
|
|
(set p (* p 10)))
|
|
p))
|
|
|
|
;; The whole point of the file. Answers the width drawn, so a caller can put
|
|
;; something after it — which is how the `TextFormat("%s: %i", ...)` shapes in
|
|
;; the C are reassembled here: draw the literal part with draw-text, then this
|
|
;; at x plus its width.
|
|
;;
|
|
;; Negative numbers get the sign and then the magnitude. i32's most negative
|
|
;; value is NOT handled: negating it wraps to itself, so it would print its own
|
|
;; bit pattern with a minus in front. Nothing here ever reaches it — these are
|
|
;; screen coordinates, frame counts and axis readings — and guarding it would
|
|
;; be a branch that no call site can take.
|
|
(defn draw-int [n i32 x i32 y i32 size i32 color rl/Color] i32
|
|
(let [cx x
|
|
v n]
|
|
(when (< v 0)
|
|
(set cx (+ cx (draw-glyph minus-glyph cx y size color)))
|
|
(set v (- 0 v)))
|
|
(let [count (digit-count v)]
|
|
(dotimes [i count]
|
|
(let [d (% (/ v (pow10 (- (- count 1) i))) 10)]
|
|
(set cx (+ cx (draw-glyph (at digit-glyphs d) cx y size color))))))
|
|
(- cx x)))
|
|
|
|
;; The same with a fixed number of leading zeroes — the C's "%03i". A number
|
|
;; wider than `width` is drawn in full rather than truncated, which is what
|
|
;; printf does too.
|
|
(defn draw-int-padded [n i32 width i32 x i32 y i32 size i32 color rl/Color]
|
|
i32
|
|
(let [cx x
|
|
v n]
|
|
(when (< v 0)
|
|
(set cx (+ cx (draw-glyph minus-glyph cx y size color)))
|
|
(set v (- 0 v)))
|
|
(let [count (max width (digit-count v))]
|
|
(dotimes [i count]
|
|
(let [d (% (/ v (pow10 (- (- count 1) i))) 10)]
|
|
(set cx (+ cx (draw-glyph (at digit-glyphs d) cx y size color))))))
|
|
(- cx x)))
|
|
|
|
;; "%.02f" and friends. `places` digits after the point, rounded by adding a
|
|
;; half at that scale before the split — so 0.999 at two places is "1.00" and
|
|
;; not "0.99", which is what the C's printf would have done and what a reader
|
|
;; comparing the two screens would expect.
|
|
;;
|
|
;; f32 and not f64 deliberately: every number this draws comes out of raylib,
|
|
;; and raylib's are floats. Widening them here would suggest a precision the
|
|
;; value does not have.
|
|
(defn draw-f32 [v f32 places i32 x i32 y i32 size i32 color rl/Color] i32
|
|
(let [cx x
|
|
av v]
|
|
(when (< av 0.0)
|
|
(set cx (+ cx (draw-glyph minus-glyph cx y size color)))
|
|
(set av (- 0.0 av)))
|
|
(let [scale (pow10 places)
|
|
;; The rounding and the split happen in one integer so the two halves
|
|
;; cannot disagree: rounding them separately is how "0.999" becomes
|
|
;; "0.100" — the fraction carries and the whole part does not hear
|
|
;; about it.
|
|
total (i32 (+ (* av (f32 scale)) 0.5))
|
|
whole (/ total scale)
|
|
frac (% total scale)]
|
|
(set cx (+ cx (draw-int whole cx y size color)))
|
|
(when (> places 0)
|
|
(set cx (+ cx (draw-glyph dot-glyph cx y size color)))
|
|
(set cx (+ cx (draw-int-padded frac places cx y size color)))))
|
|
(- cx x)))
|