画面

描くのはランタイム

画面のプログラムは、絵を描きません。「何を描くか」を返すだけです。それを受け取って描くのはランタイムです。

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 ].

画面の状態が変わると、ランタイムが view を呼び直して描き直します。プログラムの側に「ここを描き直す」「この部品を更新する」というコードはありません。状態と見た目が食い違うことがありません。

使える部品は、矩形、文字、円、線、多角形、画像と、それを並べる・まとめる・重ねる・切り抜くものです。ダイアログのように上に浮かせるものや、外れた所を押しても何も起きない確認ダイアログも、同じ形で書けます。

変わった所だけ描き直す

画面のプログラムに「どこを描き直すか」は書きません。状態が変わらなかった部分は、ランタイムが自分で見分けて描き直しません。

一覧の行や監視盤の計器を一つずつ別のアクタにしておくと、一コマで描くのは変わった行、変わった計器だけになります。280 個の計器が並ぶ監視盤で、一コマに描いた部品の数の中央値は 16 でした。

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 は、その場所を行のアクタ r に任せます。行は自分の状態から自分を描きます。一つの行を押すと、描き直されるのはその行だけです。

押したときの処理は、書いた所で検査される

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

ボタンを押したら Save というメッセージが画面に届く、と書きます。画面に Save を処理するメソッドが無ければ、プログラムは動き始めません。ボタンを足して処理を書き忘れた、名前を打ち間違えた、という誤りは、利用者が押す前に見つかります。

ドラッグやホイールのように値を運ぶ操作も同じです。運ばれる値は、受け取るメッセージのフィールドに入ります。数や型が合わなければ、これも動く前にエラーになります。

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 ].

扱える操作は、クリック、文字の入力、キー、ドラッグ、ホイール、ダブルクリック、右クリックのメニュー、ファイルのドロップです。

キー操作は、Windows の業務アプリと同じ

利用者が慣れているキー操作を、ランタイムが受け持ちます。画面ごとに書く必要はありません。

色は名前で書く

色は #paper、#ink のように名前で書きます。名前が何色かは、画面ごとの配色の表が決めます。昼の色と夜の色を切り替えるのは、別の表を返すだけで済みます。会社の配色に合わせるときも、表を一つ用意すれば全部の画面に効きます。

日本語入力

日本語入力の画面。変換中の「日本語」と三つの候補

変換中の文字と候補を、入力欄の中に描いたところ

変換中の文字は、入力欄の中のキャレットの位置に出ます。候補の一覧も画面の側で描けます。プログラムに届くのは確定した文字だけなので、変換の途中を気にする必要はありません。

一行の入力欄は、範囲の選択、コピー・切り取り・貼り付け、元に戻す、単語ごとの移動ができます。複数行の入力欄は、それに加えて折り返しをし、行頭に来てはいけない文字(、。」 など)を守ります。

部品

部品の画面。入力欄、ボタン、チェックボックス、コンボボックス、ラジオボタン、日付、一覧

入力欄、ボタン、チェックボックス、コンボボックス、ラジオボタン、日付、一覧

ボタン、入力欄、複数行の入力欄、チェックボックス、ラジオボタン、スイッチ、コンボボックス、タブ、一覧、ツリー、カレンダー、スクロールバー、スライダー、メーター、小さな折れ線、待っている間の回転表示が揃っています。

部品は見た目を返すだけで、状態も処理も持ちません。押したときに何をするかは、使う画面の側が書きます。部品の中に処理が隠れていないので、画面のコードを読めば何が起きるかが分かります。

表の列は一度だけ定義します。見出し、各行のセル、並べ替えが、同じ列の定義を読みます。列の幅を変えるのは一か所です。

ウィンドウ

常駐する道具

デスクトップに居続ける道具も書けます。

Resident class >> start = (
    Hotkey register: 'Ctrl Ctrl'.
    Window hide.
    Resident fields: { copied: '(まだ何もコピーしていない)', from: '' }).
Resident class >> hidesOnClose = (true).

Resident >> tray = #[
    ((Tray item: '表示') on: #click send: BringUp),
    Tray separator,
    ((Tray item: '終了') 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).

ホットキー、前面のプログラムの名前、コピーされたものの印は Windows だけの機能です。それを使うプログラムを他の OS 向けにビルドしようとすると、何もしないプログラムを黙って作る代わりに、ビルドを断ります。