环境
- Time 2022-08-16
- Rust 1.63.0
- Tui 0.18.0
前言
说明
参考:https://github.com/fdehau/tui-rs/blob/master/examples/table.rs
目标
使用 tui-rs
显示表格。
定义应用
struct App<'a> {state: TableState,items: Vec<Vec<&'a str>>,
}impl<'a> App<'a> {fn new() -> App<'a> {App {state: TableState::default(),items: vec![vec!["阿法骨化醇软胶囊", "20.4", "5"],vec!["阿卡波糖胶囊", "9.6", "4"],vec!["阿仑膦酸钠片", "20.05", "7"],vec!["阿莫西林胶囊", "11.66", "2"],vec!["阿普仑片", "4.93", "6"],vec!["阿奇霉素胶囊", "15.3\n14.3", "1"],vec!["阿昔洛韦片", "5.46", "8"],],}}pub fn next(&mut self) {let i = match self.state.selected() {Some(i) => {if i >= self.items.len() - 1 {0} else {i + 1}}None => 0,};self.state.select(Some(i));}pub fn previous(&mut self) {let i = match self.state.selected() {Some(i) => {if i == 0 {self.items.len() - 1} else {i - 1}}None => 0,};self.state.select(Some(i));}
}
ui
fn ui<B: Backend>(frame: &mut Frame<B>, app: &mut App) {let chunks = Layout::default().constraints([Constraint::Percentage(100)]).margin(5).split(frame.size());let selected = Style::default().add_modifier(Modifier::REVERSED);let normal_style = Style::default().bg(Color::Blue);let header_cells = ["名称", "单价", "数量"].iter().map(|header| widgets::Cell::from(*header).style(Style::default().fg(Color::Red)));let header = widgets::Row::new(header_cells).style(normal_style).height(1).bottom_margin(1);let rows = app.items.iter().map(|item| {let height = item.iter().map(|content| content.chars().filter(|c| *c == '\n').count()).max().unwrap_or_default()+ 1;let cells = item.iter().map(|c| widgets::Cell::from(*c));widgets::Row::new(cells).height(height as u16).bottom_margin(1)});let t = widgets::Table::new(rows).header(header).block(Block::default().borders(Borders::ALL).title("Table")).highlight_style(selected).highlight_symbol(">> ").widths(&[Constraint::Percentage(50),Constraint::Length(30),Constraint::Min(10),]);frame.render_stateful_widget(t, chunks[0], &mut app.state);
}
效果展示
总结
使用 tui-rs
渲染表格。
附录
源码
use anyhow::{Context, Result};
use crossterm::{event, terminal, ExecutableCommand};
use layout::{Constraint, Layout};
use style::{Color, Modifier, Style};
use tui::backend::{Backend, CrosstermBackend};
use tui::{layout, style, widgets, Frame, Terminal};
use widgets::{Block, Borders, TableState};pub fn main() -> Result<()> {terminal::enable_raw_mode()?;let mut backend = CrosstermBackend::new(std::io::stdout());backend.execute(terminal::EnterAlternateScreen)?.execute(terminal::Clear(terminal::ClearType::All))?.hide_cursor()?;let mut terminal = tui::Terminal::new(backend)?;run(&mut terminal)?;terminal::disable_raw_mode()?;terminal.backend_mut().execute(terminal::Clear(terminal::ClearType::All))?.execute(terminal::LeaveAlternateScreen)?.show_cursor().context("重置控制台失败")
}fn run<B: Backend>(terminal: &mut Terminal<B>) -> Result<()> {let timeout = std::time::Duration::from_millis(500);let mut app = App::new();loop {terminal.draw(|frame| ui(frame, &mut app))?;if event::poll(timeout)? {if let event::Event::Key(key) = event::read()? {use event::KeyCode::{self, Char, Esc};match key.code {Char('q') | Char('Q') | Esc => return Ok(()),KeyCode::Down => app.next(),KeyCode::Up => app.previous(),_ => {}}}}}
}struct App<'a> {state: TableState,items: Vec<Vec<&'a str>>,
}impl<'a> App<'a> {fn new() -> App<'a> {App {state: TableState::default(),items: vec![vec!["阿法骨化醇软胶囊", "20.4", "5"],vec!["阿卡波糖胶囊", "9.6", "4"],vec!["阿仑膦酸钠片", "20.05", "7"],vec!["阿莫西林胶囊", "11.66", "2"],vec!["阿普仑片", "4.93", "6"],vec!["阿奇霉素胶囊", "15.3\n14.3", "1"],vec!["阿昔洛韦片", "5.46", "8"],],}}pub fn next(&mut self) {let i = match self.state.selected() {Some(i) => {if i >= self.items.len() - 1 {0} else {i + 1}}None => 0,};self.state.select(Some(i));}pub fn previous(&mut self) {let i = match self.state.selected() {Some(i) => {if i == 0 {self.items.len() - 1} else {i - 1}}None => 0,};self.state.select(Some(i));}
}fn ui<B: Backend>(frame: &mut Frame<B>, app: &mut App) {let chunks = Layout::default().constraints([Constraint::Percentage(100)]).margin(5).split(frame.size());let selected = Style::default().add_modifier(Modifier::REVERSED);let normal_style = Style::default().bg(Color::Blue);let header_cells = ["名称", "单价", "数量"].iter().map(|header| widgets::Cell::from(*header).style(Style::default().fg(Color::Red)));let header = widgets::Row::new(header_cells).style(normal_style).height(1).bottom_margin(1);let rows = app.items.iter().map(|item| {let height = item.iter().map(|content| content.chars().filter(|c| *c == '\n').count()).max().unwrap_or_default()+ 1;let cells = item.iter().map(|c| widgets::Cell::from(*c));widgets::Row::new(cells).height(height as u16).bottom_margin(1)});let t = widgets::Table::new(rows).header(header).block(Block::default().borders(Borders::ALL).title("Table")).highlight_style(selected).highlight_symbol(">> ").widths(&[Constraint::Percentage(50),Constraint::Length(30),Constraint::Min(10),]);frame.render_stateful_widget(t, chunks[0], &mut app.state);
}