Skip to content

Rendering under the hood

以前のセクションでは、Ratatuiが即時モードのレンダリングライブラリであることを読んだことがあるかもしれません。しかし、それは本当にどういう意味ですか?そして、それはどのように実装されていますか?このセクションでは、 Terminaldraw メソッドから始まり、選択したバックエンドライブラリで終了する、Ratatuiが画面にウィジェットをどのようにレンダリングするかについて説明します。

導入

RatatuiでUIをレンダリングするには、アプリケーションがTerminal::draw()メソッドを呼び出します。このメソッドは、[Frame]のインスタンスを受け入れる[閉鎖]を取ります。 draw メソッド内で、アプリケーションはFrame::render_widget()を呼び出して、利用可能なレンダリング可能な領域内のウィジェットの状態をレンダリングできます。このページの Frame::render_widget() についてのみ説明しますが、レンダリングに関するこの議論はFrame::render_stateful_widget()に等しく適用されます。

例として、 terminal.draw() は、ratatuiとの単純な「hello world」を呼び出すことを次に示します。

terminal.draw(|frame| {
frame.render_widget(Paragraph::new("Hello World!"), frame.area());
});

閉鎖は、タイプ &mut Frame の引数 frame を取得します。

frame.area() は、レンダリング可能な領域を表す Rect を返します。 Frame は、 render_widget() メソッドを使用してウィジェットをレンダリングできる中間バッファーへの参照も保持します。 draw メソッド (閉鎖が戻った後) の終わりに、ratatuiはバッファのコンテンツをターミナルに持続させます。次のセクションで、より詳細を進めましょう。

Widget trait

ratatuiでは、 frame.render_widget() メソッドは、Widgettrait を実装するタイプエラスの構造体に Widget::render() メソッドを呼び出します。

pub trait Widget {
/// Draws the current state of the widget in the given buffer.
fn render(self, area: Rect, buf: &mut Buffer);
}

任意の構造体 (Ratatuiまたはサードパーティのクレート内) は、 Widget trait を実装して、その構造体のインスタンスをターミナルにレンダリング可能にすることができます。 Widget::render() メソッドは、structをレンダリング可能なウィジェットにするために必要な唯一の方法です。

上記の Paragraph 例では、 frame.render_widget()Paragraph に実装された Widget::render()メソッドを呼び出します。コンテンツを描画する方法の例については、他のウィジェットの render メソッドを見ることができます。

簡単な例として、ビルトイン Clear ウィジェットを見てみましょう。 Clear ウィジェットは、バッファ内のすべてのセルのスタイル情報をデフォルトに戻します。 Clear ウィジェットの完全な実装は次のとおりです。

pub struct Clear;
impl Widget for Clear {
fn render(self, area: Rect, buf: &mut Buffer) {
for x in area.left()..area.right() {
for y in area.top()..area.bottom() {
buf.get_mut(x, y).reset();
}
}
}
}

上記の Clear ウィジェットの例では、アプリケーションが Frame::render_widget() メソッドを呼び出すと、 Clear ‘s Widget::render() メソッドを呼び出します。 render が領域全体をループし、 buf.get_mut(x, y).reset() を呼び出すことがわかります。ここでは、 Buffer の多くの方法のいずれかを使用します。つまり、Cell を返す get_mut(x, y) および reset() は[Cell]のメソッドです。

バッファ

[Buffer]は、コンテンツを操作することでアプリケーションを引き付けることができるターミナルの[Viewport]をカバーする長方形の領域を表します。 Buffer には、ターミナルのディスプレイエリアの行と列を表す[Cell]のコレクションが含まれています。上記の Clear 例で見たように、ウィジェットは Buffer メソッドを使用してこれらの Cell sと対話します。

これは、幅 12Cells、高さ 4Cellsの Buffer の視覚的表現です。

0 1 2 3 4 5 6 7 8 9 10 11 H e l l o W o r l d ! symbol style 0 1 2 3 fg bg Reset Reset : : o”

Ratatuiでは、 Cell structはコードの最小の単位です。各 Cell は、シンボルとスタイルの情報 (前景の色、背景色、修飾子など) を追跡します。 Cell は、グラフィカルUIの「ピクセル」に似ています。通常、ターミナルはテキストをレンダリングして、個々のセルが幅の約2倍のスペースを占有するようにします。ratatuiの Cell は通常、1つの幅の文字列コンテンツを含める必要があります。

Buffer テキストを作成し、特定の領域にスタイルを設定し、個々のセルを操作する方法を実装します。例えば、

  • buf.get_mut(0, 0) は、row = 0とcol = 0のシンボルとスタイルの情報を備えた Cell を返します。
  • hello worldBuffer にdefict ow = 0およびcol = 0でデフォルトに設定されます。

