#tui #ratatui #crossterm #窗口管理器 #GUI #窗口

bin+lib tuiwindow

为 Ratatui+Crossterm TUI 应用程序提供最简窗口和焦点管理器

2 个版本

0.1.1 2024 年 3 月 2 日
0.1.0 2024 年 2 月 29 日

#2521 in 命令行工具

48 每月下载次数

自定义许可

60KB
1.5K SLoC

tui-window

RatatuiCrossterm 提供最简页面和焦点管理器(尽管它也支持 Ratatui 支持的其他后端)。

tui-window 提供一个非常简单的设置,让您可以快速构建简单、基于页面/页面的 TUI(基于文本的用户界面)应用程序。

其灵感来源于 HTML 的工作方式:声明一个小部件树,并由 tui-window 管理应用程序级别的关注点,如 焦点管理、将输入重定向到特定小部件等。

特性

  • 声明性地构建 TUI 布局。定义一个组件树,仅在需要精细控制渲染过程时才计算布局。
  • 具有观点的焦点管理:计算可聚焦小部件的顺序(例如,按 Tab 键聚焦到下一个元素)。
  • 创建“页面”(小部件树集合)并在它们之间轻松导航。
  • 支持原生的 Ratatui 小部件。
  • 初始化 Ratatui 和 Crossterm 的实用程序,具有开箱即用的恐慌处理。

状态

此库正在积极开发中。请随时提出改进建议(请注意,此库的范围故意很小)。

入门

使用 Cargo 将 tui-window 安装到您的项目中

cargo add tuiwindow

通过实现 Render 构建 few 小部件(用于不接受焦点的组件)或 FocusableRender(用于应接受焦点的组件)(不需要派生 Default


#[derive(Default)]
struct TestWidget {
    text_content: String,
}

impl FocusableRender for TestWidget {
    fn render(&mut self, render_props: &RenderProps, buff: &mut Buffer, area: Rect) {
        if let Some(InputEvent::Key(c)) = render_props.event {
            self.text_content.push(c)
        }
        Paragraph::new(format!(
            "Hello world! Focused? {}: {}",
            render_props.is_focused, self.text_content
        ))
        .block(
            Block::new()
                .borders(Borders::all())
                .style(if render_props.is_focused {
                    Style::new().fg(Color::Red)
                } else {
                    Style::new()
                }),
        )
        .wrap(Wrap { trim: false })
        .render(area, buff)
    }
}

#[derive(Default)]
struct StaticWidget {}

impl Render for StaticWidget {
    fn render(&mut self, _render_props: &RenderProps, buff: &mut Buffer, area: Rect) {
        Paragraph::new("I'm static")
            .block(Block::new().borders(Borders::all()))
            .render(area, buff)
    }
}

定义您的应用程序结构(您可以设计整个页面)

    let mut app = PageCollection::new(vec![
        Page::new(
            "Page 1", // the page's title
            '1', // a shortcut for navigating to this page
            row_widget!( // macro for evenly-distributing your widgets in rows
                SlowWidget::default(),
                column_widget!(StaticWidget {}, TestWidget::default())
            ),
        ),
        Page::new(
            "Page 2",
            '2',
            column_widget!( // macro for evenly-distributing your widgets in
                AnotherWidget::default(),   //columns
                column_widget!(StaticWidget {}, TestWidget::default())
            ),
        )
        .with_style(Style::default().bg(Color::White).fg(Color::Black)),
    ]);

在您的 main 函数中将所有内容组合在一起,使用提供的辅助程序 TuiCrossterm 设置渲染

fn main() -> Result<(), Box<dyn Error>> {
    let mut tui = TuiCrossterm::new()?;
    let terminal = tui.setup()?;

    // define the collection of pages:
    let mut app = PageCollection::new(vec![
        Page::new(
            "Page 1",
            '1',
            row_widget!(
                SlowWidget::default(),
                column_widget!(StaticWidget {}, TestWidget::default())
            ),
        ),
        Page::new(
            "Page 2",
            '2',
            column_widget!(
                AnotherWidget::default(),
                column_widget!(StaticWidget {}, TestWidget::default())
            ),
        )
        .with_style(Style::default().bg(Color::White).fg(Color::Black)),
    ]);

    let mut window = Window::new(&app, |ev| match ev {
        // define the termination condition for the app:
        tuiwindow::core::InputEvent::Key(c) => *c == 'q',
        _ => false,
    });

    while !window.is_finished() {
        terminal.draw(|f| {
            let area = f.size();
            let buff = f.buffer_mut();

            let mut second_buff = buff.clone();
            // draw
            window.render::<DefaultEventMapper>(&mut app, &mut second_buff, area);

            buff.merge(&second_buff);
        })?;
    }

    Ok(())

}

依赖关系

~6–12MB
~120K SLoC