Skip to content

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, or width and height read 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:

CallWhat 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:

CallUse
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 ​