Rendering under the hood
以前のセクションでは、Ratatuiが即時モードのレンダリングライブラリであることを読んだことがあるかもしれません。しかし、それは本当にどういう意味ですか?そして、それはどのように実装されていますか?このセクションでは、 Terminal
の draw
メソッドから始まり、選択したバックエンドライブラリで終了する、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()
メソッドは、Widget
trait を実装するタイプエラスの構造体に 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と対話します。
これは、幅 12Cell
s、高さ 4Cell
sの Buffer
の視覚的表現です。
Ratatuiでは、 Cell
structはコードの最小の単位です。各 Cell
は、シンボルとスタイルの情報 (前景の色、背景色、修飾子など) を追跡します。 Cell
は、グラフィカルUIの「ピクセル」に似ています。通常、ターミナルはテキストをレンダリングして、個々のセルが幅の約2倍のスペースを占有するようにします。ratatuiの Cell
は通常、1つの幅の文字列コンテンツを含める必要があります。
Buffer
テキストを作成し、特定の領域にスタイルを設定し、個々のセルを操作する方法を実装します。例えば、
buf.get_mut(0, 0)
は、row = 0とcol = 0のシンボルとスタイルの情報を備えたCell
を返します。hello world
はBuffer
に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はバッファの内容をターミナルに書き込みます。
[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