Rust终端UI开发库crosstermion的使用,跨平台终端控制与样式管理的高效解决方案

Rust终端UI开发库crosstermion的使用,跨平台终端控制与样式管理的高效解决方案

Crosstermion是一个实用工具库,用于统一termioncrossterm两个库的部分类型,使开发者能够轻松构建在Unix系统上使用轻量级termion库,而在Windows系统上使用crossterm库的应用程序。

当前提供的功能

  • Key类型和input_stream(异步)用于接收按键输入
  • AlternativeRawTerminal结合了替代屏幕和原始模式
  • 通过crosstermtermion后端创建tuitui-react终端的方法

如何实现颜色和样式?

使用tui

  • 使用tui时,将获得跨后端的原生颜色和样式支持

不使用tui

  • 使用**color**功能获取ansi_term的额外颜色工具
  • 或者使用ansi_termcoloredtermcolor也能正常工作

如何在Windows上使用crossterm而在Unix上使用termion

目前没有简单的方法,因为cargo总是会构建所有依赖项,即使它们不应该在你的平台上使用。这会导致termioncrossterm都被构建,这在Windows上是致命的。因此,在创建发布构建时,必须手动选择功能开关,即默认排除所有需要TUI的功能,让用户启用他们需要的功能。

compile_error!(...)宏可以用来通知用户是否需要功能选择。或者,确保即使没有选择任何后端也能编译通过。

最后,也可以直接选择总是针对crossterm进行编译。

功能

所有功能都是可叠加的,但如果它们相互排斥(例如tui-reacttui,或crosstermtermion),将选择更通用的功能。

相互排斥的功能

  • crossterm
    • 提供从crossbeam::event::KeyEventKey转换支持和AlternativeRawTerminal
    • 提供线程化的按键输入通道
    • input-async-crossterm(附加)
      • 为crossterm添加原生异步功能,由于mio无需额外线程
      • 注意线程化按键输入始终支持
  • termion
    • 提供从termion::event::KeyKey转换支持和AlternativeRawTerminal
    • 提供线程化的按键输入通道
    • input-async(附加)
      • 生成线程并通过futures Stream提供输入事件
      • 注意线程化按键输入始终支持

使用tui_(相互排斥)

  • tui-termion(隐含termion功能)
    • 结合tuitermion,提供带有termion后端的tui::Terminal
  • tui-crossterm(隐含crossterm功能)
    • 结合tuicrossterm,提供带有crossterm后端的tui::Terminal

使用tui-react(相互排斥)

  • tui-react-termion(隐含termion功能)
    • 结合tui-reacttermion,提供带有termion后端的tui::Terminal
  • tui-react-crossterm(隐含crossterm功能)
    • 结合tui-reactcrossterm,提供带有crossterm后端的tui::Terminal

color

  • 添加对基于ansi_term的条件着色的支持。该库小巧,简洁,允许零拷贝绘制字节和UTF-8字符串,同时支持Windows 10。

光标移动(相互排斥)

  • crossterm
    • 使用crossterm实现光标移动
  • termion
    • 使用termion实现光标移动

完整示例代码

use crosstermion::{input::Key, terminal::AlternativeRawTerminal};
use std::io::{stdout, Write};

fn main() -> std::io::Result<()> {
    // 创建替代屏幕和原始模式的终端
    let _terminal = AlternativeRawTerminal::new(stdout())?;
    
    // 简单的终端输出示例
    println!("Hello, Crosstermion!");
    println!("Press any key to exit...");
    
    // 等待按键输入
    let _key = Key::read()?;
    
    Ok(())
}

安装

在项目目录中运行以下Cargo命令:

cargo add crosstermion

或者将以下行添加到Cargo.toml:

crosstermion = "0.14.0"

更完整的示例代码

下面是一个更完整的示例,展示了如何使用crosstermion创建带颜色和样式的终端UI:

use crosstermion::{
    input::Key,
    terminal::{AlternativeRawTerminal, Clear, ClearType},
    style::{Color, Stylize},
};
use std::io::{stdout, Write};

