RandomThoughts

RandomThoughts

ポータブルなUI

Contents:
  1. コンテキスト
  2. UIプログラムを考える
  3. 共通部分を抜き出す(のでは何が駄目か)
  4. UIは結構面倒くさい(からさらに面倒にはしたくない)
  5. UI用の言語を考えてみる
  6. 必要なものを考える
  7. ユーザーとのやり取りを考える
  8. なんかIR的なものからどうにかならんか?
  9. なるべくネイティブ開発に近い開発
  10. ツリーとハンドラ
  11. 2つの道具が必要

技術的なメモ

なぜUIはポータブルにならないのか、どうにか出来ないか?みたいな事を考えてみたい。 この問題は20年前くらいに考えた時には良い答えが見つからなかったが、今でも見つからないだろうか?

コンテキスト

Qtのコードがあって、これをiOSで動かしたい、みたいな話はよく見かけるが、全然うまく行かない。なんでこんなうまく行かないのか? そもそもUIって、どのプラットフォームでもだいたい同じような事を書きたいのだから、同じコードに出来ないものなんだろうか?

UIプログラムを考える

例えばリソースを書いてメニューを追加して、そのメニューが選ばれたらなにかしたい。 それはだいたいどの環境でも同じような仕組みがある。

ただグレーにしたりチェックにしたり階層化したり、と細々な違いがあって、それらのやり方は結構違う。

ダイアログにボタンとテキストエリアとカスタムなカラーピッカーを置きたいとする。 レイアウトの方法はプラットフォームで全然違う。

カラーピッカーの所はカスタムコントロールの作り方で、これまた全然違う。 一方で画面に描きたいもののロジックや、押された座標に対してやりたい事はどのプラットフォームでも同じだ。

モーダルダイアログの扱いも環境によって大きく違うし、結果の受け取り方もだいぶ違う。

一方でやりたい事はどの環境でもだいたい一緒だ。

なんか明らかに共通化出来ると感じるのに、全然うまく行かない。

共通部分を抜き出す(のでは何が駄目か)

割と簡単に思いつく事として、UIのプラットフォームに依存しない部分をポータブルに書いておいてそれを共有して、 各プラットフォームではこれを使えばいい。これはまぁみんなやってる。

これを限界までUI側まで共通化していけば、ほとんど共通になるのではないか?という考えはどうだろう?

この考えが意外とうまく行かないのは、ユーザーとのやりとりのロジックというのは、UIと深く結びつきがちで、 これを無理に引き離そうとするとコードが面倒になりすぎてしまう。 そしてやりとりのロジックを含まない所だけ共通化、というのは、その範囲に含まないものが結構多くなってしまう。 これを各環境ごとに書くのは嫌だ。

UIは結構面倒くさい(からさらに面倒にはしたくない)

UIのコードというのはそれ自体結構面倒くさい。だからさらにそれを面倒にするような事はあまりしたくない。 例えば共通部分になにかを書いて、それと同じような事を各環境側にも書かなきゃいけない、みたいなのはきっとうまく行かない。

普通に特定のプラットフォームでUIを開発するのと同程度の面倒さで開発出来ないと駄目な気はする。

例えばなんか共通の抽象クラスみたいなのでコードを書いて各環境でその実装クラスを書かなきゃいけない、 みたいなのは、新たなものを追加するのがかったるいのでうまく行かないような気はする。

UI用の言語を考えてみる

言語でうまくいくとは思わないのだが、抽象度を上げて書けば良いような気はなんとなくするので、UI用言語みたいなのを考えてみたい気もする。 なんか駄目そうな路線ではあるが、なんで駄目そうに感じるのかも言語化しておく意義はあるかもしれない。

XMLのようなものを書いてそれからUIを生成する、みたいなのは既にあるが、そういうのでは全然駄目な複雑なUIを書きたい。 だから宣言的なものではなくて、もっと普通にロジックを書きたい。 interface builderくらいじゃ全然駄目なんだよな。

FlutterとかReactNativeなどは、自分らの用途だと、普通にUIを書くのとは遠いのがいまいちに思う。 普通にC++でネイティブの環境でUIを書いているように書きたいと思う。 だからUI用の言語というアイデアは駄目そうに感じるんだろうな。

話を戻して。 何かUIとそのロジックを記述する理想的な言語があったとして、そこからQtとかUIKitのコードやリソースを生成すると考えると、 その理想的な言語はどういう感じになるだろうか?

まずUIと絡み合ったロジックはC++で書きたい気がする。 でもC++のテンプレートとかでどうにかなる気も、どうにかしたい気もあまりしない。 なんかそういうのじゃ駄目だよなぁ。

Qtのプログラミングくらいでいいんだが、もうちょっと抽象化されてて欲しいんだよな。 そして抽象化されてほしくない所はC++で直接書きたい。

流れるインターフェースくらいでそれっぽい記述をする、とかにならないかなぁ? それではリソースが生成されないから駄目だな。 なんかリソースも生成されつつ、UIのコードも書けつつ、オーナードロー的な部分やマウスのイベントなどが抽象化されてて欲しいんだよな。

しかもダイアログなどでは、前回開いた時の位置やサイズを覚えておいて欲しい時とそうでない時とかある訳だ。 そういうものも何か指定したいのだが、いちいちそういうやり方を覚え直すのも大変だよなぁ。