これらの方法により、 Widget trait の実装が Buffer のさまざまな部分に書き込むことができます。

アプリケーションが terminal.draw(|frame| ...) を呼び出すたびに、ratatuiは[Frame]の新しいインスタンスを閉鎖に渡します。Ratatuiウィジェットは、情報がターミナルに書き込まれる前にこの中間バッファーにレンダリングし、 Buffer にレンダリングされたコンテンツは、 draw コール中にフレームに接続されている Buffer にのみ保存されます。これは、 crossterm のようなライブラリを直接使用することとは対照的であり、テキストをターミナルに書き込むことがすぐに発生する可能性があります。

flush()

draw メソッドに提供された閉鎖が終了した後、 draw メソッド呼び出し[Terminal::flush()]。 flush() ターミナルにバッファーのコンテンツを書き込みます。Ratatuiはダブルバッファーアプローチを使用します。現在のバッファーと以前のバッファーの間の diff を計算して、ターミナル画面に効率的に書き込むコンテンツを把握します。 flush() の後、ratatuiはバッファーを交換し、次に terminal.draw(|frame| ...) を呼び出すときに Frame を他の Buffer で構築します。

すべてのウィジェットが単一の terminal.draw(|frame| ...) コール内で同じ Buffer にレンダリングされるため、異なるウィジェットのレンダリングは、バッファで同じ Cell を上書きする場合があります。これは、ウィジェットがレンダリングされる順序が最終UIに影響することを意味します。

たとえば、以下のこの draw 例では、 "content2" によって上書きされます。

terminal.draw(|frame| {
frame.render_widget(Paragraph::new("content1"), frame.area());
frame.render_widget(Paragraph::new("content2"), frame.area());
frame.render_widget(Paragraph::new("content3"), frame.area());
})

新しい Frame が構築される前に、Ratatuiは現在のバッファーをきれいに拭きます。このため、アプリケーションが terminal.draw() を呼び出す場合、フレームの一部ではなく、ターミナルにレンダリングされると予想されるすべてのウィジェットを描画する必要があります。Ratatuiの拡散アルゴリズムは、ターミナル画面への効率的な書き込みを保証します。

結論

要約すると、アプリケーションは terminal.draw(|frame| ...) を呼び出し、ターミナルはアプリケーションによって提供される閉鎖に渡されるフレームを構築します。閉鎖は、 Frame::render_widget を呼び出すことにより、各ウィジェットをバッファーに描画し、各ウィジェットのレンダリング方法を呼び出します。最後に、Ratatuiはバッファの内容をターミナルに書き込みます。

widgetframeBufferTerminalCrosstermAppwidgetframeBufferTerminalCrosstermAppoptdrawnewuinewrender_widgetrenderget_cellset_stringset_lineset_styleflush()return

[Cell]https://github.com/ratatui/ratatui/blob/e5caf170c8c304b952cbff7499fd4da17ab154ea/src/buffer.rs#L15-L26 [Buffer]https://github.com/ratatui/ratatui/blob/e5caf170c8c304b952cbff7499fd4da17ab154ea/src/buffer.rs#L149-L157 [Viewport]https://github.com/ratatui/ratatui/blob/88ae3485c2c540b4ee630ab13e613e84efa7440a/src/terminal.rs#L41-L65 [Text]https://github.com/ratatui/ratatui/blob/e5caf170c8c304b952cbff7499fd4da17ab154ea/src/text/text.rs#L30-L33 [Line]https://github.com/ratatui/ratatui/blob/e5caf170c8c304b952cbff7499fd4da17ab154ea/src/text/line.rs#L6-L10 [Span]https://github.com/ratatui/ratatui/blob/e5caf170c8c304b952cbff7499fd4da17ab154ea/src/text/span.rs#L55-L61 [render method for Block]https://github.com/ratatui/ratatui/blob/e5caf170c8c304b952cbff7499fd4da17ab154ea/src/widgets/block.rs#L752-L760 [Frame]https://github.com/ratatui/ratatui/blob/e5caf170c8c304b952cbff7499fd4da17ab154ea/src/terminal.rs#L566-L578 [Terminal::flush()]https://github.com/ratatui/ratatui/blob/e5caf170c8c304b952cbff7499fd4da17ab154ea/src/terminal.rs#L253-L263 [get_mut]https://github.com/ratatui/ratatui/blob/88ae3485c2c540b4ee630ab13e613e84efa7440a/src/buffer.rs#L207-L211 [set_string]https://github.com/ratatui/ratatui/blob/88ae3485c2c540b4ee630ab13e613e84efa7440a/src/buffer.rs#L289-L294