Spec2入門(その5)

これはSmalltalk Advent Calendar 2019の記事です。

Spec2は、Pharo Smalltalk で採用されているUIフレームワークであるSpec の新しいバージョンです。Pharo 7.0 では Spec が使われていましたが、Pharo 8 では(ほぼ?)全てのUIが Spec2 で書き直されるようです。この記事では Spec2 を使った UI の実装方法について簡単に紹介します。

Squeak/Pharo Smalltalk では、UIの実現のために長らく Morphic を用いてきました。Morphic はシンプルで強力なフレームワークですが、複数のコンポーネントを組み合わせていくとアナーキーな実装をしがちで、リファクタリングや再利用の際に泣きたくなるような状況に陥りがちです。Spec/Spec2 はUIの構築に秩序をもたらし、コンポーネントの再利用がしやすく設計されています。

前回の記事では、Scratch 1.4風のファイル選択ダイアログに、ディレクトリ移動の機能を追加しました。


前回はファイル一覧でディレクトリ名をダブルクリックしたらサブディレクトリに移動する機能を付けましたが、それだと深くディレクトリに入っていくだけなので、今回は親ディレクトリに移動するボタンを追加します。また、現在のディレクトリ階層がわかるようなドロップリストも追加します。

ドロップリストとボタンを追加する

新しくボタンとドロップリストを追加するので、それぞれを格納するインスタンス変数を加えてクラスを再定義します。

SpPresenter subclass: #SpecFileListSample
     instanceVariableNames: 'directory listEntries buttonParent listCurrent'
     classVariableNames: ''
     package: 'SpecSample'

追加したのは buttonParent と listCurrent で、それぞれ親ディレクトリへの移動ボタンとディレクトリ階層を表すドロップリストを格納します。

いつものように initializePresenters で、それぞれのウィジェットを初期化します。

initializePresenters
     listEntries := self newList
         beSingleSelection;
         activateOnDoubleClick;
         items: self getEntries.
     buttonParent := self newButton
         icon: (Smalltalk ui icons iconNamed: #up).
     listCurrent := self newDropList.
     (以下略)

以前に紹介したとおり、ボタンは self newButton で生成します。また、ドロップリストは self newDropList で生成します。ボタンには上矢印のアイコンを付けておきます。

ボタンの機能の設定は、 connectPresenters メソッドで行います。ボタンが押されたら changeParentDirectory メッセージを送って親ディレクトリに移動するようにします。(太字部分を追加します)

ボタンの機能を設定する

connectPresenters
     listEntries
         display: [ :m | self showEntry: m ];
         whenActivatedDo: [ :selection | self entriesChanged: selection ].
     buttonParent action: [ self changeParentDirectory ].

changeParentDirectory のメソッドを実装します。単にルートディレクトリでなければ親ディレクトリに設定しなおすだけです。

changeParentDirectory
     directory isRoot ifTrue: [ ^ self ].
     self directory: directory parent

レイアウトを修正する

レイアウトを Scratch 1.4 のダイアログボックスのように変更します。

上の図では、ファイル一覧の上側にドロップリストとボタンが一列にならんでいます。このようなレイアウトを実現するには、クラスメソッドの defaultSpec を以下のようにします。

defaultSpec
     ^ SpBoxLayout newVertical
         add:
             (SpBoxLayout newHorizontal
                 add: #listCurrent;
                 add: #buttonParent;
                 yourself);
         add: #listEntries;
         yourself

SpBoxLayout newVertical で生成された縦並びのレイアウトオブジェクトに、SpBoxLayout newHorizontal で生成された横並びのレイアウトオブジェクトを追加して、そこでドロップリストとボタンを追加しています。

このレイアウト設定で表示させると、上のように高さや幅が均等な状態になって不格好です。そこで、子ウィジェットの幅や高さを制限するようにします。

defaultSpec
     ^ SpBoxLayout newVertical
         add:
             (SpBoxLayout newHorizontal
                 add: #listCurrent;
                 add: #buttonParent withConstraints: [ :c | c expand: false ];
                 yourself)
             withConstraints: [ :c | c expand: false ];
         add: #listEntries;
         yourself

withConstraints: メッセージをレイアウトオブジェクトに送ると、幅や高さなどを細かく設定できます。width: や height: で直接値を指定することもできますが、ここでは expand: false メッセージを送ることで幅や高さを拡張しないようにしています。

ウィジェット間にスペースを入れる

今度は上限のウィジェットが詰まりすぎなので、間にスペースを入れます。

defaultSpec
     ^ SpBoxLayout newVertical
         spacing: 4;
         add:
             (SpBoxLayout newHorizontal

spacing: メッセージでウィジェット間のスペースを調整できます。これで下図のようにすっきりしました。

右側のボタンを押せば親ディレクトリに移動することも確認できます。

親ディレクトリに移動するボタンの追加は以上です。リストボックスの機能は実現できませんでしたので、次回に回したいと思います。