10 个版本 (5 个重大更改)

0.7.0 2023 年 8 月 3 日
0.6.4 2023 年 6 月 30 日
0.6.3 2023 年 2 月 5 日
0.5.0 2022 年 12 月 29 日
0.0.1 2019 年 2 月 12 日

#187命令行界面

每月 42 次下载
用于 w3t

MIT 许可证

295KB
7K SLoC

欢迎来到这个山丘! Termit 实现 TUI - 另一个终端 UI。

简而言之

//! This is a basic hello world termit example.
//!
//! Just add a loop and some input handling...
use std::io;
use termit::prelude::*;
#[async_std::main]
async fn main() -> io::Result<()> {
    // The Termit facilitator:
    // * Use the Terminal to set up your terminal experience.
    // * Initialize Termit.
    let mut termit = Terminal::try_system_default()?.into_termit::<NoAppEvent>();

    // The TUI tree:
    // * Canvas sets the default style for the contained widget and fills space.
    // * "Hello World!" - &str/String - is a widget.
    // * We just place the text in the middle with some placement constraints.
    let mut ui = Canvas::new(
        "Hello World!"
            .width(6)
            .height(2)
    ).back(Color::blue(false));

    // Render and update and print... rinse and repeat?
    termit.step(&mut (), &mut ui).await?;
    //                 \      |
    //   the mutable    |     |
    //   application    |     |
    //   model goes here      \- The UI to show off

    Ok(())
}

您还可以以裸形式使用 termit,仅用于渲染一些一次性内容

//! This is a minimal hello world termit example.
//!
//! There is no input processing, no async, no looping. Just print some nice screen.
use std::io;
use termit::prelude::*;
fn main() -> io::Result<()> {
    // The Termit facilitator:
    // * Use the Terminal to set up your terminal experience.
    // * Initialize Termit.
    let mut termit = Terminal::try_system_default()?.into_termit::<NoAppEvent>();

    // The TUI tree:
    // * Canvas sets the default style for the contained widget and fills space.
    // * "Hello World!" - &str/String - is a widget.
    // * We just place the text in the middle with some placement constraints.
    let mut ui = Canvas::new(
        "Hello World!"
            .width(6)
            .height(2)
    ).back(Color::blue(false));

    // render and update:
    termit.update(&mut (), Event::Refresh(0), &mut ui);
    //                  |   |                      |
    //   the mutable    |   |                       \- The UI to show off
    //   application    |   |
    // model goes here -/   |
    //                       \- this is an event,
    //                           here a refresh
    //
    // print       ,- the reduced scope (rectangular window)
    //            |     None => all of the screen
    termit.print(None)
}

asciicast

对于更复杂的示例,包括输入事件循环和自定义小部件,请参阅cargo run --example color。受 dreamer 项目 启发的简单 Delta.Chat 客户端称为 dech。还有更多 示例

这是 编辑器示例 的回放

asciicast

为什么?

回到基础,减少行绘制,更多内容和优雅。异步?

使用方法

在您的 Cargo.toml

[dependencies]
termit = "*"

请注意,API 仍在演变。欢迎反馈。符合人体工程学的静态 UI 初始化和动态 UI 更新有些矛盾。

箱子里有什么?

在系统方面

  • 异步输入解析到一个整洁的输入模型,该模型是从 crossterm 偷来的。
  • 同步输出,具有转义和样式。
  • TTY 终端 ioctl / Windows 控制台 WinRT 设置和配置。
  • 插入您自己的 IO 和控制。
  • 显式使用 IO = 没有单一的全局 tty/console 或全局设置。
  • 内部屏幕缓冲区努力减少 IO 量。
  • 关注点分离,有 UI,有模型,有应用。

在 UI 方面

  • 一个可组合的 widget 生态系统,鼓励您创建自己的。
  • 专注于创建一个漂亮的 UI,忘记终端和 IO 以及操作系统。
  • 颜色!不用担心哪种颜色适合哪种终端,我们兼容。如果您愿意,以 RGB 的形式表达,我们会确保它在 8 色终端上也能显示,嗯,在大约 8 种颜色中。
  • 样式!有一个方便的 API 来制作 Pretty 的事物,字面上的。
  • Windows 上的样式

在应用方面

  • Termit 帮助您处理应用循环、输入、打印...
  • 有偏见,但你可以按照自己的方式做一些事情,比如处理输入、打印。
  • 以异步为主 - 除了输出会复杂很多(如何从drop()运行异步终端清理?)。
  • 无需异步即可使用。
  • 通过禁用默认功能来减少依赖,即使移除了async-std、windows-sys、rustix...你仍然可以使用--no-default-features渲染UI。我们尽力优雅降级。
  • 可以通过TerminalEmulator与其他TUI库集成。让他们将ANSI输出打印到teemu,然后像termit小部件一样显示。
  • 可重用组件 - 只需输入解析器、屏幕缓冲区或终端初始化...