ロジックをC++で書きたい、というのがいろいろ厳しいよなぁ。

うーん、言語を考えてみようとしたがうまく考えられない。

必要なものを考える

  • カスタムコントロール
    • Draw
    • イベント
    • 外部とのやりとり
  • レイアウト
    • Vertical, Horizontal
  • メニュー
  • dockするペイン的なもの
  • タイトルバー
  • ステータスバー
  • ボタン、リストなどの普通のコントロール

もっといろいろあるな。やはり膨大だ。なんか最大公約数的な共通の何かを作って各環境で実装するみたいなのが出来る気はしない。 なんかそういうのではなくて、基礎となるものがあったらそこから先は作っていきながら広めていける何かになってないと駄目だよなぁ。

ユーザーとのやり取りを考える

UIは何が面倒ってUIとロジックが結びついている所だ。

  • ダイアログでドロップダウンなどがあるのを出す
  • ドロップダウンの選ばれたものに応じてdisableになったり他のリストの中身が変わったりする
  • ユーザーが選んだものに応じて次の出すUIが違ったりする
  • ユーザーとは関係ない事情で出すものが違ったりもする(ファイルに保存しようとしたらファイルが既にあった、とか)

こうしたロジックはかなり複雑なので、やはりプログラムである必要がある。

ボタンが押された時に実行したい処理などはだいたい同じなのだが、他のコントロールの状態を変えたい、とかだとプラットフォーム依存になってしまう。

なんかIR的なものからどうにかならんか?

イマドキっぽい考え方だと、IR的なものから生成出来ないかしら? 流れるインターフェース的なのでIR的なツリーを生成して、 これを元にC++のコードとリソースが生成される。

これはC++の中で行われて、このIR的な何かとのやり取りが統一的な形で書ければ、徐々に置き換えていく感じに出来ないものか?

でも例えばQtの場合、ヘッダに特殊なdefineを置いてmocに掛けたりする必要があって、それはCMakeとかqmakeに影響を与える。 だからプリプロセスで.hや.cppファイルを生成するのは避けられない。 XcodeでもやはりXcodeのプロジェクトに生成される方が取り回しはいいから避けられないよなぁ。

cppで書くけれどプリプロセス、かぁ。なかなか地獄だな。

なるべくネイティブ開発に近い開発

変に遠い抽象がいろいろ挟まるのではなく、むしろネイティブでそのまま開発するのになるべく近い開発にしたい。

近いというのは幾つかの意味があるが、まずこのシステムでUIを書くと、ネイティブのどういうコードが動くかがすぐに分かるのが望ましい。 そうすれば、ネイティブでUI開発の経験がありさえすれば、すぐに理解して使っていけるようになるから学習コストが低くて済む。 また、mocやqmakeなどの面倒な仕組みにそのまま乗っかれるためにも、実際に手で同じようなのを書いたらこうなる、 というのがわかりやすいものになっていて欲しい。何をmocに加えたらいいのか、などが、見たらすぐに明らかになっていて欲しい。

理想的にはネイティブのコントロールを隠すのではなくて、ネイティブのコントロールの開発をする便利ライブラリのように使えるものが良い。 ネイティブのコントロールを開発するのにそれを使っていると、意外とネイティブ特有の部分は多くなくてそのライブラリ内でいろいろ書けるようになって、 その部分は他のプラットフォームにも持っていけるので共通部分が増えていく、みたいなのがいい気がする。 レイアウトのリソースだとかそういうものをまずはそのままプラットフォームのものを使いつつこのシステムを部分的に採用していって、 だんだんとプラットフォーム固有の部分を減らしていって共有部分を増やしていきたい。

また、いざとなったらプラットフォームに降りていけるように、間が薄いというのも重要に思う。 最初から全部をポータブルに書くのはポータブルな層が不足していると出来ない事が出てきて困るので、 とりあえず何か分からなかったらプラットフォームのコントロールに降りて書けば良い、というものになっていて欲しい。

ツリーとハンドラ

なんかポータブルなツリーのデータ構造を作り、それに対応したネイティブのコントロールを作る。 そしてイベントハンドラはツリーにぶら下げたものを使う感じにする、という事でどうだろう? その他なるべく多くの操作をツリーの方経由でやるようにする、みたいな。

コントロール単位で採用出来るようになっていれば、順番に試していける気がするし、まずはそのコントロールで使っているものだけを対応すれば良い。

2つの道具が必要

必要なのは以下の2つの道具に思う。

  1. ネイティブのウィジェットを作るのに共通のコードが使える仕組み
  2. 作ったネイティブのウィジェットを操作するコードを共通にする仕組み

そしてこの両者は、部分的な採用が出来るようなものであって欲しい。

基本的にはネイティブのアプリを書く。 けれどそれを書く時に使える道具がいろいろとある。 そして用意したウィジェットに対してのロジックも共通化したければ出来る。 しなくて良いようなのはプラットフォームごとに書いても良い。 ロジックとUIのやりとりが複雑な所は共通化して使えるようにしたい。

逆にこの2つが達成出来たら、あとは既存のコードを順番に置き換えていけば良い。