Concepts
willowui has four ideas. Learn them once and every widget behaves the same way.
Components
Every widget is a Component (Button, Label and Window all extend it). A component has:
- a name, given in the constructor, used to find it later;
- a position and a size (
setPosition,setSize, orwidthandheightread back); - children, added with
addChild, which it can lay out for you (see Layout); - state flags: enabled, visible, hovered, pressed, focused;
- a variant that picks its look from the theme (primary, secondary, danger and so on);
- an underlying willow node (
component.node) that holds the drawing.
A plain Component draws nothing. It is a box that holds and arranges children. A Panel is the same with a background, border and corner radius.
Components can also hold raw willow nodes that are not widgets, for example a Sprite or a Quad, with addRawChild(node).
Screens
A Screen is one page of UI: a root component, a focus manager and an input manager. You add widgets to it with screen.add(widget), and you can look one up with screen.findByName("save").
A screen has a lifecycle:
| Call | What happens |
|---|---|
show() | Attaches the screen to the scene. The first time, calls the controller's onCreate. |
hide() | Detaches it, keeping all its state. |
update(dt) | One frame: overlays, focus and keyboard dispatch, the controller's onUpdate, then layout. |
dispose() | Calls onDispose, stops every tracked binding and disposes the whole widget tree. The screen cannot be shown again. |
Controllers
A Controller owns a screen's behaviour. It has three methods:
ts
interface Controller {
onCreate(screen: Screen): void; // build the UI here
onUpdate(dt: float): void; // per-frame logic
onDispose(): void; // clean up
}Attach one with new Screen().withController(new MyController()). A controller is optional: a screen without one uses a controller that does nothing.
A controller that also has lookupRef(path) and callMethod(name) is a data provider for XML templates. See Bindings and events.
The stage
The stage (DEFAULT_STAGE, a StageManager) is a stack of screens. You tell it where to draw once, then push screens onto it:
| Call | Use |
|---|---|
setScene(engine.root2d) | Where screens attach. Call once at start-up. |
add(screen) | Push and show a screen. |
replace(screen) | Dispose the top screen and show the new one, for moving from a title screen to a game screen. |
remove(screen) | Dispose one screen. |
top() | The top screen, or null. |
update(dt) | Update every visible screen. Call it from App.onFrame. |
dispose() | Dispose everything, at shutdown. |
The usual frame loop is one line:
ts
App.onFrame((dt: float): void => { DEFAULT_STAGE.update(dt); });Disposal
Widgets own native objects (nodes, textures, text), and ThicketScript frees them explicitly. Disposing a component disposes its children, its nodes and every binding it owns. Disposing a screen disposes everything on it. So:
- remove a widget for good with
widget.dispose(); - move between screens with
DEFAULT_STAGE.replace(next), which disposes the old one; - a component can take ownership of any stop function with
component.own(stopFn), so the stop runs when the component goes.
Overlays and managers
Some widgets float above everything: windows, menus, popovers, tooltips, toasts and the color picker. Each has a default manager (DEFAULT_WINDOW_MANAGER, DEFAULT_MENU_POPUP_MANAGER, DEFAULT_POPOVER_MANAGER, DEFAULT_TOOLTIP_MANAGER, DEFAULT_TOAST_MANAGER, DEFAULT_COLOR_PICKER_MANAGER) that keeps their stacking order and input in step. You usually only touch them to add a window or show a toast.
Next
- Layout
- Reactive state: refs, computed values and binding widgets to data.
- XML templates
- Widget catalogue