Screens

The Runtime Draws

A screen's program draws nothing. It answers what to draw, and the runtime takes that and draws it.

Counter >> view = Paint group: #[
        Paint rect: 0 y: 0 w: 320 h: 160 color: #paper,
        Paint move: (Paint column: #[
            W label: ('pressed ' , n asString , ' times'),
            W gap: 20,
            (W button: 'Press' wide: 120) on: #click send: Bump ] gap: 0) x: 24 y: 28 ].

When the screen's state changes, the runtime calls view again and draws the result. There is no code in the program saying "draw this again" or "update that widget", so the state and what is shown never disagree.

The pieces are rectangles, text, discs, lines, polygons and pictures, with ways to arrange, group, layer and clip them. Something lifted above the rest, like a dialog, and a confirmation that ignores presses outside it, are written the same way.

Only What Changed Is Drawn Again

A screen's program does not say what to draw again. The runtime tells for itself which parts did not change, and leaves them alone.

Make each row of a list, or each reading on a monitoring wall, an actor of its own, and a frame draws only the rows or readings that changed. On a wall of 280 readings, the median frame drew 16 pieces.

Actor subclass: #Row fields: (name, picked).
Row >> Pick = self copyWith: { picked: picked not }.
Row >> view = (
    bg := picked ifTrue: [ #chosen ] ifFalse: [ #paper ].
    (Paint group: #[
        Paint rect: 0 y: 0 w: 280 h: 36 color: bg,
        Paint move: (W label: name) x: 12 y: 8 ]) on: #click send: Pick).

Actor subclass: #List fields: (rows).
List >> view = Paint column: (rows collect: [ :r | Paint child: r ]) gap: 0.

Paint child: r hands that place to the row's actor r, which draws itself from its own state. Press one row, and that row is all that is drawn again.

What a Press Does Is Checked Where It Is Written

(W button: 'Save' wide: 120) on: #click send: Save

This says: pressing the button sends the screen a Save. If the screen has no method that handles Save, the program does not start. A button added without its handler, or a misspelt name, is found before any user presses it.

Input that carries values, such as a drag or the wheel, works the same way: the values go into the fields of the message that receives them, and a mismatch in number or type is an error before the program runs.

Object subclass: #MoveBy fields: (dx, dy).
MoveBy >> dx = dx.
MoveBy >> dy = dy.

Desk >> MoveBy m = self copyWith: { x: x + m dx, y: y + m dy }.

Desk >> view = Paint group: #[
    Paint rect: 0 y: 0 w: 400 h: 260 color: #paper,
    (Paint rect: x y: y w: size h: size color: #mark) on: #drag send: MoveBy ].

Clicks, typed text, keys, drags, the wheel, double clicks, context menus and dropped files are all handled.

The Keyboard Works as in Windows Business Applications

The runtime takes care of the keys users are used to; no screen has to write them.

Colours Are Written as Names

A colour is written as a name, #paper or #ink, and each screen's colour table says what the names are. Switching between day and night colours is answering another table. Matching a company's colours is one table that every screen uses.

Japanese Input

The Japanese input screen: 日本語 being composed, with three candidates

The composing text and the candidates, drawn inside the field

The text being composed appears at the caret inside the field, and the candidate list can be drawn by the screen too. Only committed text reaches the program, so it never has to deal with a conversion halfway through.

A one-line field has selection, copy, cut and paste, undo, and moving a word at a time. A multi-line box adds wrapping, which keeps the rule that some characters (、。」 and the like) may not start a line.

Widgets

The widgets: a field, buttons, a check box, a combo box, radio buttons, a date and a list

A field, buttons, a check box, a combo box, radio buttons, a date and a list

Buttons, fields, multi-line boxes, check boxes, radio buttons, switches, combo boxes, tabs, lists, trees, a calendar, scrollbars, sliders, meters, sparklines and a busy spinner.

A widget only answers how it looks; it holds no state and no handler. What a press does is written by the screen that uses it. Nothing is hidden inside a widget, so reading a screen's code tells you what happens.

A table's columns are defined once, and the heading, each row's cells and the sorting all read the same definition. Changing a column's width is one place.

The Window

Resident Tools

Tools that stay on the desktop can be written too.

Resident class >> start = (
    Hotkey register: 'Ctrl Ctrl'.
    Window hide.
    Resident fields: { copied: '(nothing copied yet)', from: '' }).
Resident class >> hidesOnClose = (true).

Resident >> tray = #[
    ((Tray item: 'Show') on: #click send: BringUp),
    Tray separator,
    ((Tray item: 'Quit') on: #click send: Quit) ].

Resident >> ClipboardChanged = (
    Resident fields: { copied: Clipboard text, from: (OS foregroundApp orElse: '?') }).

Resident >> HotkeyPressed h = (Window show. self).
Resident >> BringUp = (Window show. self).
Resident >> Quit = (Window close. self).

Hotkeys, the name of the program in front and the clipboard's mark exist on Windows alone. Building a program that uses them for another system is refused, rather than producing one that silently does nothing.