はじめに
Universal Windows Platform(UWP)でUI.Text.Coreを使って日本語入力をする方法を先週Qiitaに投稿しました。
[UWP]さよならTSF、Windows.UI.Text.Coreで簡単日本語入力
Qiitaもいいんですけどテーマに沿って複数の記事を書く場合はブログの方が向いてるので、これからはブログに書いていきたいと思います。
日本語入力の応用として今回は簡単なテキストエディタを自作しました。
サンプルプログラムはGitHubに上げています。
https://github.com/teatime77/UI.Text.Core-Sample
この中のTestプロジェクトはUI.Text.Coreの動作確認用で、TextEditorプロジェクトがテキストエディタです。
データ構造
アプリ内のテキストは以下のように1文字ごとの情報をリストに持っています。行の終わりは改行文字\nで指定します。
またテキストの選択範囲として選択開始位置と選択終了位置を持っています。
各文字の情報は以下の構造体に保存します。
struct TChar {
char Chr; // ユニコード文字
UnderlineType Underline; // 下線の型
}
下線の型(Underline)はかな漢字変換の途中の状態を示すのに使います。
今後、文字の色などの属性を追加する場合はこの構造体に属性のフィールドを追加していくことになります。
描画
描画はWin2Dを使っています。
Win2DはUWPのAPIではなくNuGetでパッケージを取得する必要がありますが、作者はマイクロソフトなので安心して使っていいと思います。
Win2DはGPUでレンダリングするので1文字入力するたびに画面全体を再描画します。
GPUによる描画が高速のせいか入力していてもわたしは特にストレスは感じませんでした。
テキストの編集
テキストの編集は基本的には文字のリストと選択範囲を変更する作業になります。
以下のように「どうぞ、」の文字が選択されて時に、いろいろなキーを押してどのように変化するかを見ていきます。
Deleteキー
「どうぞ、」の文字がかなくなり、選択開始位置と選択終了位置は「よ」の位置に移動します。
「よ」の手前にIカーソルが表示されます。
文字Aのキー
「どうぞ、」の文字がかなくなり、「よ」の手前に「A」の文字が挿入されます。
Enterキー
「どうぞ、」の文字がかなくなり、「よ」の手前に改行文字\nが挿入されます。
Shift+右矢印(→)キー
選択終了位置が1つ右に移動し、「よ」の文字が反転します。
マウスによる操作
Visual Studioなどのテキストエディタではマウスで以下の操作ができます。
- クリックして文字の挿入位置を指定する。
- ドラッグして選択範囲を指定する。
- ダブルクリックで単語を選択する。
- 選択されたテキストをドラッグ&ドロップで別の位置に移動/コピーする。
キーボードによる操作は一瞬で処理が終わりますが、マウスによる一連の操作ではアプリ内部の状態遷移を考える必要があります。
状態遷移を実装するにはコルーチンが便利なのでサンプルプログラムでもコルーチンを使っています。
コルーチンを使ったマウスのイベントハンドラについては別の記事で詳しく書く予定です。
アンドゥ(元に戻す)とリドゥ(やり直し)
テキストを変更したときに、どのようにすれば元に戻せるでしょうか?
以下のように6文字目から「どうぞ、」が選択されてる時に、Ctrl+Vで「これからも」の文字列を貼り付けた例で考えます。
内部の動作は2段階になります。
まず6文字目から「どうぞ、」を削除します。
次に6文字目から「これからも」を挿入します。
以上の2段階の操作を逆向きに行うと以下のようになります。
まず6文字目から「これからも」の5文字を削除します。
次に6文字目から「どうぞ、」を挿入します。
上記のアンドゥの動作で使った情報は変更開始位置の6文字目と削除した文字列の「どうぞ、」と新しく挿入した文字列の長さの5文字です。
アンドゥに必要な変更情報は変更開始位置と削除した文字列と新しく挿入した文字列の長さということになります。
そこで、テキストの変更情報を以下のオブジェクトに保存します。
class TDiff {
int DiffPos; // 変更開始位置
TChar[] RemovedChars; // 削除した文字列
int InsertedCount; // 新たに挿入した文字列の長さ
}
テキストを変更するときは変更情報をTdiffオブジェクトに入れて、アンドゥのスタックにプッシュします。
アンドゥの時はアンドゥのスタックから変更情報をポップしてテキストを元に戻します。
この時、テキストを元に戻したときの変更情報をリドゥのスタックにプッシュします。
リドゥの時は上記と全く逆でリドゥのスタックから変更情報をポップしてテキストを変更します。
それと同時に、今回の変更情報をアンドゥのスタックにプッシュします。
まとめ
以上、駆け足でしたがテキストエディタの作り方を見てきました。
テキストエディタにはたくさんのキー操作がありますが、一つ一つの動作は10行くらいの単純な処理なので、コツコツとコードを書いていけば作ることができます。
サンプルプログラムはC#の行数で1500行くらいですが、コメント行や空行を除くと1000行くらいです。
テキストエディタとして足りない機能はまだありますが、実用的なテキストエディタの雛形になればと思っています。
技術的な文章を書くのは慣れていないので、読みづらかったかもしれませんがここまで読んでいただきありがとうございます。
間違いや分かりづらい点、バグなどがありましたらコメントをお願いします。
win2dはdirectwriteの薄いラッパーなので、論理行ごとにcanvastextlayoutを作る形にしたらレタリングの部分はかなり短くできそう
文字列操作がとてもめんどくさくなるけど…
さびねこにゃさん
コメントありがとうございます。
テキスト処理のアプリを作るとき、内部データを論理行ごとに持つのか、改行文字を含めた一つの配列に持つのかで、いつも悩みます。
どちらの方法も一長一短なので...