【Ratatui】Rustで作るTUIツール:ユーザーからの文字入力を受け取る(Input Formの実装)

rust tui第二回では、文字入力をイベント受付し、表示できるようにします

TUIツールを作ってみるというこのシリーズの第2回は、基本となる文字入力についてです。

前回の「Hello World」では固定の文字列を表示しましたが、実用的なツール(電卓やエディタなど)を作るには、ユーザーからの入力をリアルタイムで反映させる必要があります。

文字入力の受付方法

1. キー入力イベントの仕組み

Ratatui(crosstermバックエンド)では、キーボードの操作を Event として受け取ります。 基本は「ループ内でイベントを待ち受け、押されたキーに応じて処理を分岐させる」というシンプルな構造です。

2. 変数への受け取りと基本実装

もっとも単純に「入力した文字を画面に出す」ための最小実装です。

環境構築メモ

# プロジェクトの作成
cargo new simple-char-input
cd simple-char-input

# 必要なクレートの追加
# ratatui: TUI本体
# crossterm: ターミナルの描画・イベント制御(クロスプラットフォーム)
cargo add ratatui crossterm

サンプルコード (mainの中身)

    // --- 2. 状態管理用の変数 ---
    let mut input = String::new(); 

    // --- 3. メインループ ---
    loop {
        // UIの描画
        terminal.draw(|f| {
            let area = f.area();
            let input_display = Paragraph::new(input.as_str())
                .block(Block::default().borders(Borders::ALL).title(" TUKUMO Input Test "));
            f.render_widget(input_display, area);
        })?;

        // イベント処理(キー入力の判定)
        if let Event::Key(key) = event::read()? {
            // 押し下げイベントのみ処理する
            if key.kind == KeyEventKind::Press {
                match key.code {
                    KeyCode::Char(c) => {
                        input.push(c); // 文字を追加
                    }
                    KeyCode::Backspace => {
                        input.pop(); // 1文字削除
                    }
                    KeyCode::Esc => {
                        break; // Escで終了
                    }
                    _ => {}
                }
            }
        }
    }

全文は長かったので閉じています。”ソースを表示”をクリックすると見れます。

use crossterm::{
    event::{self, Event, KeyCode, KeyEventKind},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{
    backend::CrosstermBackend,
    widgets::{Block, Borders, Paragraph},
    Terminal,
};
use std::{error::Error, io};

fn main() -> Result<(), Box<dyn Error>> {
    // --- 1. ターミナルの初期化 ---
    enable_raw_mode()?; // 入力をダイレクトに受け取る
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen)?; // 専用画面に切り替え
    
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;

    // --- 2. 状態管理用の変数 ---
    let mut input = String::new(); 

    // --- 3. メインループ ---
    loop {
        // UIの描画
        terminal.draw(|f| {
            let area = f.area();
            let input_display = Paragraph::new(input.as_str())
                .block(Block::default().borders(Borders::ALL).title(" TUKUMO Input Test "));
            f.render_widget(input_display, area);
        })?;

        // イベント処理(キー入力の判定)
        if let Event::Key(key) = event::read()? {
            // 押し下げイベントのみを処理(Windowsなどの重複防止)
            if key.kind == KeyEventKind::Press {
                match key.code {
                    KeyCode::Char(c) => {
                        input.push(c); // 文字を追加
                    }
                    KeyCode::Backspace => {
                        input.pop(); // 1文字削除
                    }
                    KeyCode::Esc => {
                        break; // Escで終了
                    }
                    _ => {}
                }
            }
        }
    }

    // --- 4. 後片付け ---
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
    terminal.show_cursor()?;

    Ok(())
}

前々回 Ubuntu上で Rust × ratatui の開発環境構築方法・TUI上にHelloWorld表示まで で紹介した方法(環境セットアップ+上でメモっている方法)でバニラな環境を構築して、これをmain.rsに記入して実行すると以下のような表示になり、文字入力できることを確認できます。

input テストUIで表示されるTUI

Input TestのTUI

前回作った、UIの中身は”Hello,world!”という固定値でしたが、今回のコードでは、文字入力を受け付けてそれをcという変数で受け付け、inputに保持してそれを表示するという流れで文字入力できるようになっています。

tukumo と打ってみた。

tukumoと自作したTUIで入力受付できるかテストした例

打ち込んだ文字が表示できていることを確認

文字入力の受付方法のちょこっと解説

今回のコードで行っている処理は、大きく分けて「イベントのキャッチ」と「状態の反映」の2つです。

1. イベント処理:キーボードから文字を取り出す

if let Event::Key(key) = event::read()? { ... }
  • event::read(): ターミナルで発生した「何か(キー入力、マウス移動など)」を捕まえます。
  • KeyEventKind::Press: 「キーを押した瞬間」だけをフィルタリングします。これがないと、環境によっては「キーを離した瞬間」も二重にカウントされてしまいます。
  • KeyCode::Char(c): 押されたキーが「文字」だった場合、その内容を変数 c に取り出します。
  • input.push(c): ループの外に定義した String 型の変数(状態)に、取り出した文字を連結して保存します。

ここは、よくあるUI言語(Qtとか,tkinterとかjavaScriptなど)とも似ていますし、KMKなどキーボードの開発における入力検知と同じですね。

アケコン風キーボード自作:RP2040+KMKで実現する「マウスも動かせる」最強レイアウト-その3 KMK-【RP2040】

2. UI描画:保存された文字を画面に映す

terminal.draw(|f| { ... })?
  • terminal.draw: Ratatuiの心臓部です。実行されるたびに画面全体を一度クリアし、最新の情報を「ゼロから描き直し」ます。
  • input.as_str(): 保存されている String の中身を、ウィジェットが扱える「文字列の参照(&str)」として貸し出します。
  • Paragraphウィジェット: 文字列を「段落」として扱うための部品です。ここで枠線(Borders)やタイトルを設定します。
  • f.render_widget: 完成したウィジェットを、指定した領域(area)に流し込みます。
Paragraph::new(input.as_str()) なんで inputをそのまま渡さないの?

ここで .as_str() を使っているのは、Paragraphウィジェットに文字列の「所有権」を渡さず、中身だけを「貸し出す(参照)」ためです。これにより、ループの次の回でも input 変数をそのまま使い続けることができます。

最後に

基本入力とイベント処理の方法がわかったので、あとは、機能面を作っていけばツールとしては完成まで作れます。

今回の内容で「文字を打つ」ことはできましたが、これだけでは「保存」や「終了」といった複雑なコマンドが実行できません。次回は modifiers(修飾キー)を使って、Ctrl + SCtrl + Q などのコンボ入力を判別する方法を解説します。

次回コンボ処理関連記事は、2026年4月21日に公開予定 (あと18時間)

ここまで読んでいただきありがとうございます。

では、次の記事で。 lumenHero