Concepts
Tweak UI has two layers:
- Components: solid-js components. They follow the solid conventions: reactive props, controlled or uncontrolled values,
onInputandonChange,class,classListandstyle. - Builder: creates components and binds them to plain objects, signals or stores.
Builder
mount creates a panel in a host element. The builder function runs one time.
import { mount } from 'tweak-ui'
const panel = mount('#tools', (ui) => {
ui.group('Camera', (ui) => {
ui.scalar(camera, 'fov', { min: 10, max: 120 })
ui.button('Reset', { onClick: () => camera.reset() })
})
})A container (group, tabs, dialog, ...) takes a builder function for its content. The function runs when the container creates its content, e.g. when a group expands.
mount returns the panel. It has redraw() and dispose().
Set the float option to show the panel above the page. Drag the title bar to move it. See Float Panel.
mount(document.body, (ui) => { ... }, { float: { title: 'Debug' } })Value binding
The builder binds a control to one of these sources:
// a property. The builder accepts only properties of a matching type.
ui.scalar(camera, 'fov')
// a getter and an optional setter. Without a setter, the control is read-only.
ui.scalar(
binding(
() => light.intensity,
(v) => light.setIntensity(v),
),
{ label: 'Intensity' },
)
// a converted binding: radians in the model, degrees in the control
ui.scalar(adapt(field(camera, 'fovRad'), radToDeg), { label: 'FOV' })
// a signal
const [scale, setScale] = createSignal(1)
ui.scalar([scale, setScale], { label: 'Scale' })
// a mutable store
const settings = createMutable({ volume: 0.5 })
ui.scalar(settings, 'volume')The builder changes vectors, matrices, points and colors in place, if they are arrays or objects. Thus you can edit them also if the property is read-only, e.g. get position().
Redraw
Solid tracks signals and stores. Solid cannot track plain objects. The panel reads plain values again on redraw.
The panel redraws:
- after a control writes a value
- after an option callback (
onClick,onChange, ...) - when you call
panel.redraw()
Call panel.redraw() after code outside of the panel changes a plain object, e.g. in a game loop.
An option can be a function. The panel calls it again on redraw and when a signal that it reads changes:
ui.scalar(camera, 'fov', { max: () => settings.maxFov, readonly: () => camera.locked })In this example, a loop changes time and redraws each frame. scale is a signal.
Widget rows
A widget row has a label and a control. The default label is the property name. An empty label ('', null or false) removes the label.
Components
You can use the components without the builder. With JSX:
const [fov, setFov] = createSignal(60)
<ScalarWidget label="FOV" value={fov()} onInput={setFov} min={10} max={120} />Without JSX, use the shorthand of a component, e.g. uiScalarWidget for ScalarWidget. The shorthand changes a function without parameters to a reactive prop. Additional arguments are the children. A shorthand only converts its arguments. The component is the same as in JSX.
uiScalarWidget({ label: 'FOV', value: fov, onInput: setFov, min: 10, max: 120 })
uiGroup({ title: 'Camera' }, uiWidget({ label: 'Info' }, 'Some text'))mount also renders the content that the builder function returns:
Custom controls
A custom control is a function. It creates its elements one time and binds reactive values. el(tag, props, ...children) creates an element. Function props and function children update in place. Add the control with ui.add, or in a labeled row with ui.widget.
To bind a custom control to a plain object:
- read the value with
ui.panel.pull(() => ...). The panel reads it again on redraw. - call
ui.panel.redraw()after you write the value.