🔲

我が為に図を描くツールを作る ~Grid snap編~

2024/09/19に公開

テキストエディタ、ノードエディタ、お絵描きツールと並んで誰でも一度は自作したことがあるでお馴染みの作図ツールに関する話題です。テーマが多岐に渡りすぎるので今回はその中でもgrid snap機能に絞って注目していきたいと思います。

満足できなかったケーススタディ

まずはケーススタディとして有名ツールを観察していきます。当然ですが満足できるorできないの観点は完全に主観なことをご留意ください。どのツールにもそれぞれの思想があり、ことgrid snapにおいてはその思想がやや合っていないのかもしれない程度の言ってしまえばいちゃもんです。

Case: draw.io

フリー作図ツールの王とも言える定番ツールです。作図ツールを自作するにあたり「draw.ioでいいじゃん」問題を避けて通ることはできず、如何に「draw.ioでは満足できない」理由を見つけ出して自らを鼓舞するかは非常に重要です。全力で満足できない点を探し出していきましょう。ちなみにダークモード解除の仕方を調べても分からなかった程度の習熟度なので様々な機能を見逃している可能性は高いです。

Grid間隔は50、矩形のサイズは50x200を出発点としています。

素直なリサイズの挙動をしているようです。しかしリサイズが離散的になってしまうのは満足できないポイントです。gridの交点に確実にsnapしたいという思想も理解できますが、x軸方向のgrid上に乗っていればy軸方向のgridから外れてもよい柔軟さを欲張りたいところです。

リサイズと同じく、図形の移動でも離散的な移動となってしまうのは満足できないポイントです。

作図ツール頻出の状況である回転した図形のリサイズも観察してみます。

お分かりいただけたでしょうか、これは見逃せません。図形のサイズをgrid間隔単位でリサイズしているしているだけ、つまりgrid線に全くsnapされていません。少なくとも図形のリサイズにおいて参照されているのはgrid間隔の値だけであり、画面上のgrid線はもはや飾りです。grid snap警察である私はこれではとても満足できません。

Case: Miro

作図ツールというよりホワイトボードで売っていそうなのでややフェアではないのを承知の上で、ケーススタディ水増しのために紹介します。

Grid snap警察の方々ならばリサイズ開始時の一瞬をきっと見逃さなかったはずです。水平方向にリサイズしているはずが、gridに沿うよう垂直方向にも勝手にリサイズされています。憶測にはなりますがGrid snap機能の実装上の都合が操作者の意図よりも優先されてしまっていそうです。
図形や線の頂点移動時の挙動はdraw.ioと同じく離散的なgrid交点への強制snapなので、ここも満足できないポイントです。

次に定番の回転した状態です。

図形がgrid軸に沿っていない場合は図形リサイズ時にgrid snapが発動しませんし、そもそもリサイズの挙動が怪しくてよく分かりません。ホワイトボードツールと作図ツールを分ける大きな要素の1つは図形の回転に対する真剣さにあると個人的に考えており、この点でもやはりMiroはホワイトボードツールであると言えそうです。

満足できるgrid snapの姿

Grid snapの理想とする挙動を仕様として書き下すとどうしても複雑になりがちですが、その大元となる思想はいたってシンプルです。

  • 移動点の近くにgridがあれば吸い付く

これです。もう少し具体的な動きを並べると次のようになります。

  • 移動点の近くにgridがなければsnapしない
  • 両軸のgrid線それぞれがsnap対象となる
  • Grid線が制約として他のsnapと協働する

上の2つはイメージしやすいと思います。gridの交点に必ずsnapする強いgridではなく、交点にも線上にもsnapするし距離が遠ければsnapもしないしな弱いgridです。

弱いgrid

満足できないgridによくある特徴がsnapが強力すぎるという点です。snap候補への吸い込む範囲が広いというより、そもそもsnap候補のどれかへ必ずsnapするという挙動になっていることが多々あります。スクリューパイルドライバー(以下、SPDと表記)の吸い込み範囲が対戦ステージの横幅より広いのかそもそも確定ヒットなのかは実装次第ですが結果は同じで逃げ場はないのでどちらでもよいです。