fn main() -> std::io::Result<()> {
    // 创建替代屏幕和原始模式的终端
    let mut terminal = AlternativeRawTerminal::new(stdout())?;

    // 清除屏幕
    terminal.execute(Clear(ClearType::All))?;

    // 带颜色的文本输出
    writeln!(
        terminal,
        "{} {}",
        "Hello".with(Color::Red).bold(),
        "Crosstermion!".with(Color::Green).underline()
    )?;

    // 光标定位示例(需要启用cursor-move功能)
    #[cfg(feature = "cursor-move")]
    {
        terminal.execute(crosstermion::cursor::MoveTo(10, 10))?;
        writeln!(terminal, "Cursor moved to (10,10)")?;
    }

    // 等待按键输入
    println!("Press any key to exit...");
    let _key = Key::read()?;

    Ok(())
}

这个示例展示了:

  1. 创建原始模式终端
  2. 清除屏幕
  3. 使用颜色和样式输出文本
  4. 可选的光标移动功能
  5. 等待用户按键输入

要运行这个示例,需要在Cargo.toml中添加以下依赖:

crosstermion = { version = "0.14.0", features = ["color"] }

如果需要光标移动功能,可以添加:

crosstermion = { version = "0.14.0", features = ["color", "cursor-move"] }

1 回复

Rust终端UI开发库crosstermion的使用

介绍

crosstermion是一个基于crossterm的Rust库,提供了跨平台的终端控制与样式管理功能。它简化了终端UI开发,支持Windows、macOS和Linux系统,是构建命令行界面(CLI)应用程序的强大工具。

主要特性

  • 跨平台支持
  • 终端样式控制(颜色、背景、文本属性)
  • 光标操作和定位
  • 终端输入处理
  • 屏幕缓冲区管理
  • 异步支持

使用方法

添加依赖

首先在Cargo.toml中添加依赖:

[dependencies]
crosstermion = "0.10"

基本示例

use crosstermion::{
    color, cursor,
    event::{read, Event, KeyCode},
    execute,
    style::{Print, Stylize},
    terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
    Result,
};

fn main() -> Result<()> {
    // 启用原始模式以获得更好的控制
    enable_raw_mode()?;
    
    // 使用execute!宏执行多个命令
    execute!(
        std::io::stdout(),
        Clear(ClearType::All),            // 清屏
        cursor::MoveTo(5, 5),             // 移动光标到(5,5)
        Print("Hello".red().on_blue()),   // 红色文字蓝色背景
        cursor::MoveToNextLine(1),        // 下移一行
        Print("Press 'q' to quit".bold()) // 加粗文字
    )?;
    
    // 事件循环
    loop {
        if let Event::Key(key) = read()? {
            if key.code == KeyCode::Char('q') {
                break;
            }
        }
    }
    
    // 恢复终端
    disable_raw_mode()?;
    Ok(())
}

更复杂的UI示例

use crosstermion::{
    color::{self, Color},
    cursor,
    execute,
    style::{Print, Stylize},
    terminal::{Clear, ClearType},
    Result,
};

fn draw_menu() -> Result<()> {
    let items = ["Option 1", "Option 2", "Option 3", "Exit"];
    
    execute!(
        std::io::stdout(),
        Clear(CClearType::All),
        cursor::MoveTo(10, 5),
        Print("Main Menu".bold().underlined()),
    )?;
    
    for (i, item) in items.iter().enumerate() {
        let y = 7 + i as u16;
        execute!(
            std::io::stdout(),
            cursor::MoveTo(10, y),
            Print(format!("{}. {}", i + 1, item))
        )?;
    }
    
    Ok(())
}

fn main() -> Result<()> {
    draw_menu()?;
    Ok(())
}

处理用户输入

use crosstermion::{
    event::{read, Event, KeyCode},
    Result,
};

fn handle_input() -> Result<()> {
    loop {
        match read()? {
            Event::Key(key) => match key.code {
                KeyCode::Char('1') => println!("Option 1 selected"),
                KeyCode::Char('2') => println!("Option 2 selected"),
                KeyCode::Char('3') => println!("Option 3 selected"),
                KeyCode::Char('q') | KeyCode::Esc => break,
                _ => {}
            },
            _ => {}
        }
    }
    Ok(())
}

样式组合

use crosstermion::{execute, style::{Print, Stylize}};

fn apply_styles() -> crosstermion::Result<()> {
    execute!(
        std::io::stdout(),
        Print("Bold and underlined".bold().underlined()),
        Print("\n"),
        Print("Red on yellow".red().on_yellow()),
        Print("\n"),
        Print("Blinking text".blink())
    )?;
    Ok(())
}

高级功能

