GUIの「こちら側」からのデザイン #macosnative
この記事は、2019年7月7日に開催されたmacOSネイティブアプリケーション開発を主題としたイベント macOS native symposium #04 での同名講演を記事化したものです。MacやmacOSのGUIデザインをテーマに「入力方法」に関するデザインの観点を考察しました。
GUIというとどうしても画面表現の方法について考えてしまいがちですが、ユーザーインターフェイスである以上はその入力方法にも着目しなければなりません。今回は、私たちがどのようにしてMacを操作するのか、GUIの入力方法(操作方法)のデザインを考えてみたいと思います。
インターフェイスの「側」
今回「側」という表現を用いてみました。インターフェイスの「側」とはどういうことなのでしょう?
例えば次のイメージはQuickTime Playerのインスペクタパネルです。ユーザーの視点で見ると当たり前ですがこのように見えます。特におかしなところはありませんね。
ユーザー視点で見たGUIの表面
では、次のイメージはどうでしょう? インスペクタを表裏反転させてみたものです。この見え方はコンピューターからの視点だと捉えてください。表裏反転した「インスペクタの向こう側」にユーザーの影が見えます。
コンピューター視点で見たGUIの表面
このように、インターフェイスを一種の面と捉えてみると、その面の両側には「こちら側=ユーザーの領域」と「あちら側=コンピューターの領域」がそれぞれ存在していて、両者は互いに向き合っていることが窺えるかと思います。
インタラクション
インターフェイスという言葉を英単語で考えてみたいと思います。英語では “Interface” と書きますが、これは二つの言葉で構成されています。間にハイフンを差し込むと “Inter-face” となりますが、これを直訳しながら読み解くと、“inter-” とは「何かの間」、“face” とは「表面」という意味であることがわかります。要するに、“Interface” とは「面と面の間」のことを表しています。
上の図は「こちら側」と「あちら側」の領域を簡単に図示してみたものです。この二つの領域の境界面のあたりがインターフェイスです。左側にはユーザーとしてヒトがいます。ここが私たちの住む世界です。右側にはソフトウェアの領域があり、そこには何かしらのオブジェクトの姿が見えます。
このときの両者の関わり方は、インターフェイスを通して互いに向き合っていて、何かしらの作用を与えあっているものと考えられます。ヒトが「あちら側」に与える具体的な作用としては、カーソル移動、クリック、キー入力、音声入力などが挙げられます。対してソフトウェアが「こちら側」に与える具体的な作用としては、グラフィック描画、サウンド再生、デバイスの振動、紙への印刷などが挙げられます。
この、お互いに作用しあっている様子のことを「インタラクション」といい、日本語では相互作用と呼ぶこともあります。
Human-Computer Interaction
インタラクションの様子をもう少し掘り下げてみると、Macを例にすると上の図のように捉えられるかもしれません。左側にヒトのドメインがあり、中央にハードウェア(Mac)、右側にソフトウェア(macOS、アプリケーション等)があります。細かく区切られた一番左側はヒトの精神で、ここがユーザーのメンタルモデルが構築される領域であると捉えてください。
ヒトは身体デバイスとして感覚器や身体能力を発動するデバイスを生まれた時から搭載しています。視聴覚や触覚はセンスと呼ばれるデバイスで、外部からの情報を受信するために活用されます。また、指での操作や発声能力は何かしらのものに対して作用を与えるための出力デバイスとして活用されます。
中央から右側にかけてはコンピューターの領域で、Macのインターフェイスは人間の持つデバイスとそれぞれ磁石のように関わってきます。身体能力がプラスの磁極を持つデバイスだとしたら、Macの周辺機器(入力)はマイナスの磁極を持つデバイスです。Macからするとキーボードやマウスは入力デバイスで、ヒトの出力デバイスと結合してはじめて対話のための経路が開通すると言う具合です。同じようにMacのディスプレイやスピーカーはヒトの感覚器に結合するように設計されています。
ソフトウェアのアーキテクチャをいくつかに分解してみると、macOSのレイヤーでは様々なフレームワークが見えてきます。実際には図示したよりも多くのソフトウェアが関わってくるのですが、ここではわかりやすい例として代表的なフレームワークを挙げています。CocoaやQuartz、Darwinなどの基盤となるソフトウェアは、アプリケーションとヒト(ユーザー)をつなぐためのソフトウェア群です。
アプリケーションのレイヤーではよく知られたGUI要素を挙げています。ウインドウやメニュー、カーソルは代表的なコンポーネントで、いずれも入出力に関わる性質を持っています。
サウンド、ビデオ、イメージ、テキストなどはコンピューターからの出力に特化した要素です。これらはGUI部品というより、情報コンテンツとして扱われることの方が多いかと思います。
キーイベント、マウスイベント、音声入力はユーザーからの入力情報を抽象化したオブジェクトです。これらは目に見える形では直接現れず、GUIの裏側の処理で活用されています。
そして一番右側のオブジェクトが並ぶ領域ですが、これはヒトでもコンピューターでもない、より観念的な領域を表したものです。私たちはインタラクションの過程でこの観念の世界にあるであろう「オブジェクト」に触れようとするわけですが、直接的には触れられないため、ソフトウェアが表現したオブジェクトの姿がハードウェアを通じてヒトの精神の中に映り、それを内在化させることによってあたかも「オブジェクトに触れている」感覚を味わえるのだと思います。この感覚をうまくデザインすることができれば、きっと「直感的」だとか「なめらか」といった言葉で形容されるような親しみやすいGUIを実現できるのではないかと考えています。
オブジェクト指向UI
ここで、ユーザー(ヒト)とオブジェクトの関係をオブジェクト指向設計的に捉えてみたいと思います。ユーザーから対象となるオブジェクトに作用を与える様子は、おそらく次の図のように表せます。もしも対象が複数あったときにはオブジェクトの姿や性質はそれぞれ微妙に異なっているのかもしれません。この例ではどの図形も腕の数が異なります。
あくまで一例ですが、「尖った腕を持つ」という性質に着目すると、いずれも三角形の親戚として表現することができそうです。今回は三角形をスーパークラスとして置き、図形はぞれぞれ三角形の性質を継承するという関係で表してみました。図形は見た目の性質だけではなく、振る舞い方も抽象化されているものとして考えます。ユーザーはオブジェクトに対して何らかの操作を行いますが、例えばコピー操作として“⌘C”を送信したいユースケースを検討する場合、その挙動の定義は個々のオブジェクトにではなく三角形の単位で記述した方が良さそうです。
このモデルは「ユーザーは三角形の仲間の図形をコピーするために、対象に“⌘C”のメッセージを送る」というふうに読むことができます。
(あくまで例として表しているので、実際にはこのような単純な構造ではありません。)
ここでもしも図形のうちの一つが円だとしたら、これは三角形の仲間と言うのは難しくなります。今回は「尖った腕を持つ」という共通点で抽象化していたので、腕が無い図形は仲間はずれであるという判断を一旦します。そうすると、円には三角形が実装している性質「⌘Cを受け付ける挙動」が継承されないので、ここに⌘Cメッセージを送っても受け付けることができなくなります。
オブジェクト指向にはポリモーフィズムという設計の考え方がありますが、まさに今の例で挙げたコピー操作のようなGUIの操作設計においてもオブジェクト指向の設計論を活用できます。
具体例としてFinderのメニュー操作を考えてみます。Finderではまず操作対象オブジェクトを選んでから、次にメニューからコマンドを選ぶことで、任意の操作をオブジェクトに対して実行することができます。操作対象オブジェクトにはアプリケーションやフォルダなどが挙げられますが、デスクトップに現れる要素の共通点に着目すると、フォルダもアプリケーションもファイルとしての性質を持つことが考えられるので、それらの共通する性質や挙動(受け付ける操作)を<<File>>という抽象単位でまとめることが検討できます。
Finderではファイルの仲間ではない要素も現れることがあります。ウインドウはどうみてもファイルではないし、その振る舞い方もファイルとは全く異なる性質を示しますから、このウインドウを選択した状態で⌘Cを実行しようとしても、そのコマンドは弾かれてしまいます。
このように、GUIの要素の形と挙動、操作方法を抽象化してまとめることで、GUIの表現と入力方法を論理的に設計することができます。
入力方法のデザイン原則としてmacOS HIGの “Mouse and Trackpad” の章には上の図のような内容が書かれています。標準部品を使用する、意味に基づく、一般的なジェスチャーに対応する、あるいはそれらを別の意味で再定義しない、といった事柄です。私はこれを読んだとき、実はどれも同じことを言っているのではないかと思いました。要するにこれらは入力方法の「一貫性」について述べていて、それを確保するための考え方をデザインの観点として取り入れてUIを設計しましょう、ということなのだろうと思います。
入力方法の組み立て方
次は入力方法をどのように設計すると良いのかを考えてみます。次の図はAbout Faceに現れる図を私が解釈し直して清書したものです。書ではユーザーインターフェイスの「イディオム」の構築方法のくだりで紹介されています。それはUIの慣用的な表現や操作方法は基本的な要素が組み合わさって積まれたもので、基本単位から作り上げてゆくのが良いとされています。
イディオムが多くのUIパターンとして現れるのであれば、それらを構成する最小単位はほとんど数種類の要素の組み合わせのように思えます。
ここで、Keynote.app などでみられるテキストレイヤー要素の操作方法をUI設計観点から考えてみましょう。マウスからテキストレイヤーに与えられる作用と対応する挙動にはいくつかの種類があるものと考えられますが、それらはテキストレイヤーの「状態」によって変化します。具体例を次に示します。
未選択状態から、クリックでレイヤーを選択状態にする。
レイヤー選択状態から、クリックでテキストを編集状態にする。
テキスト編集状態から、ダブルクリックでテキスト選択状態にする。
このように、「同じ」クリックという入力操作でもテキストレイヤー側の状態によって挙動が変化することがわかります。Macの操作は突き詰めるとほぼマウスとキーボードのみですから、GUIのイディオムを構成する入力と出力に関わる要素は、数種類の入力操作、パターン化された画面表現、そして画面要素が備えるさまざまな挙動というふうに区別することができるのではないでしょうか。そして出力(挙動)よりも入力(操作)の種類の方が少ないであろうことが見て取れます。
Responder Chainによるイベント処理
インタラクションの技術を考えてみましょう。GUIの要素は入れ子構造をとりながら「誰」が入力イベントを受け取るのかを決定するプロセスを踏みます。これは複雑な入れ子構造の中でも適切な相手にイベントを届けるための仕組みです。このイベントディスパッチのロジックはChain of Responsibilityの仕組みによって実現されていますが、CocoaではResponder Chainの名前で知られています。
Responder Chainは簡単に言ってしまうと、「メッセージをたらい回し」するための仕組みです。まず入れ子の一番最下層(クリックされたビュー、あるいは選択された状態のビュー)にメッセージが投下され、もしそのイベントを受け取るならそこで処理を終える、受け取らないなら次のレスポンダーにパスしていくような「たらい回し」をひたすらに行います。この仕組みではレスポンダーであるビュー自身が受け取るメッセージに関する責任の所在を自ら決めることができます。
Responder Chainの仕組みがあるおかげで複雑なビュー階層の中でもイベントがうまくディスパッチされる世界が出来上がります。これはmacOSのGUIの根幹を支える技術の一つとなっています。
ちなみに、CocoaのResponder Chainの仕組みはもちろんNeXTSTEPに由来するものですが、次の記事によると、かつてのNeXTSTEPの実装がGoFのChain of Responsibilityパターンに影響を与えたのだそうです。CocoaのResponder Chainに触れることは、Chain of Responsibilityパターンの元祖に触れるようなことなのかもしれません。
First Responder
フォーカスが当たったビューはFirst Responderと呼ばれる特殊なマーカーのついたビューとなり、メッセージが投下されることが明示されます。チェインの中の末端が常にFirst Responderとなるわけではなく、いずれのビュー(レスポンダー)もFirst Responderになる可能性は持っています。ただ、同時に2つ以上のFirst Responderは現れないようになっていて、ビュー階層のうちのどれかひとつがFirst Responderの役割を担っています。当然チェインの中途にあるオブジェクトがそうである場合もあり得ます。
仮にテキストビュー内のテキストが選択状態にあったとしても、ウインドウを閉じるための⌘Wショートカットを受け付けるための挙動がテキストビューには実装されていないため、このイベントはテキスト系では処理できないものとして次のレスポンダーにパスします。基本的に階層の奥から上(外側)に向かってパスされてゆくので、最終的にはウインドウ、もしくはアプリケーションの層まで到達することもあります。このたらい回しの果てにはウインドウの存在があるので、ここでようやく「ウインドウを閉じる」という処理が実行されるはずです。
マウスドラッグ処理の設計観点
マウスドラッグに関する処理を考えてみましょう。例えば上の図のような「マウスドラッグでオブジェクトを選択する」機能の設計では、一つ一つの動作ごとにイベントを分解していって、マウスダウン/マウスアップやドラッグの単位でオブジェクトの振る舞い方を検討します。
まず、カンバスのどこかしらでマウスのボタンが押下されたことを検知します。今回はボタンの種類を区別しないものとします。AppKitでは“mouseDown(with:)”イベントとして扱われます。普通「クリック」という言葉を聞くとそれは単一のイベントのように考えてしまいがちですが、これを分解すると「マウスのボタンを押し込む」「押し込んだボタンを離す」のオン/オフで区別することができますから、設計では「マウスダウン」「マウスアップ」といった単位で考えるようにします。
操作ではほぼ同時に“mouseDragged(with:)”イベントが発せられることがあります。正確にはボタンが押下された状態でカーソルが1pt動く度にこれが通知されます。ドラッグ処理を実装するにはここで行うのが良いでしょう。今回は一時的なモードを表したいので、mouseDown(with:)もしくはmouseDragged(with:)でドラッグによる範囲選択のモードに入ったことを明示しておきます。
カンバス上に選択範囲を示す矩形を描画します。mouseDown(with:)の座標を保持しておき、mouseDragged(with:)が起こる毎にその瞬間の座標との間に半透明の矩形を描画します。これを実行すると矩形はマウスドラッグに追従するように描画されるでしょう。
ここで、矩形の範囲内にわずかでもオブジェクトが交差したならばそのオブジェクトをハイライト(選択)状態に変化させるという処理を行います。矩形のフレーム値とオブジェクトのフレーム値をカンバスの座標系で交差を比較し、結果がtrueならばオブジェクトのハイライトに反映する、falseならハイライトを解除する、という具合です。これらの処理もmouseDragged(with:)のイベントで実行されるので、ユーザーの視界からはマウスに連動してほぼリアルタイムに画面に描画されているように映るはずです。
ユーザーがマウスのボタンを離すと“mouseUp(with:)”が発せられます。ここでようやく範囲選択モードが解除されます。ユーザーからするとマウスのボタンを押し込んだ、ドラッグした、ボタンから指を離した、の3ステップ程度のように感じられますが、以上の例からGUIの実装としてはもっと複雑な処理が行われていることが想像できるのではないでしょうか。
同じような考え方で、今度はドラッグ&ドロップによるオブジェクトの移動処理を検討してみましょう。
まずオブジェクトの上でmouseDown(with:)が起こったことを検知します。オブジェクトはクリックで選択状態にすることもできるので、この時点でハイライトにしてしまいましょう。クリックというとマウスボタンの押し込みと離す動作のオン/オフで構成されますが、ほとんどの場面では「クリックで選択」を「マウスダウンで選択」と解釈してしまっても問題ありません。mouseUp(with:)の方でオブジェクトを選択する処理にしてしまうと、わずかであってもその時差が選択に関わる処理の「もっさり感」を生じさせてしまい、なんだかうまく操作しにくいような印象を与えてしまいます。またドラッグ移動のことも考えると選択状態(ハイライト)になってからマウスに追随する方が正しい振る舞い方のようにも思えます。
移動処理そのものはとてもシンプルで、mouseDragged(with:)ごとに座標をオブジェクトに反映させるだけです。ただマウスカーソルの座標を直接オブジェクトの座標にしてしまうと位置がずれてしまうので、「オブジェクトのどこを掴んでいるか」をよく考えながら座標値を計算すると良いでしょう。
あとはmouseUp(with:)が来るまでこの処理を継続し、ドロップが行われたらドラッグによる移動モードを終了します。
ドラッグの“はずみ”を抑える
ここで、ドラッグ時のユーザビリティをもう少し深く考えてみようと思います。今の説明のまま実装してしまうと、オブジェクトはマウスカーソルが1ptでも動けばドラッグで移動できてしまうことになるので、ふとした誤操作でもオブジェクトがわずかに移動してしまうようなことが考えられます。人間のマウスの操作は割と大雑把なので、これはあまりにも敏感すぎてなかなか操作しづらいのではないかと思われます。
その対策として、mouseDragged(with:)のイベントの中でドラッグ移動を開始する際の閾値を設けてみようと思います。マウスダウンが発生した座標からおよそ6ptの距離をマウスカーソルが移動したら、そこでようやくドラッグ開始とするものです。これにより6pt未満の範囲のドラッグは実質無視されるようになるので、人間の誤操作によるドラッグの「はずみ」を抑えることができます。
この6ptという数値は経験則から導き出したもので明確な根拠はありませんが、ほとんどの場面で有効であると思います。
ドラッグの中断
最後に、ドラッグを中断したくなった場合のことも考慮しておきましょう。Cocoaアプリケーションの一般的な挙動としては、何かしらの操作中に“⌘.”やエスケープキーを入力することでその操作を中止することができるようにデザインされています。これはほとんどシステム全体で共通の振る舞い方となりますが、独自のインタラクションを設計しているならば当然実装しておかないと有効にはなりません。
これの実装方法は単純で、NSResponder(またはビュー)の方で“cancelOperation(_:)”メソッドを実装しておくだけです。今回の例ですとドラッグによる移動モードを中断して、ドラッグ開始前の状態に戻す内容をここで実行するようにしておけば良さそうです。
macOSネイティブインターフェイス“らしさ”をデザインする
macOSネイティブインターフェイスの「らしさ」を考えたとき、その実現方法はCocoaのアーキテクチャレベルから構成される逆三角形モデルをイメージすることができます。これはイディオムの逆三角形と同じ考え方であると思います。入力方法の原始的な定義から複合的なクリックやドラッグ&ドロップといった操作体系が出来上がり、オブジェクトの範囲選択や移動処理といったシステムワイドの共通のイディオムが完成します。
逆三角形モデルが表すように、ユーザーからはなるべく簡単で少ない種類の操作方法のみでGUIに作用を与えられるようにし、一方でシステムからは多彩な表現や大きな効果がフィードバックされるような構造を目指すのが理想であると思います。「なるべく簡単に、最大限の効果を得る」がGUI設計におけるひとつの原則と言えるのかもしれません。
macOSネイティブインターフェイスの「らしさ」はアーキテクチャから一貫してデザインされていますから、macOSアプリケーションの設計ではきちんとCocoaの仕組みを理解して正しく実装すること、HIGに書かれた設計思想をくみとってデザインに反映させること。やはりこれが「らしさ」のデザインのための基本姿勢となるのではないでしょうか。
この記事が気に入ったらサポートをしてみませんか?