Gridの交点に揃えることを強制したいという厳密なシーンでは確かにこの挙動が便利なのかもしれません。しかし日常的な図を作るというシーンにおいて、少なくとも私の観測範囲では、そこまでの厳密さを求めることは稀ですし、もし必要となってもgridだけで達成可能な厳密さも高が知れています。Gridのon/offを手軽に切り替える機能があったとしても、強いgrid環境ではonとoffのギャップが広すぎて融通が効きません。

この強いgridのonとoffの間のどこかに位置するのが弱いgridです。吸い込み範囲が適度に調整されたSPDです。依然としてその吸い込み範囲と火力に緊急動画が続出する可能性はありますが強すぎた時とは異なりパラメータ調整という余地があるのできっと大丈夫です。

弱いgridの挙動例はこちらです。grid交点 > grid線の順で優先してsnapし、grid線が近くになければsnapは行いません。

弱いgridを採用することで強いgridが持っていたパワーは失われますが、強いgridで行えていたsnap結果は容易に再現可能です。選択肢を絞ってパワーを取るか、パワーを弱めて選択肢を残すか。好みが分かれる場面かもしれませんが、grid snap機能においては私は後者を選びました。SPDであれば迷わず前者を選びます。

制約としてのgrid

弱いgridを前提とすることで、3つ目の挙動「Grid線が制約として他のsnapと協働する」が輝きます。線の頂点を移動するケースで実際の動きを見るとおそらくイメージがつきやすいです。

まずgrid無効状態での挙動がこちらです。頂点移動時に、元々の線分が作る直線に沿うようなsnap機能が存在するとします。

ここでgridを有効にすると次のような動作となります。上で紹介した直線snapを満たしたまま、近くにgrid線があればそこにsnapします。

この動きをより一般的な言葉に直すと次のようなステップとなります。

  1. Grid以外のsnapを行う
  2. ステップ1で行ったsnapの結果に自由度が残っていれば、その自由度の範囲内でgrid snapを行う

上で紹介した線の頂点移動ケースでは、ステップ1として元々の線分が作る直線へのsnapを行っています。このsnapではその直線上に頂点の移動が束縛されただけでまだその直線上を移動する自由度が残っています。つまりその直線とgrid線が作る交点にsnapすることで、直線へのsnapとgridへのsnapという2種類のsnapが同時に成立します。文字にするとややこしいですが画面上で起きていることは一目瞭然だと思われるので今一度実際の動きをご確認ください。

強いgridを前提とする世界ではそもそもgrid snapを行った時点で結果が最寄りの交点に確定してしまうため他のsnapと協働する余地が残りません。この柔軟性を確保するという点でも弱いgridを採用するメリットが大きくなります。

満足できるようになったケーススタディ

grid軸に沿っていないリサイズであっても、リサイズの特徴点がgrid線にsnapしています。各特徴点それぞれがsnapできるというのも密かな頑張りポイントです。

改めて満足できない版のケーススタディを振り返ってみると、強いgridの世界ではgrid軸に沿ってないリサイズがsnap可能なgrid交点が非常に限定的です。この状況を素直に受け入れるとリサイズが機能しなくなってしまうためにgrid線を無視するようなsnapをワークアラウンドとして採用しているのかもしれません。

おわり

強いgridの実装は弱いgridより大抵はシンプルで取り入れやすいです。なにせ必ずgrid交点にsnapするので実質座標を丸めるだけですし、他のsnap機能の存在も無視できます。あぁしかしここは我が為の個人開発、目先の実装コストよりも作者の満足が当然優先されます。お手持ちの自作作図ツールのコードの複雑さをさらに加速させてくれること間違いなしなので、ぜひとも弱いgridを取り入れてみてください。

今更ですがタイトルにも掲げていた我が為に作っているツールはこちらです。今回紹介した満足版grid snapも搭載されているので満足できるかお試しください。できなかったらすみません、もっと作り込みます、あるいはそのときこそ新たな自作チャンスです。

https://no-mans-folly.com/

Discussion