カーソルの表示と移動ができるようになったので次はスクロールの機能をつけたいと思います。
スクロールはratatuiのParagraphにあるそのものズバリなscrollメソッドを使って行います。
これらの処理を行うために、スクロール処理を扱う新たなScroll構造体を追加しました。
メインパネルとASCIIコードを表示するサブパネルのスクロール量を配列に格納して、Paragraphで利用する形としました。
これにいくつかのヘルパー関数を作成してスクロール量の計算をしていきます。
// スクロール量
pub(crate) struct Scroll {
scroll_y: [u16; 2], // main:0, sub0:1
}
impl Scroll {
fn new() -> Self {
let scroll_y = [0; 2];
Self { scroll_y }
}
pub(crate) fn scroll_y(&self) -> &[u16; 2] {
&self.scroll_y
}
pub(crate) fn scroll_y_mut(&mut self) -> &mut [u16; 2] {
&mut self.scroll_y
}
// スクロール上限計算
pub(crate) fn calc_border(bottom: u16) -> u16 {
const SCROLL_Y_BORDER: u16 = 3;
let border = bottom.saturating_sub(SCROLL_Y_BORDER);
border
}
// スクロール計算
pub(crate) fn calc_scroll(y: u16, border: u16) -> u16 {
let scroll_y;
if y > border {
scroll_y = y - border;
} else {
scroll_y = 0;
}
scroll_y
}
}
アプリとしては縦方向のみのスクロールを想定しています。
Y軸の画面上の下限を取得してそのいくつか手前(上限 – SCROLL_Y_BORDER)にカーソルが来た段階でスクロールが始まるように処理をしています。
描画処理関連のコードが大きくなってきたこととmessageが可変である必要が出てきたので新たにrender_prep()を追加してそちらで前処理をおこなう事とします。
スコープを限定した可変参照
ここで少し所有権に関してテクニカルな書き方をしています。
Rustの所有権について以前書きましたが可変参照は1つしか持てません。
そのため、6行目のmessage.cursor_mut()と15行目のmessage.scroll_mut().scroll_y_mut()はmessageのメンバーを変更するので同時に使用することができません。
ただし、同一スコープでなければ記述は可能です。
同じスコープで同時に可変参照を持たないようにpos_y変数の値の取得を限定したスコープ内で先におこない、message.scroll_mut().scroll_y_mut()とコンフリクトしないようにしています。
現状はこのままのコードでも良いですが、message.scroll_mut().scroll_y_mut()の処理も限定したスコープ内で実行するようにしたほうが良いかもしれません。
// ratatuiレンダリング準備
pub(crate) fn render_prep(message: &mut Message) -> io::Result<()> {
.....
// カーソルY座標の算出
let pos_y = {
let cursor = message.cursor_mut();
cursor.calc_position();
cursor.position().y
};
// 画面の下限
let main_bottom = main_layout[0].bottom();
// メインパネルのスクロール開始ラインの算出
let main_border = Scroll::calc_border(main_bottom);
// メインパネルのスクロール量計算
let scroll_y = message.scroll_mut().scroll_y_mut();
scroll_y[0] = Scroll::calc_scroll(pos_y, main_border);
.....
その他
イベントループの中に新しく作ったrender_prep()を追加しました。
// イベントループ
while event_handler.is_looping() {
// 描画処理準備
let _ = render_prep(&mut terminal, &mut message);
// 描画処理
let _ = render_main(&mut terminal, &message);
// イベント処理
event_handler.run(&mut message);
}
render_main()では下記のようにスクロール処理をしています。
let main_contents = Paragraph::new(Text::from(main_panel_data))
.scroll((message.scroll().scroll_y()[0], 0))
.block(block.clone());
TOC
- 準備
- ターミナル入出力を試す
- ターミナル系crateを利用する
- 編集データと表示処理
- 画面制御
- その他
GitHubにコードをアップロードしています。
コードのコメントに書かれているfirst_stepなどをcargoコマンドに渡すと実行できます。
# Example
$ cargo run --examples first_step