
前回の「レイアウト編」では、画面を複数のボックスに分割する方法を学びました。 しかし、複数の入力欄やボタンがある場合、「今、どこを操作しているのか」をプログラムが把握していなければ、ユーザーの入力に反応することができません。
今回は、TUIアプリにおける「フォーカス管理」の基本を、ステートマシン(状態遷移)の考え方を用いて実装してみましょう。
前回 レイアウト編:【Ratatui】画面を分割して「ツール」らしくする(Layout編)
1. フォーカス変更の方法

画像 : フォーカス管理のサンプルTUI
サンプルとして、tabでフォーカスを切り替え、フォーカス状態に応じて枠の色を変える(白から黄色)ものを作ってみました。この実装のフォーカス管理の方法について以下で説明します。
1.1フォーカス状態の管理 enum
フォーカス管理はいろんな方法があると思いますが、今回は列挙型でそれぞれの状態を管理して切り替える方法について紹介しようと思います。
まずは、どのようなフォーカス先があるのかを列挙型(enum)で定義します。 今回は「Left(左)」「Right(右)」「Bottom(下)」の3つのエリアを行き来する構成にします。
// 1. フォーカス状態を定義
#[derive(PartialEq)]
enum Focus {
Left,
Right,
Bottom,
}
1.2 フォーカスの可視化
フォーカス中の管理は、1.1で定義したFocusという列挙型を持つ変数 current_focusを用いているので、この状態を描画ループの中で読み、色を変えています。
# 可視化に関する部分だけ抜粋しています。
let mut current_focus = Focus::Left;
loop {
terminal.draw(|f| {
// フォーカス中なら黄色、そうでなければ白
let get_style = |focus: Focus| {
if current_focus == focus {
Style::default().fg(Color::Yellow)
} else {
Style::default().fg(Color::White)
}
};
}
}1.3 Tabキーでの「巡回」と「分岐」
ここが本節のメインです。 「Tabキーでフォーカスを移動させる処理」と、「現在のフォーカス先に応じて入力を振り分ける処理」を実装します。
Tabキーによるフォーカスの巡回
match 式を使い、現在の状態から次の状態へと「状態遷移」させます。
// Tabキーが押された際の処理
KeyCode::Tab => {
current_focus = match current_focus {
Focus::Left => Focus::Right,
Focus::Right => Focus::Bottom,
Focus::Bottom => Focus::Left, // 最初に戻る(ループ)
};
}フォーカス先に応じた入力の振り分け
文字入力(Char)イベントが発生した際、current_focus を見て「どこに文字を渡すか」を決定します。
// キー入力の振り分け
KeyCode::Char(c) => match current_focus {
Focus::Left => left_input.push(c),
Focus::Right => right_input.push(c),
Focus::Bottom => { /* 下部エリア用の処理 */ }
},このように、「状態を持つ(enum)」→「状態を変える(match)」→「状態を見て振る舞いを変える(match)」という流れが、TUIにおけるフォーカス管理の基本パターンです。
2. サンプルコードの動作

操作例 tabで切り替え
tabで入力欄をきりかえて操作できました。
3. さいごに
今回までで、基本的なTUIの構築の方法は触れたと思います。次回はここまでの操作まとめとして、タブ切り替えを実装したサンプルコードとここまでの記事の振り返りをしたいと思います。
次回 まとめ : 【完結】Rust × Ratatuiで作るTUIツール:基礎編の総まとめと実践サンプル
ここまで読んでいただきありがとうございます。
では、次の記事で。 lumenHero