| Paint glimmer with nothing under it f554a01 nandi 12d ago | 1 | # glimmer-gfx |
| 2 | |
| 3 | glimmer's **software** backend: no toolkit, no shared object, no GPU. The |
| 4 | rasterizer, the layout and the font are jolt; the only foreign code is Xlib, |
| 5 | and Xlib draws nothing — it opens a window and takes a finished framebuffer. |
| 6 | |
| 7 | It is [glimmer-vidya](../glimmer-vidya) with nothing underneath it. The |
| 8 | reconciler does not know what it is patching, so the same hiccup that egui |
| 9 | paints as a window and [glimmer-tui](../glimmer-tui) paints as cells is painted |
| 10 | here, a pixel at a time, by code in this directory: |
| 11 | |
| 12 | ```clojure |
| 13 | (ns myapp |
| 14 | (:require [glimmer.ratom :as ra] |
| 15 | [glimmer.core :as ui] |
| 16 | [glimmer-gfx.core])) ; installs this backend |
| 17 | |
| 18 | (defn app [] |
| 19 | [:card {:spacing 8} |
| 20 | [:title {:label "Counter"}] |
| 21 | [:label {:label (str "Count: " (ra/deref count))}] |
| 22 | [:button {:label "+ 1" :kind :primary :on-click #(ra/swap! count inc)}]]) |
| 23 | |
| 24 | (defn -main [& _] (ui/run app :title "myapp")) |
| 25 | ``` |
| 26 | |
| 27 | ## Running |
| 28 | |
| 29 | ```bash |
| 30 | jolt test # headless: no window, no display |
| 31 | LD_LIBRARY_PATH=/path/to/libX11 jolt counter |
| Play three games on the gfx backend e98a184 nandi 11d ago | 32 | LD_LIBRARY_PATH=/path/to/libX11 jolt tictactoe # a game, in the same widgets |
| 33 | LD_LIBRARY_PATH=/path/to/libX11 jolt maze # WASD, under the widgets |
| 34 | LD_LIBRARY_PATH=/path/to/libX11 jolt asteroids # vector primitives, same two files |
| Paint glimmer with nothing under it f554a01 nandi 12d ago | 35 | ``` |
| 36 | |
| 37 | Xlib is a *system* library here, not one of this repo's crates, and it must be |
| 38 | one the jolt binary can load — on a nix-built jolt, the host `/usr/lib` copy is |
| 39 | a different glibc and fails before `dlopen` returns. |
| 40 | |
| Play three games on the gfx backend e98a184 nandi 11d ago | 41 | `maze` is the other half of the story: sixty frames a second whatever you do, |
| 42 | so there is nothing for a reconciler to reconcile. It skips glimmer and talks |
| 43 | to `raster` and `x11` directly — `run-window` hands it a framebuffer and the |
| 44 | keys held this frame, which is all a game loop ever wanted. |
| 45 | |
| 46 | `asteroids` is what those primitives are actually for: every shape is a list of |
| 47 | points rotated and translated per frame and stroked with `raster/poly!`, so |
| 48 | there is no sprite, no bitmap and no asset — the ship is four points and some |
| 49 | trigonometry. `step` is pure, which is why the tests play a whole game without |
| 50 | a display. |
| 51 | |
| Paint glimmer with nothing under it f554a01 nandi 12d ago | 52 | ## What a backend has to do |
| 53 | |
| 54 | Read this one first. It is the smallest complete backend in the repo, and the |
| 55 | only one where every part of the answer is visible rather than behind an ABI. |
| 56 | |
| 57 | src/glimmer_gfx/raster.clj framebuffer, rect/line, a 3x5 font no FFI |
| 58 | src/glimmer_gfx/core.clj the backend: tree, layout, paint no FFI |
| 59 | src/glimmer_gfx/x11.clj the window, and nothing else |
| 60 | |
| 61 | `glimmer.backend`'s map is mostly trivial — `create!` allocates an atom, |
| 62 | `append-child!` conjes onto a vector. The work is what a toolkit would have |
| 63 | done for you: |
| 64 | |
| 65 | * **A tree to patch.** The reconciler needs somewhere to hold widgets between |
| 66 | frames, which an immediate-mode painter has not got. Here it is atoms of |
| 67 | `{:tag :props :children}`, about 60 lines. glimmer-vidya needs the same thing |
| 68 | and keeps it in Rust behind a second C ABI, because egui hands the |
| 69 | reconciler nothing to hold. |
| 70 | * **Layout.** `measure!` sizes bottom-up, `place!` positions top-down. This is |
| 71 | most of the file. `:spacing`, `:max-width` and container padding are |
| 72 | honoured; `:align` and `:grow` are not, so children keep their measured |
| 73 | width, left-aligned. |
| 74 | * **Hit testing.** Deepest interactive node under the cursor, last match wins. |
| 75 | A press claims a widget and holds it until release, so a slider keeps |
| 76 | tracking when the pointer leaves its rect. |
| 77 | |
| 78 | Tags: `:page` `:card` `:vbox` `:hbox` `:title` `:label` `:button` `:checkbox` |
| 79 | `:slider` `:spacer`. Props are the shared vocabulary — `:label`, `:spacing`, |
| 80 | `:max-width`, `:kind`, `:checked`, `:value`/`:min`/`:max`, `:on-click`, |
| 81 | `:on-change`. |
| 82 | |
| 83 | ## Text |
| 84 | |
| 85 | There is no font stack. `raster.clj` carries a 3x5 bitmap font for ASCII, |
| 86 | authored as art and editable in place: |
| 87 | |
| 88 | "A.#.|#.#|###|#.#|#.#" |
| 89 | |
| 90 | It scales by whole pixels, which is why the UI looks like it does. A real font |
| 91 | means FreeType, which means a crate — at which point this stops being the |
| 92 | backend that runs anywhere. |