首页 > 解决方案 > 如何在 Rust 中为终端设置动画

问题描述

我需要分析几个变量,例如在我的应用程序中呈现的每秒帧数。因此,我需要一种简单的方法来更新终端中的变量。

我搜索并找到ascii_table了生成表和termion更新终端。但我怀疑termion这里只是用来清除终端。

无论如何,我能够绘制一个简单的表格并每 200 毫秒更新一次其内容:

use ascii_table::{Align, AsciiTable, Column};
extern crate termion;

use termion::{clear, color, cursor};

use std::fmt::Display;
use std::{thread, time};
fn main() {
    let mut ascii_table = AsciiTable::default();
    ascii_table.max_width = 40;

    let mut column = Column::default();
    column.header = "H1".into();
    column.align = Align::Left;
    ascii_table.columns.insert(0, column);

    let mut column = Column::default();
    column.header = "H2".into();
    column.align = Align::Center;
    ascii_table.columns.insert(1, column);

    let mut column = Column::default();
    column.header = "H3".into();
    column.align = Align::Right;
    ascii_table.columns.insert(2, column);
    let mut i = 0;
    while (true) {
        let data: Vec<Vec<&dyn Display>> = vec![
            vec![&i, &"hello", &789],
        ];

        let s = ascii_table.format(data.clone());

        println!(
            "\n{}{}{}{}",
            cursor::Hide,
            clear::All,
            cursor::Goto(1, 1),
            s
        );
        println!("Hello");//couldn't make this appear on top.
        i = i+1;
        std::thread::sleep(std::time::Duration::from_millis(200));
    }
}

这是程序top在终端上更新数据的方式吗?或者,还有更好的方法?有更复杂的结构会很好。

标签: rust

解决方案


在终端上格式化复杂数据没有比您现在做的更好的方法了。可以进行一些单独的改进以提高显示质量。

特别是,为了减少闪烁,最好覆盖文本而不是先清除整个终端,只清除需要变为空白或已经为空白的部分,使用更窄的清除操作,例如清除到行尾,当你替换一行时你会使用它,它可能会变短——通过在文本的末尾清楚地放置它,这样如果文本没有改变,它就不会短暂消失。

由于您从生成多行文本的代码开始,因此您需要编辑字符串:

// Insert clear-to-end-of-line into the table
let s = ascii_table.format(data.clone())
    .replace("\n", &format!("{}\n", clear::UntilNewline));

println!(
    "\n{}{}{}{}",
    cursor::Hide,
    cursor::Goto(1, 1),
    s,
    clear::AfterCursor,
);

请注意,我已经对操作进行了重新排序:首先,我们转到 (1, 1) 并绘制文本(在进行时清除到行尾)。然后当一切都完成后,我们从光标清除到屏幕末尾。这样,我们永远不会清除任何我们希望仍然存在的文本,因此不会有闪烁。

我注意到你还有一个愿望:

println!("Hello");//couldn't make this appear on top.

你需要做的就是在 goto 之后和 table 之前做,包括清理,它会按照你的意愿工作。

// Move cursor to top. This always goes first.
// Note print!, not println!, since we don't want to move down
// after the goto.
print!("{}{}", cursor::Hide, cursor::Goto(1, 1));

// Use clear::UntilNewline on intermediate things
println!("Hello{}", clear::UntilNewline);

// ...even if they are blank lines
println!("{}", clear::UntilNewline);

// Use clear::AfterCursor on the *last* thing printed
// Note print!, not println!, since if we are filling the entire terminal
// we don't want to cause it to scroll down.
print!("{}{}", s, clear::AfterCursor);

我没有涉及的一件事是,您还可以使用termion::cursor::Goto移动到终端上的特定区域来更新它们,而不是从上到下编写整行。这当然更复杂,因为您的程序必须理解整个布局才能知道要转到哪个光标位置,并知道需要重绘哪些部分。在实际串行终端和调制解调器的数据速率非常低的时代,这是一项非常重要的优化,可以避免在相同字符上浪费传输时间——今天,它已经不那么重要了。


推荐阅读