Morphの設定

もう10年以上Morphicと付き合っているのに、未だに設定のパラメータを覚えられない。
困ったときは自由自在を調べれば良いのだが、備忘録として少しまとめておいた方がいいように思う。
ということでまとめてみた。

モーフの作成

モーフを作るには以下をDo itすればよい。

Morph new.

これだけだと作るだけで画面に表示されないので、作ったモーフを眺めたいなら以下をDo itする。

Morph new openInWorld.

特に座標を指定しなければ左上の座標が(0,0)の状態で表示される。マウスに貼付けた状態にしたいなら以下のようにする。

Morph new openInHand.

Smalltalk勉強会でgoonshさんがやっていた方法は、Do itではなくInspect itする。これだと、インスペクタから直接メッセージを送ることができる。Smalltalkerらしくて格好いい。

モーフ自体の設定

位置の指定

モーフの位置を指定するにはいろんな方法がある。基本は左上の座標を指定することだが、#topLeft:と#position:の2種類ある。(#topLeft:は#position:を呼んでいるだけ)引数としてPointオブジェクトを与える。

Morph new topLeft: 200@300; openInWorld.

この例では、生成したモーフを左上の座標が(200,300)になるように表示する。
他にも、左下、右上、右下、中央を指定する方法がある。それぞれ、#bottomLeft:, #topRight:, #bottomRight:, #center:である。
x座標やy座標だけ変更したい場合、上辺のy座標を指定するには#top:を、下辺のy座標を指定するには#bottom:を、左辺のx座標を指定するには#left:を、右辺のx座標を指定するには#right:を使う。どれも引数として数値を与える。
ちなみに、モーフの位置を取得するには、#leftのようにメッセージ・セレクタからコロン(:)を除いた単項メッセージを送る。

大きさの指定

モーフの大きさを指定するには、#extent:を用いる。引数としてPointオブジェクトを与える。

Morph new extent: 100@10; openInWorld.

この例では、横100px 縦10pxの横長のモーフを画面に表示する。
幅や高さだけを指定するには、それぞれ#width:や#height:を使う。この場合の引数は数値である。
なお、モーフの大きさを変えると、左上位置を基準にして大きさが変わる。

その他の設定

色を指定するには#color:を用いる。引数はColorオブジェクトである。

Morph new color: Color orange; openInWorld.

デフォルトのモーフの色は青(Color blue)だが、上の例では生成したモーフをオレンジ色にしている。

サブモーフの追加と削除

モーフだけならきゃっきゃうふふの平和な世界であるが、現実にはサブモーフを使う必要が出てくるし、Morphicはサブモーフを使うことに意味がある。しかし、そうなると諸悪が吹き出て地獄の世界に一変する。ちなみにサブモーフとは、モーフの中に埋め込んだ他のモーフのことである。

サブモーフの追加

