glimmer-jvui
The jvui backend for glimmer, the
reactive GUI toolkit for jolt.
glimmer owns the portable half — reactive cells, the component model, the
reconciler — and knows nothing about any toolkit. This project supplies the
other half out of ../../jvui, which is dvui's shape written in
jolt on SDL3. Requiring glimmer-jvui.core registers it, and components that
render as GTK widgets under
glimmer-gtk, as egui under
glimmer-vidya and as text under
glimmer-tui render here as jvui.
(ns myapp
(:require [glimmer.ratom :as ra]
[glimmer.core :as ui]
[glimmer-jvui.core])) ; installs this backend
(defn counter []
(let [n (ra/atom 0)]
(fn []
[:card {}
[:title {:label "Counter"}]
[:label {:label (str "Count: " (ra/deref n))}]
[:hbox {:spacing 8}
[:button {:label "- 1" :on-click #(ra/swap! n dec)}]
[:button {:label "+ 1" :kind :primary :on-click #(ra/swap! n inc)}]]])))
(defn -main [& _] (ui/run counter {:title "myapp" :max-width 420}))
jolt test # headless: no window, no SDL, no font, no display
jolt counter # a window
jolt counter --shot
The smallest of the four backends
A glimmer backend usually has to supply everything a toolkit would have done
for it. glimmer-gfx writes its own measure, place, paint and
hit test, because there is nothing underneath it. glimmer-vidya
keeps a node arena in Rust behind a second C ABI, because egui hands a
reconciler nothing to hold.
Here none of that is needed, because jvui is a toolkit rather than an ABI. What
is left is the one thing an immediate-mode library does not have — somewhere to
put a widget between frames — and it is thirty lines of atoms:
src/glimmer_jvui/core.clj the tree, the walk, the loop
create! makes an atom, append-child! conjes onto a vector, and once a frame
emit! walks that tree and calls the jvui widget each node names. Layout,
clipping, focus, capture and painting are jvui's.
Three things worth knowing
The walk is the closure. glimmer-vidya's README explains why its tree lives
in Rust: egui's ScrollArea and Frame take an FnOnce(&mut Ui) and keep
their begin/end private, so a push/pop ABI cannot scroll a page. jvui's
containers take a body function for the same reason — and here the recursion
is that function. emit! on a container hands emit-children! over as the
body and the nesting takes care of itself, which is why there is no push/pop
anywhere in this file.
Every node carries a key. jvui identifies a widget by its parent and its
index among its siblings, unless it is given a :key, which replaces the
index. A reconciler reorders children, and an identity built on the index would
hand every widget after the moved one the caret, the scroll offset and the drag
of whichever widget used to sit at its index. So each node takes a serial
number at creation and passes it as its key, and identity follows the node
rather than its position. That is the bug class
zvui's README describes from the backend side, closed
here at the other end — and jolt test reorders a list and checks it.
A re-render is queued, never inline. A handler fires in the middle of the
walk, and a ratom change would have the reconciler patch the tree while it is
being walked — half the frame old, half new. :schedule queues the work and
jvui's :before hook drains it at the top of the next frame, before anything
is placed.
What renders
Containers: :page :card :frame :vbox :box :hbox :scroll.
Widgets: :title :label :dim-label :button :checkbox :slider
:entry/:text-entry :progress :separator :spacer/:gap.
Props are the vocabulary the other backends share — :label/:text,
:spacing, :padding, :margin, :orientation, :max-width, :kind,
:checked, :value/:min/:max, :placeholder, :dim, :size,
:on-click, :on-change.
An unknown tag is a container rather than an error, so a tree written against a
richer backend still shows its contents — the same bargain jolt-zvui makes.
Not done
Everything jvui has not got, and nothing else: one font style, no text
selection, vertical scrolling only, no animation clock, no menus or dialogs.
:sensitive, :multiline and :fill-height are accepted and ignored.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
|