nandi/jolt-nativepublic Fork 0
71cdc877e694ff5acadac0f1dcc3320531bd6f19
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Write dvui's shape in jolt, on SDL3, with no shared object 109c7e4 · on 71cdc877e694ff5acadac0f1dcc3320531bd6f19 · Veronika Winters · 9d ago
README.md · 102 lines · 4.5 KBmarkdown
Blame HistoryOpen raw

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
# glimmer-jvui

The **jvui** backend for [glimmer](https://github.com/jolt-lang/glimmer), the
reactive GUI toolkit for [jolt](https://github.com/jolt-lang/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`](../../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](https://github.com/jolt-lang/glimmer-gtk), as egui under
[glimmer-vidya](../glimmer-vidya) and as text under
[glimmer-tui](../glimmer-tui) render here as jvui.

```clojure
(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}))
```

```bash
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](../glimmer-gfx) writes its own measure, place, paint and
hit test, because there is nothing underneath it. [glimmer-vidya](../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](../../zig/jolt-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.