diff --git a/NEXT.md b/NEXT.md index 7d6f7e2..74ac027 100644 --- a/NEXT.md +++ b/NEXT.md @@ -237,14 +237,30 @@ little-endian reading of the packed integer, so an identity would have passed a weaker test. That case is in the acceptance table, skipped if `libraylib` is not installed. -The bindings are 18 calls: window (`init-window`, `close-window`, +The bindings are 29 calls: window (`init-window`, `close-window`, `window-should-close?`, `set-target-fps`, `set-trace-log-level`), keyboard (`key-pressed?`/`down?`/`released?`), mouse (`mouse-button-pressed?`/`down?`/ -`released?`, `get-mouse-position`), `get-color`, and drawing (`begin-drawing`, -`end-drawing`, `draw-fps`, `clear-background`, `draw-rectangle`), plus the -`Key`, `MouseButton` and `TraceLogLevel` enums. Adding one is three lines: a +`released?`, `get-mouse-position`), `get-color`, drawing (`begin-drawing`, +`end-drawing`, `draw-fps`, `clear-background`, `draw-rectangle`), textures +(`load-texture`, `texture-valid?`, `unload-texture`, `draw-texture`, +`draw-texture-v`, `draw-texture-ex`, `draw-texture-rec`), and the shapes +texture and rectangle intersection (`set-shapes-texture`, +`get-shapes-texture`, `get-shapes-texture-rectangle`, `get-collision-rec`), +plus the `Key`, `MouseButton` and `TraceLogLevel` enums and the `Vector2`, +`Color`, `Texture2D` and `Rectangle` structs. Adding one is three lines: a `declare`, an `extern` prototype, and a one-line wrapper. +The texture calls are the first ones with no headless test, because loading +one needs a GL context. What the acceptance case does instead is pin the two +new struct layouts using the only things raylib computes from those fields +without a GPU: `GetCollisionRec`, which pins `Rectangle` completely, and +`SetShapesTexture`'s default substitution, which pins `Texture2D`'s `id` and +`format` and nothing else. Handing a struct over and reading it back proves +nothing at all — storing and returning is symmetric, so a permuted layout +comes back permuted the same way and the case passes. `width`, `height` and +`mipmaps` are therefore checked only by looking at `sand.flan` running, which +draws the brush sprite four ways for that reason. + No raylib headers are needed: `shim.c` declares the prototypes it uses, so the build depends on the shared library being linkable and not on `raylib-devel`. `vendor/raylib/link` carries `-l:libraylib.so.550` because Fedora ships the diff --git a/brush.png b/brush.png new file mode 100644 index 0000000..f2e96d4 Binary files /dev/null and b/brush.png differ diff --git a/sand.flan b/sand.flan index 8671f69..eb29636 100644 --- a/sand.flan +++ b/sand.flan @@ -28,6 +28,40 @@ (import sim "sand-sim") ; no collection prefix: relative to this file (import agent "vendor:agent") ; the dev agent: redefinitions, installed below +;; The brush sprite: a 16x8 sheet of two 8x8 frames, the ring drawn while the +;; mouse is idle and the blob while it is painting. It is here because the +;; texture calls cannot be in the acceptance table at all — loading one needs a +;; GL context — so the only way they are exercised is by running this. +(defvar brush rl/Texture2D) +(defvar brush-ok bool) + +;; A missing file is not a crash and not silence: LoadTexture hands back a +;; texture with an id of 0, every draw with it is a no-op, and the program +;; looks like it has a drawing bug. So it is asked and said once, here. +(defn load-brush [] + (set brush (rl/load-texture "brush.png")) + (set brush-ok (rl/texture-valid? brush)) + (unless brush-ok + (print-line "sand: cannot load brush.png — drawing the cursor is off"))) + +;; Four draws, one per shape the call comes in, because none of them can be in +;; the acceptance table. The cursor picks one frame out of the sheet and so +;; needs a source rectangle; the three badges in the corner are the whole +;; sheet, at integer coordinates, at a Vector2 tinted with the current sand +;; colour, and scaled up — draw-texture, draw-texture-v and draw-texture-ex. +(defn draw-brush [] + (when brush-ok + (let [m (rl/get-mouse-position) + frame (f32 (if (rl/mouse-button-down? :left) 8.0 0.0))] + (rl/draw-texture-rec brush + (rl/Rectangle {:x frame :y 0.0 :width 8.0 :height 8.0}) + (rl/Vector2 {:x (- (.x m) 4.0) :y (- (.y m) 4.0)}) + rl/white) + (rl/draw-texture brush 20 50 rl/white) + (rl/draw-texture-v brush (rl/Vector2 {:x 44.0 :y 50.0}) + (rl/get-color (nth sim/colors sim/current-color))) + (rl/draw-texture-ex brush (rl/Vector2 {:x 72.0 :y 46.0}) 0.0 2.0 rl/white)))) + ;; Locals are assignable places (spec-memory.md); parameters are not. (defn paint [] (let [m (rl/get-mouse-position) @@ -61,6 +95,7 @@ (i32 (* row sim/cell-size)) sim/cell-size sim/cell-size (rl/get-color c)))))) + (draw-brush) (rl/draw-fps 20 20)) (defn main [] @@ -68,6 +103,10 @@ (rl/init-window sim/screen-width sim/screen-height "SAND") (defer (rl/close-window)) (rl/set-target-fps 120) + ;; After the window, never before: LoadTexture uploads to the GPU and there + ;; is no GPU to upload to until InitWindow has made a context. + (load-brush) + (defer (rl/unload-texture brush)) ;; The dev agent listens on a socket for redefinitions and hands them over; ;; (agent/poll) below is where they are installed. Building without --dev is ;; fine — nothing has cells to install into, so a module is refused on the