使用屏幕缓冲区

use crosstermion::{
    terminal::{EnterAlternateScreen, LeaveAlternateScreen},
    execute
};

fn alternate_screen() -> crosstermion::Result<()> {
    execute!(std::io::stdout(), EnterAlternateScreen)?;
    
    // 在这里绘制你的UI
    
    execute!(std::io::stdout(), LeaveAlternateScreen)?;
    Ok(())
}

异步支持

use crosstermion::{
    event::{poll, read, Event, KeyCode},
    terminal,
    Result,
};
use std::time::Duration;

async fn async_input() -> Result<()> {
    terminal::enable_raw_mode()?;
    
    loop {
        if poll(Duration::from_millis(100))? {
            if let Event::Key(key) = read()? {
                if key.code == KeyCode::Char('q') {
                    break;
                }
            }
        }
    }
    
    terminal::disable_raw_mode()?;
    Ok(())
}

完整示例demo

下面是一个结合了上述功能的完整终端应用示例:

use crosstermion::{
    color,
    cursor,
    event::{poll, read, Event, KeyCode},
    execute,
    style::{Print, Stylize},
    terminal::{
        disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
        Clear, ClearType,
    },
    Result,
};
use std::time::Duration;

// 主菜单结构
struct Menu {
    items: Vec<String>,
    selected: usize,
}

impl Menu {
    fn new(items: Vec<&str>) -> Self {
        Menu {
            items: items.iter().map(|s| s.to_string()).collect(),
            selected: 0,
        }
    }

    // 绘制菜单
    fn draw(&self) -> Result<()> {
        execute!(
            std::io::stdout(),
            Clear(ClearType::All),
            cursor::MoveTo(10, 5),
            Print("Main Menu".bold().underlined().cyan()),
        )?;

        for (i, item) in self.items.iter().enumerate() {
            let y = 7 + i as u16;
            let style = if i == self.selected {
                item.on_white().black()
            } else {
                item.stylize()
            };
            
            execute!(
                std::io::stdout(),
                cursor::MoveTo(10, y),
                Print(format!("> {}. {}", i + 1, item)),
                style
            )?;
        }

        execute!(
            std::io::stdout(),
            cursor::MoveTo(10, 12),
            Print("Use arrow keys to navigate, Enter to select, Q to quit".dim())
        )?;

        Ok(())
    }

    // 处理键盘输入
    fn handle_input(&mut self) -> Result<bool> {
        if poll(Duration::from_millis(100))? {
            if let Event::Key(key) = read()? {
                match key.code {
                    KeyCode::Up => {
                        if self.selected > 0 {
                            self.selected -= 1;
                        }
                    }
                    KeyCode::Down => {
                        if self.selected < self.items.len() - 1 {
                            self.selected += 1;
                        }
                    }
                    KeyCode::Enter => {
                        execute!(
                            std::io::stdout(),
                            Clear(ClearType::All),
                            cursor::MoveTo(10, 5),
                            Print(format!("You selected: {}", self.items[self.selected]).green()),
                            cursor::MoveTo(10, 7),
                            Print("Press any key to continue...".yellow())
                        )?;
                        read()?;
                        return Ok(true);
                    }
                    KeyCode::Char('q') | KeyCode::Esc => return Ok(false),
                    _ => {}
                }
            }
        }
        Ok(true)
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    // 初始化终端
    enable_raw_mode()?;
    execute!(std::io::stdout(), EnterAlternateScreen)?;

    // 创建菜单
    let mut menu = Menu::new(vec![
        "Open File",
        "Save File",
        "Settings",
        "Exit Program",
    ]);

    // 主循环
    loop {
        menu.draw()?;
        if !menu.handle_input()? {
            break;
        }
    }

    // 清理终端
    execute!(std::io::stdout(), LeaveAlternateScreen)?;
    disable_raw_mode()?;
    Ok(())
}

注意事项

  1. 使用原始模式时(enable_raw_mode),确保在程序退出前调用disable_raw_mode恢复终端
  2. 对于复杂UI,考虑使用EnterAlternateScreenLeaveAlternateScreen来防止污染用户终端
  3. 错误处理很重要,确保正确处理所有可能的终端操作错误

crosstermion提供了强大而灵活的功能来创建丰富的终端应用程序,从简单的文本样式到复杂的交互式UI都可以轻松实现。

回到顶部