Widgets
A small library of reusable controls composed from the DSL primitives and vdom hooks. A widget is an ordinary vdom component, so its interaction state (hover, pressed) lives in useState…
import io.github.edadma.suit.widgets.*
A small library of reusable controls composed from the DSL primitives and vdom hooks. A
widget is an ordinary vdom component, so its interaction state (hover, pressed) lives in
useState and survives re-renders, and it reconciles in place exactly like an application
component. Each widget is purely declarative output over box / text / row / stack;
the render tree, layout, and input routing underneath give it pixels and behaviour.
These widgets are pointer- and keyboard-driven only. A text field needs text-input
support in the SDL binding, and a scroll view needs renderer clip-rects; until those land
in sdl3, TextField and ScrollView are deliberately absent
rather than approximated. The focus and keyboard infrastructure they need already exists.
Button
val Button: Component2[String, () => Unit]
A push button: a labelled, focusable rectangle that calls onPressed when clicked (a press
and release on the button) or activated from the keyboard (Space or Enter while focused). It
tints on hover and while held.
Button("Increment", () => setCount(count + 1))
Checkbox
val Checkbox: Component2[Boolean, Boolean => Unit]
A small focusable square that toggles, calling onChange with the new state on a click or
on Space while focused. It is controlled — it draws the checked it is given and never
holds the value itself, so the parent owns the state. The check is a filled inner square (no
glyph-font dependency).
val (checked, setChecked, _) = useState(false)
Checkbox(checked, setChecked)
Slider
val Slider: Component2[Double, Double => Unit]
A horizontal slider over the range 0..1: a full-width track with a draggable thumb. It is
controlled — it renders the value it is given and reports a new value through
onChange on a press, a drag, or the arrow keys while focused. The new value comes from the
press position in the slider’s own coordinate space (local.x / size.width), which is why
the handlers live on the outer track, not the thumb (see
pointer capture).
val (level, setLevel, _) = useState(0.4)
box(width = 240)(
Slider(level, setLevel),
)
Theme
object Theme:
val primary, primaryHover, primaryActive, onPrimary: Color
val surface, border, accent, track: Color
The default palette the built-in widgets paint with — a small dark-on-accent theme. It is
just a set of named Colors; applications can ignore it and pass their own, but the widgets
read from it so a stock control looks consistent.
A controlled-widget example
Because Checkbox and Slider are controlled, the pattern is always the same: hold the
value in useState, render the widget with it, and pass the setter as onChange.
val App = view {
val (on, setOn, _) = useState(true)
val (level, setLevel, _) = useState(0.5)
col(spacing = 16)(
row(crossAxisAlignment = CrossAxisAlignment.Center, spacing = 8)(
Checkbox(on, setOn),
text(if on then "on" else "off", color = Color.white),
),
text(s"${(level * 100).toInt}%", color = Color.white),
box(width = 240)(Slider(level, setLevel)),
)
}