29 个版本

0.5.0 2022 年 10 月 28 日
0.4.6 2022 年 5 月 4 日
0.4.5 2021 年 2 月 15 日
0.4.2 2020 年 10 月 20 日
0.2.6 2019 年 3 月 30 日

#135 in 命令行界面

Download history 2929/week @ 2024-03-13 3434/week @ 2024-03-20 3738/week @ 2024-03-27 3790/week @ 2024-04-03 3561/week @ 2024-04-10 3984/week @ 2024-04-17 4110/week @ 2024-04-24 3560/week @ 2024-05-01 3996/week @ 2024-05-08 4225/week @ 2024-05-15 4504/week @ 2024-05-22 4051/week @ 2024-05-29 4032/week @ 2024-06-05 3823/week @ 2024-06-12 3985/week @ 2024-06-19 3700/week @ 2024-06-26

16,309 每月下载量
用于 115 个 crate(11 个直接使用)

MIT 许可证

205KB
5K SLoC

Crates.io Build Status

Tuikit

Tuikit 是一个用于编写终端 UI 应用的 TUI 库。特点

  • 线程安全。
  • 支持非全屏模式以及全屏模式。
  • 支持 Alt 键、鼠标事件等。
  • 缓冲以提高渲染效率。

Tuikit 是基于 termbox 设计的,它将终端视为固定大小单元格的表格,并将输入视为结构化消息流。

警告:该库尚不稳定,API 可能会更改。

使用方法

在你的 Cargo.toml 中添加以下内容

[dependencies]
tuikit = "*"

如果你想要使用最新的快照版本

[dependencies]
tuikit = { git = "https://github.com/lotabout/tuikit.git" }

以下是一个示例(也可以通过 cargo run --example hello-world 运行)

use tuikit::prelude::*;
use std::cmp::{min, max};

fn main() {
    let term: Term<()> = Term::with_height(TermHeight::Percent(30)).unwrap();
    let mut row = 1;
    let mut col = 0;

    let _ = term.print(0, 0, "press arrow key to move the text, (q) to quit");
    let _ = term.present();

    while let Ok(ev) = term.poll_event() {
        let _ = term.clear();
        let _ = term.print(0, 0, "press arrow key to move the text, (q) to quit");

        let (width, height) = term.term_size().unwrap();
        match ev {
            Event::Key(Key::ESC) | Event::Key(Key::Char('q')) => break,
            Event::Key(Key::Up) => row = max(row-1, 1),
            Event::Key(Key::Down) => row = min(row+1, height-1),
            Event::Key(Key::Left) => col = max(col, 1)-1,
            Event::Key(Key::Right) => col = min(col+1, width-1),
            _ => {}
        }

        let attr = Attr{ fg: Color::RED, ..Attr::default() };
        let _ = term.print_with_attr(row, col, "Hello World! 你好!今日は。", attr);
        let _ = term.set_cursor(row, col);
        let _ = term.present();
    }
}

布局

tuikit 提供了 HSplitVSplitWin 来管理布局

  1. HSplit 允许你将区域水平分割成多个部分。
  2. VSplitHSplit 类似,但垂直分割。
  3. Win 不进行分割,它可以有边距、填充和边框。

例如

use tuikit::prelude::*;

struct Model(String);

impl Draw for Model {
    fn draw(&self, canvas: &mut dyn Canvas) -> DrawResult<()> {
        let (width, height) = canvas.size()?;
        let message_width = self.0.len();
        let left = (width - message_width) / 2;
        let top = height / 2;
        let _ = canvas.print(top, left, &self.0);
        Ok(())
    }
}

impl Widget for Model{}

fn main() {
    let term: Term<()> = Term::with_height(TermHeight::Percent(50)).unwrap();
    let model = Model("middle!".to_string());

    while let Ok(ev) = term.poll_event() {
        if let Event::Key(Key::Char('q')) = ev {
            break;
        }
        let _ = term.print(0, 0, "press 'q' to exit");

        let hsplit = HSplit::default()
            .split(
                VSplit::default()
                    .basis(Size::Percent(30))
                    .split(Win::new(&model).border(true).basis(Size::Percent(30)))
                    .split(Win::new(&model).border(true).basis(Size::Percent(30)))
            )
            .split(Win::new(&model).border(true));

        let _ = term.draw(&hsplit);
        let _ = term.present();
    }
}

分割算法很简单

  1. 无论是 HSplit 还是 VSplit,都会获取多个 Split,其中 Split 会包含
    1. 基本,原始大小
    2. 增长,如果还有足够的空间,增长的因素
    3. 收缩,如果空间不足,收缩的因素
  2. HSplitVSplit 将计算分割项的总宽度/高度(基本)
  3. 判断当前宽度/高度是否足够用于分割项
  4. 根据它们的增长/收缩: factor / sum(factors) 缩放分割项
  5. 如果仍然空间不足,最后一个(些)将被设置为宽度/高度为0

参考

Tuikit 从许多其他项目中借鉴了想法

  • rustyline 使用Rust实现的Readline。
    • 如何进入原始模式。
    • 按键码解析逻辑的一部分。
  • termion 一个用于控制终端/TTY的无绑定库。
    • 如何解析鼠标事件。
    • 如何进入原始模式。
  • rustboxtermbox
    • 将终端视为固定单元格表格的想法。
  • termfest 使用Rust编写的简单TUI库
    • 缓冲区想法。

依赖项

~2.5MB
~46K SLoC