モーフにサブモーフを追加するには、#addMorphFront:か#addMorphBack:を用いる。モーフに埋め込むサブモーフはArrayの形(submorphsインスタンス変数)で管理されており、#addMorphFront:は先頭に、#addMorphBack:は末尾からモーフを挿入・追加する。#addMorph:は#addMorphFront:を呼び出しているので代わりに使うことができる。
サブモーフはsubmorphsインスタンス変数に格納されるのと逆の順序で表示される(Morph>>#drawSubmorphsOn:)ので、末尾に追加したモーフは最下層に表示される。このことは以下のコードで確認することができる。

| x a b c |
x := Morph new extent: 60@60.
a := Morph new extent: 20@20; color: Color red; topLeft: 10@10.
b := Morph new extent: 20@20; color: Color green; topLeft: 15@15.
c := Morph new extent: 20@20; color: Color yellow; topLeft: 20@20.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

addMorphBack
もちろん、addMorphBack:の代わりにaddMorphFront:を使えばサブモーフは上とは逆の順序で表示される。
サブモーフの間にモーフを入れることができる。サブモーフの直前に別のモーフを挿入するには、#addMorph:inFrontOf:を使う。第一引数に指定したモーフを、第二引数のモーフの前に挿入する。
逆にサブモーフの後にモーフを追加するには、#addMorph:after:か#addMorph:behind:を用いる。両者の違いは、無い。(余談だが、Morphicが巨大化する理由にこういう無駄メソッドの存在がある。しかも、前者は3メソッドから、後者は2メソッドからしか参照がない。)引数の指定の仕方は#addMorph:inFrontOf:と同じ。
モーフにサブモーフを埋め込むと、元のモーフの位置を変更した時にサブモーフの位置も自動的に変更される。

サブモーフの削除

サブモーフを削除するには、#removeMorph:を用いる。引数には削除したいサブモーフを指定する。また、全てのサブモーフを削除するには、#removeAllMorphsを用いる。

ActiveWorld removeAllMorphs.

上をDo itすれば、ロゴやタスクバーも含めた全てのモーフが画面から消え、デスクトップに再び平和な世界が訪れる。(何かの作業中はやらない方がいいと思う)

レイアウト

レイアウトは、モーフ内のサブモーフを自動的に配置するための仕組みである。レイアウトを使わなければサブモーフは自由な位置に配置することができるが、大きさの変更などに伴う他のモーフの位置や大きさの調整なども自分でやる必要がある。そういった場合には、おそらくレイアウトを使うのが良いのだろう。レイアウトを上手に使えば、サブモーフの配置に関するかなりの労力を軽減できるが、実際にはとても不可解な動きをすることがあって、頭を悩ませることも多い。
レイアウトにはテーブルレイアウトとプロポーショナルレイアウトがある。前者はサブモーフを縦方向あるいは横方向に並べ、後者は比率に従ってモーフを配置する。今回は前者のみを取り上げ、そのうち気が向いたら後者を取り上げる。

配置の方向

テーブルレイアウトではサブモーフを縦方向あるいは横方向に配置する。その方向は、#listDirection:によって指定する。引数には方向を表すシンボルとして、#leftToRight(左から右へ)、#rightToLeft(右から左へ)、#topToBottom(上から下へ)、#bottomToTop(下から上へ)を与えることができる。

| x a b c |
x := Morph new extent: 60@60; changeTableLayout; listDirection: #leftToRight.
a := Morph new color: Color red; topLeft: 10@10.
b := Morph new color: Color green; topLeft: 15@15.
c := Morph new color: Color yellow; topLeft: 20@20.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

上の例では、モーフをテーブルレイアウトに切り替え、左から右へ横方向の配置にしている。赤、緑、黄のサブモーフは、この順で保持され、画面上も青いモーフの左端から赤、緑、黄の順序で並ぶ。(下図の上段)また、listDirectionの引数を#rightToLeftにすると、青いモーフの右端から赤、緑、黄の順で右から左方向に並ぶ。(下図の下段)
Screen Shot 2014-04-26 at 8.59.59

サイズの自動調整

直前の例では、青いモーフ(サブモーフに対するオーナー(owner))が、サブモーフの横幅より小さかったり、サブモーフの縦幅より大きかったりしている。サブモーフのサイズに合わせたり、逆にオーナーのサイズに合わせるには#hResizing:や#vResizing:を用いる。前者は横方向の、後者は縦方向のサイズ調整を指定する。
これらのメッセージには1つの引数を与える。その値には#rigid, #shrinkWrap, #spaceFillのいずれかである。#rigidはデフォルトの状態で、サイズの自動調整をしない。#shrinkWrapはサブモーフの大きさに合わせてサイズを調整する。#spaceFillはオーナーの大きさに合わせてサイズを調整する。
#shrinkWrapの例として、先ほどの青いモーフの横幅をサブモーフに合わせて広げるようにする。

| x a b c |
x := Morph new extent: 60@60; changeTableLayout; listDirection: #leftToRight; hResizing: #shrinkWrap.
a := Morph new color: Color red.
b := Morph new color: Color green.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

モーフのサイズを60×60に指定しているが、hResizing: #shrinkWrapによって、横方向が自動調整され、サブモーフの横幅にあわせられる。
Screen Shot 2014-04-26 at 9.50.59
shrinkの語感から、モーフを縮めるようにしか働かないような気がするが、上の例のようにモーフを広げるようにも働く。
#spaceFillの例として、上のサブモーフのうち緑色のモーフを青色のモーフのサイズまで拡張する。

| x a b c |
x := Morph new extent: 60@60; changeTableLayout; listDirection: #leftToRight; hResizing: #shrinkWrap.
a := Morph new color: Color red.
b := Morph new color: Color green; vResizing: #spaceFill.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

緑色のモーフで#vResizing:の引数に#spaceFillを指定することで、オーナーである青色のモーフの高さに自動調整された。
Screen Shot 2014-04-26 at 9.55.44

配置の自動調整

オーナーである青のモーフの大きさがサブモーフに対して大きい場合に配置を自動的に調整することを考える。

| x a b c |
x := Morph new extent: 200@200; changeTableLayout; listDirection: #leftToRight.
a := Morph new color: Color red.
b := Morph new color: Color green.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

Screen Shot 2014-04-26 at 10.14.47
これに対して、サブモーフを横方向の配置を行う場合、#topLeft, #bottomRight, #center, #justifiedのいずれかを引数として#listCentering:メッセージを送る。例えば、サブモーフを中央に揃える場合、以下のようにする。

| x a b c |
x := Morph new extent: 200@200; changeTableLayout; listDirection: #leftToRight; listCentering: #center.
a := Morph new color: Color red.
b := Morph new color: Color green.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

Screen Shot 2014-04-26 at 10.14.22
配置の自動調整で気をつけなければいけないのは、#listCentering:の設定が#listDirection:の設定に依存している点である。上の例で、#listDirection:は#leftToRightつまり左から右の横方向である。これを#topToBottomつまり、縦方向にするとサブモーフの並びも縦方向の中央に揃えられる。

| x a b c |
x := Morph new extent: 200@200; changeTableLayout; listDirection: #topToBottom; listCentering: #center.
a := Morph new color: Color red.
b := Morph new color: Color green.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

Screen Shot 2014-04-26 at 10.23.01
つまり、#listCentering:のパラメータの意味は、#listDirection:の状態によって変わる。#listDirection:が#leftToRightの時は#topLeftが左揃えになるが、#topToBottomの時は#topLeftが上揃えを意味する。

サブモーフの折り返し

下の図のようにサブモーフに対してオーナーの横幅が小さい場合について再度考える。
Screen Shot 2014-04-26 at 10.34.05
青のモーフをはみ出している黄色のサブモーフを折り返してオーナー内に収めるには、#wrapDirection:を用いる。#wrapDirection:には#none, #leftToRight,#rightToLeft,#topToBottom,#bottomToTopを引数として与えることができて、折り返しの方向を指定する。なお、デフォルト値は#noneで折り返しをしない。
黄色のサブモーフを下側に折り返すには、#wrapDirection:に#topToBottomを指定する。

| x a b c |
x := Morph new extent: 120@100; changeTableLayout; listDirection: #leftToRight; wrapDirection: #topToBottom.
a := Morph new color: Color red.
b := Morph new color: Color green.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

Screen Shot 2014-04-26 at 10.44.16
なお、#listDirection:に横方向(例えば#leftToRight)の設定をした場合は、#wrapDirection:には縦方向(例えば#topToBottom)の設定をする必要がある。

折り返しの配置

折り返した場合のサブモーフ全体の配置は、#wrapCentering:で行う。パラメータには、#topLeft, #bottomRight, #center, #justifiedを指定することができる。例えば、サブモーフ全体は横方向の中央に配置するが、折り返しで縦方向に均等配置するには以下のようにする。

| x a b c |
x := Morph new extent: 120@100; changeTableLayout; listDirection: #leftToRight; listCentering: #center; wrapDirection: #topToBottom; wrapCentering: #justified.
a := Morph new color: Color red.
b := Morph new color: Color green.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

Screen Shot 2014-04-26 at 10.54.18

余白の設定

サブモーフの自動配置では、オーナーのサイズを目一杯使う。オーナーの内側に余白を設けたり、サブモーフの間隔を調整するには#layoutInset:を用いる。
オーナーの内側に3ピクセル分の余白を設けるには、以下のようにする。

| x a b c |
x := Morph new extent: 200@100; changeTableLayout; listDirection: #leftToRight; listCentering: #justified; layoutInset: 3.
a := Morph new color: Color red.
b := Morph new color: Color green.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

Screen Shot 2014-04-26 at 11.01.26
横方向と縦方向で余白の量を調整したい場合には、数値ではなく座標(Point)を指定する。例えば、横方向は10ピクセルだが縦方向は5ピクセルにしたい場合、上の例で、layoutInset: 10@5のように指定する。
左右や上下の余白を別々に指定する場合には、矩形(Rectangle)を指定する。例えば、左は2ピクセル、上は3ピクセル、右は10ピクセル、下は20ピクセルにしたい場合、layoutInset: (2@3 corner: 10@20)のように指定する。

| x a b c |
x := Morph new extent: 200@100; changeTableLayout; listDirection: #leftToRight; listCentering: #justified; vResizing: #shrinkWrap; layoutInset: (2@3 corner: 10@20).
a := Morph new color: Color red.
b := Morph new color: Color green.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

Screen Shot 2014-04-26 at 11.09.15

間隔の設定

サブモーフ間の間隔を設定するには、#cellInset:を用いる。例えば、サブモーフ間に3ピクセルの間隔を設定するには、以下のようにする。

| x a b c |
x := Morph new extent: 120@100; changeTableLayout; listDirection: #leftToRight; wrapDirection: #topToBottom; cellInset: 3.
a := Morph new color: Color red.
b := Morph new color: Color green.
c := Morph new color: Color yellow.
x addMorphBack: a; addMorphBack: b; addMorphBack: c.
x openInWorld.

Screen Shot 2014-04-26 at 11.14.20
横方向と縦方向の間隔を変えたい場合には、数値ではなく座標(Point)を使う。横方向は3ピクセル、横方向は6ピクセルの間隔にしたい場合には、cellInset: 3@6のようにする。
Screen Shot 2014-04-26 at 11.15.39
最後におさらいとして以下をやってみよう。何が起こるか考えてからやった方がいいだろう。

ActiveWorld changeTableLayout; listDirection: #leftToRight; wrapDirection: #topToBottom.

疲れたので以上で終わり。Morphicの設定は微妙で難解だが、あまり複雑なことをしなければそこそこ楽にUIを作れる。これらの理解にあたって、「自由自在Squeakプログラミング」は欠かせない。この記事を作るのにも参考にさせていただいた。著者の梅澤 真史さんに大感謝である。