画面
描くのはランタイム
画面のプログラムは、絵を描きません。「何を描くか」を返すだけです。それを受け取って描くのはランタイムです。
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 の業務アプリと同じ
利用者が慣れているキー操作を、ランタイムが受け持ちます。画面ごとに書く必要はありません。
- Tab と Shift+Tab で項目を移ります。順番は画面に並べた順で、入れ替えたり外したりもできます。
- フォーカスのあるボタンは、Space か Return で押せます。
保存(&S)と書いたボタンは、Alt+S で押せます。S には下線が付きます。- ダイアログが出ている間、Tab はダイアログの中だけを移ります。閉じると、フォーカスは開く前の場所に戻ります。
- Escape は、いちばん上に出ているものに届きます。
- コンボボックスは F4 と Alt+↓ で開きます。
色は名前で書く
色は #paper、#ink のように名前で書きます。名前が何色かは、画面ごとの配色の表が決めます。昼の色と夜の色を切り替えるのは、別の表を返すだけで済みます。会社の配色に合わせるときも、表を一つ用意すれば全部の画面に効きます。
日本語入力
変換中の文字と候補を、入力欄の中に描いたところ
変換中の文字は、入力欄の中のキャレットの位置に出ます。候補の一覧も画面の側で描けます。プログラムに届くのは確定した文字だけなので、変換の途中を気にする必要はありません。
一行の入力欄は、範囲の選択、コピー・切り取り・貼り付け、元に戻す、単語ごとの移動ができます。複数行の入力欄は、それに加えて折り返しをし、行頭に来てはいけない文字(、。」 など)を守ります。
部品
入力欄、ボタン、チェックボックス、コンボボックス、ラジオボタン、日付、一覧
ボタン、入力欄、複数行の入力欄、チェックボックス、ラジオボタン、スイッチ、コンボボックス、タブ、一覧、ツリー、カレンダー、スクロールバー、スライダー、メーター、小さな折れ線、待っている間の回転表示が揃っています。
部品は見た目を返すだけで、状態も処理も持ちません。押したときに何をするかは、使う画面の側が書きます。部品の中に処理が隠れていないので、画面のコードを読めば何が起きるかが分かります。
表の列は一度だけ定義します。見出し、各行のセル、並べ替えが、同じ列の定義を読みます。列の幅を変えるのは一か所です。
ウィンドウ
- 題とアイコンを画面のクラスに書きます。アイコンは実行ファイルに埋め込まれます。
- 画面の大きさを決めておけば、ウィンドウの大きさに合わせて全体が拡大されます。ウィンドウの大きさに合わせて自分で並べ直す画面も書けます。
- ウィンドウの位置と大きさを覚えておき、次に開いたときに戻せます。
- 閉じるときに一度知らされます。そこで始めた保存は、途中で切られずに最後まで走ります。
- OS のファイルを開く・保存するダイアログが使えます。
常駐する道具
デスクトップに居続ける道具も書けます。
- タスクトレイにアイコンを置き、メニューを出せます。メニューの項目も、ボタンと同じく、押したときのメッセージを書いた所で検査されます。
- どの画面を使っていても呼び出せるホットキーを登録できます。Ctrl の二度押しも使えます。
- どのプログラムでコピーされても知らされます。パスワードマネージャのように「覚えないでほしい」という印が付いたものは見分けられます。
- 閉じるボタンでウィンドウを隠すだけにできます。
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 向けにビルドしようとすると、何もしないプログラムを黙って作る代わりに、ビルドを断ります。