好奇心方面

  • 屏幕缓冲区位于匿名内存映射区域,避免频繁分配。 MmapVec的大小与其内容相同。
  • 我们以Rust风格优雅地封装了一些Windows控制台API调用。它调用新的windows-sys存储库和WinRT。
  • 我们使用rustix处理所有*nix ioctl事物。你可能能够在不使用libc的情况下编译,尚未测试。
  • 一些终端仿真支持 - 我们可以在窗口中运行其他终端应用程序并显示它们。
  • 对FPS敏感的应用程序具有恒定时间的刷新率 - 游戏。
  • 虚拟终端仿真仍不完整。

开发

  • 文档和示例
  • 测试覆盖率
  • 自动构建和测试

以及实际应用

  • 终端delta.chat客户端 - dech - 正在制作中,但已经开始聊天了...

非目标

  • 全面覆盖tty ioctl / windows控制台功能。我们将实现渲染UI和处理输入所必需的尽可能多的功能。
  • 大量现成的小部件库 - 请制作自己的存储库并联系我进行链接。

概念

Termit深受crosstermtui的启发。它希望在控制(ttys)和API易用性方面比crossterm有显著改进。由于crossterm存在许多设计缺陷,我们已经转向自己的跨平台实现。crossterm对于构建这个存储库至关重要。

Termit负责运行终端应用程序和构建TUI(基于文本的用户界面)的常见任务。如果你不希望全面覆盖所有终端/控制台功能,它可能是你所有“终端”事务的一站式商店。

异步

异步以与其他高效的异步应用程序融合。Termit也可以不使用异步。为了达到最佳匹配,你会将后端设计为消费命令的事件流。简单的应用程序可以设计为简单的静态状态和循环处理。

小部件保持自己的状态

与其他一些TUI库不同,小部件不是临时的,除非你想要它们是。你可以轻松实现一个有状态的Widget

use termit::prelude::*;
struct UpdateCounter {
    state: usize
}
impl<M, A: AppEvent> Widget<M, A> for UpdateCounter {
    fn update(
        &mut self,
        _model: &mut M,
        _input: &Event<A>,
        screen: &mut Screen,
        painter: &Painter,
    ) -> Window {
        self.state +=1;
        painter.paint(&format!("{}", self.state), screen, 0, false)
    }
}

这很有用,如果你的小部件跟踪多个对整个应用程序来说无关紧要的内部状态值。例如,文本框编辑器不需要将光标位置污染应用程序状态。

如果你想在每次更新周期中重新创建整个或部分UI树,你也可以这样做。然后你的小部件必须在模型中保持其状态。

小部件直接更新应用程序模型

小部件可以直接操作应用程序状态模型。

然而,这些更改应该是几乎立即发生的。更密集的工作(io、网络)应作为命令发送到应用程序,并独立处理,最好是异步处理。在dech示例中就是这样做的。

use termit::prelude::*;
struct AppState {
    state: usize
}
struct UpdateCounter;
impl Widget<AppState, ()> for UpdateCounter {
    fn update(
        &mut self,
        model: &mut AppState,
        _input: &Event<()>,
        screen: &mut Screen,
        painter: &Painter,
    ) -> Window {
        model.state +=1;
        painter.paint(&format!("{}", model.state), screen, 0, false)
    }
}

小部件通常可变地访问整个应用程序状态。但是,应该可以创建一个小部件适配器,该适配器将专注于模型的一个子集或完全另一个模型。

简单小部件、组合和装饰

避免创建复杂的小部件。相反,可以通过包装其他小部件在装饰器中或通过从其他小部件组合一个复杂子树来添加功能。

例如,查看crate::widget::WidgetBinder,它允许我们加载和保存任何小部件,小部件的任何属性或甚至在整个更新中完全替换它。它是一个为所有具有impl Widget的代码块实现的装饰器,并具有.bind_with()

# use termit::prelude::*;
# fn sample () {
struct AppState {
    banner: String
}
let mut bound_widget = Canvas::new(String::new()).back(Color::blue(false))
        .bind_with(
            |w,m:&AppState| *w.content_mut() =  m.banner.clone(),
            |w,m:&mut AppState| m.banner =  w.content().clone()
        );
# bound_widget.update(&mut AppState{banner:"I".to_string()}, &Event::<()>::Refresh(0), &mut Screen::default(), &Painter::default());
# }

crate::widget::WidgetPropertyBinder相同

# use termit::prelude::*;
# fn sample () {
struct AppState {
    banner: String
}
let mut bound_widget = Canvas::new(String::new()).back(Color::blue(false))
        .bind_property(|w| w.content_mut(), |m: &mut AppState| &mut m.banner);
# bound_widget.update(&mut AppState{banner:"I".to_string()}, &Event::<()>::Refresh(0), &mut Screen::default(), &Painter::default());
# }

公司

有一些其他流行的TUI库。Termit是Rust原生(没有尴尬的ffi到C* TUI库)。它旨在快速入门,同时在需要时提供高级控制。它还紧凑,试图避免冗余。

  • tui - 可能与termit最接近
  • rltk 也称为针对游戏设计的bracket库
  • ncurses - 遗留的ncurses包装器
  • cursive - 多个后端的抽象

依赖项

~4–14MB
~177K SLoC