開発

[binllion] ratatuiを導入する

入力部分の処理は大まかな骨組みができたと思いますので次は画面表示について進めていきたいと思います。

crosstermでスクラッチから表示処理を構築していくのもよいですが、便利なクレートを利用して手間を減らしたいと思います。

そこでクレートを探したところ ratatuiが良さそうでしたのでこちらを導入してみたいと思います。

ratatuiは画面表示をリッチにするためのWidget機能などを提供しバックエンドとしてcrosstermを使用することが可能です1

導入

crosstermと同様にcargo addコマンドでクレートを追加します。

執筆時点ではv0.26.3がインストールされました。

cargo add ratatui

ratatuiについて

ratautiの説明文によるとこのクレートは immediate mode で動作するlightweight なgraphics libraryです。

表示部分の機能強化を担うcrosstermへのアドオン的なものとなります。

イベント処理などはしないのでその部分は従来通りcrosstermの機能を使用することになります。

ratautiの提供するwidgetを使うとリッチな表現を手軽に使うことができます。

主要な機能

まずはお試しとしてratautiのチュートリアルを元にコードに新たにrender_main() を追加してどのように使うのか動作確認をしてみました。

おそらくコード量が増えると思いますので後にモジュール化することになるでしょう。

今回追加したコードを元にratautiの主要な機能を解説してみたいと思います。

Layout

widgetの描画は四角い領域を指定して出力していく形になります。そのため、まずはどこの座標にどのくらいの大きさで出力するのかを決める必要があります。

座標軸は左上を原点にしていますが画面サイズは端末によって違いますしリサイズも発生します。そのため、都度サイズ割り出しが必要になります。

下記はレイアウト計算をしている箇所になります。

ここでは画面全体のサイズを左右で二分割し、分割された右側の領域を更に上下に二分割して、3つの領域を作っています。

それぞれ3つの領域の最大サイズをそれぞれのwidgetのサイズとして使用することにしています。

// ratatui

    let _ = terminal.draw(|frame| {
        // 左右に50%分割
        let layout = Layout::default()
            .direction(Direction::Horizontal)
            .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
            .split(frame.size());

        // 右側を上下に50%分割
        let sub_layout = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
            .split(layout[1]);

        let main_panel = layout[0];
        let sub_panel_0 = sub_layout[0];
        let sub_panel_1 = sub_layout[1];Code language: JavaScript (javascript)

Widget

ratautiは様々なWidgetを提供していますが、ここでは基本的なBlock widgetとParagraph widgetを使用しています。

Block widgetはベーシックなwidgetで四角い領域を表示します。ここでは領域に沿って枠線とタイトルを上下に表示するようにしています。

Paragraph widgetは文字出力に使います。文字の色を変更したり、右寄せなどアライメント機能を提供しています。

    // メインパネル
    // 上タイトル
    let title = Title::from(" main ".bold());
    // 下タイトル
    let instructions = Title::from(Line::from(vec![" Quit ".into(), "<Ctrl+Q> ".blue().bold()]));

    // パネルブロック
    let block = Block::default()
        .title(title.alignment(Alignment::Center))
        .title(
            instructions
                .alignment(Alignment::Center)
                .position(Position::Bottom),
        )
        .borders(Borders::ALL)
        .border_set(border::THICK);

    // 16進数ヘッダー
    let header = Line::from(format!(
        "{:width$} +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F",
        " ",
        width = 8
    ));

    let contents = Paragraph::new(Text::from(vec![header])).block(block);Code language: JavaScript (javascript)

Rendering

レンダリング部分はレイアウト部分の続きのコードで実施しています。

レイアウト処理で割り出したサイズとwidgetをrender_widgetに渡して描画、となります。

動作確認のため、同じwidgetを使いまわしています。

        // パネルを描画
        frame.render_widget(&contents, main_panel);
        frame.render_widget(&contents, sub_panel_0);
        frame.render_widget(&contents, sub_panel_1);Code language: JavaScript (javascript)

最終的な画面出力は下記のようになりました。

想定通り3ペインの画面となっています。左側をエディターのメイン画面、右側をASCII表現で表示する領域にしていこうと思いますがまだ詳細は決めていません。

今後はこのコードを調整してエディターらしい画面に仕上げていきたいと思います。


  1. crosstermの他にもtermionとtermwizをサポートしています。 ↩︎

GitHubにコードをアップロードしています。0.1.0バージョン

コードのコメントに書かれているfirst_stepなどをcargoコマンドに渡すと実行できます。

# Example
$ cargo run --example first_stepCode language: Bash (bash)
管理人

Recent Posts

情報セキュリティマネジメント試験取得への道

スキルアップを図るべく情報セキ…

2か月 ago

ファイナンシャルプランナー3級試験取得への道

スキルアップを図るべくファイナ…

2か月 ago

[rust] New Type Patternを使ってみる

DDDの考えを取り入れることで…

5か月 ago

RustでDDDの要素を取り入れてみる

前回SOLID原則というものを…

5か月 ago

期間限定!書籍無料キャンペーン2025

「mdBookではじめるKin…

5か月 ago