| 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 |
| 32 | ``` |
| 33 | |
| 34 | Xlib is a *system* library here, not one of this repo's crates, and it must be |
| 35 | one the jolt binary can load — on a nix-built jolt, the host `/usr/lib` copy is |
| 36 | a different glibc and fails before `dlopen` returns. |
| 37 | |
| 38 | ## What a backend has to do |
| 39 | |
| 40 | Read this one first. It is the smallest complete backend in the repo, and the |
| 41 | only one where every part of the answer is visible rather than behind an ABI. |
| 42 | |
| 43 | src/glimmer_gfx/raster.clj framebuffer, rect/line, a 3x5 font no FFI |
| 44 | src/glimmer_gfx/core.clj the backend: tree, layout, paint no FFI |
| 45 | src/glimmer_gfx/x11.clj the window, and nothing else |
| 46 | |
| 47 | `glimmer.backend`'s map is mostly trivial — `create!` allocates an atom, |
| 48 | `append-child!` conjes onto a vector. The work is what a toolkit would have |
| 49 | done for you: |
| 50 | |
| 51 | * **A tree to patch.** The reconciler needs somewhere to hold widgets between |
| 52 | frames, which an immediate-mode painter has not got. Here it is atoms of |
| 53 | `{:tag :props :children}`, about 60 lines. glimmer-vidya needs the same thing |
| 54 | and keeps it in Rust behind a second C ABI, because egui hands the |
| 55 | reconciler nothing to hold. |
| 56 | * **Layout.** `measure!` sizes bottom-up, `place!` positions top-down. This is |
| 57 | most of the file. `:spacing`, `:max-width` and container padding are |
| 58 | honoured; `:align` and `:grow` are not, so children keep their measured |
| 59 | width, left-aligned. |
| 60 | * **Hit testing.** Deepest interactive node under the cursor, last match wins. |
| 61 | A press claims a widget and holds it until release, so a slider keeps |
| 62 | tracking when the pointer leaves its rect. |
| 63 | |
| 64 | Tags: `:page` `:card` `:vbox` `:hbox` `:title` `:label` `:button` `:checkbox` |
| 65 | `:slider` `:spacer`. Props are the shared vocabulary — `:label`, `:spacing`, |
| 66 | `:max-width`, `:kind`, `:checked`, `:value`/`:min`/`:max`, `:on-click`, |
| 67 | `:on-change`. |
| 68 | |
| 69 | ## Text |
| 70 | |
| 71 | There is no font stack. `raster.clj` carries a 3x5 bitmap font for ASCII, |
| 72 | authored as art and editable in place: |
| 73 | |
| 74 | "A.#.|#.#|###|#.#|#.#" |
| 75 | |
| 76 | It scales by whole pixels, which is why the UI looks like it does. A real font |
| 77 | means FreeType, which means a crate — at which point this stops being the |
| 78 | backend that runs anywhere. |