Demo
This is the original demo example from the main README. It is available for each of the backends.
git clone https://github.com/ratatui/ratatui.git --branch latestcd ratatuicargo run --example=demo --features=crosstermcargo run --example=demo --no-default-features --features=termioncargo run --example=demo --no-default-features --features=termwiz
//! # [Ratatui] Original Demo example//!//! The latest version of this example is available in the [examples] folder in the repository.//!//! Please note that the examples are designed to be run against the `main` branch of the Github//! repository. This means that you may not be able to compile with the latest release version on//! crates.io, or the one that you have installed locally.//!//! See the [examples readme] for more information on finding examples that match the version of the//! library you are using.//!//! [Ratatui]: https://github.com/ratatui/ratatui//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use std::{error::Error, time::Duration};
use argh::FromArgs;
mod app;#[cfg(feature = "crossterm")]mod crossterm;#[cfg(all(not(windows), feature = "termion"))]mod termion;#[cfg(feature = "termwiz")]mod termwiz;
mod ui;
/// Demo#[derive(Debug, FromArgs)]struct Cli {    /// time in ms between two ticks.    #[argh(option, default = "250")]    tick_rate: u64,    /// whether unicode symbols are used to improve the overall look of the app    #[argh(option, default = "true")]    enhanced_graphics: bool,}
fn main() -> Result<(), Box<dyn Error>> {    let cli: Cli = argh::from_env();    let tick_rate = Duration::from_millis(cli.tick_rate);    #[cfg(feature = "crossterm")]    crate::crossterm::run(tick_rate, cli.enhanced_graphics)?;    #[cfg(all(not(windows), feature = "termion", not(feature = "crossterm")))]    crate::termion::run(tick_rate, cli.enhanced_graphics)?;    #[cfg(all(        feature = "termwiz",        not(feature = "crossterm"),        not(feature = "termion")    ))]    crate::termwiz::run(tick_rate, cli.enhanced_graphics)?;    Ok(())}use rand::{    distributions::{Distribution, Uniform},    rngs::ThreadRng,};use ratatui::widgets::ListState;
const TASKS: [&str; 24] = [    "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", "Item10",    "Item11", "Item12", "Item13", "Item14", "Item15", "Item16", "Item17", "Item18", "Item19",    "Item20", "Item21", "Item22", "Item23", "Item24",];
const LOGS: [(&str, &str); 26] = [    ("Event1", "INFO"),    ("Event2", "INFO"),    ("Event3", "CRITICAL"),    ("Event4", "ERROR"),    ("Event5", "INFO"),    ("Event6", "INFO"),    ("Event7", "WARNING"),    ("Event8", "INFO"),    ("Event9", "INFO"),    ("Event10", "INFO"),    ("Event11", "CRITICAL"),    ("Event12", "INFO"),    ("Event13", "INFO"),    ("Event14", "INFO"),    ("Event15", "INFO"),    ("Event16", "INFO"),    ("Event17", "ERROR"),    ("Event18", "ERROR"),    ("Event19", "INFO"),    ("Event20", "INFO"),    ("Event21", "WARNING"),    ("Event22", "INFO"),    ("Event23", "INFO"),    ("Event24", "WARNING"),    ("Event25", "INFO"),    ("Event26", "INFO"),];
const EVENTS: [(&str, u64); 24] = [    ("B1", 9),    ("B2", 12),    ("B3", 5),    ("B4", 8),    ("B5", 2),    ("B6", 4),    ("B7", 5),    ("B8", 9),    ("B9", 14),    ("B10", 15),    ("B11", 1),    ("B12", 0),    ("B13", 4),    ("B14", 6),    ("B15", 4),    ("B16", 6),    ("B17", 4),    ("B18", 7),    ("B19", 13),    ("B20", 8),    ("B21", 11),    ("B22", 9),    ("B23", 3),    ("B24", 5),];
#[derive(Clone)]pub struct RandomSignal {    distribution: Uniform<u64>,    rng: ThreadRng,}
impl RandomSignal {    pub fn new(lower: u64, upper: u64) -> Self {        Self {            distribution: Uniform::new(lower, upper),            rng: rand::thread_rng(),        }    }}
impl Iterator for RandomSignal {    type Item = u64;    fn next(&mut self) -> Option<u64> {        Some(self.distribution.sample(&mut self.rng))    }}
#[derive(Clone)]pub struct SinSignal {    x: f64,    interval: f64,    period: f64,    scale: f64,}
impl SinSignal {    pub const fn new(interval: f64, period: f64, scale: f64) -> Self {        Self {            x: 0.0,            interval,            period,            scale,        }    }}
impl Iterator for SinSignal {    type Item = (f64, f64);    fn next(&mut self) -> Option<Self::Item> {        let point = (self.x, (self.x * 1.0 / self.period).sin() * self.scale);        self.x += self.interval;        Some(point)    }}
pub struct TabsState<'a> {    pub titles: Vec<&'a str>,    pub index: usize,}
impl<'a> TabsState<'a> {    pub fn new(titles: Vec<&'a str>) -> Self {        Self { titles, index: 0 }    }    pub fn next(&mut self) {        self.index = (self.index + 1) % self.titles.len();    }
    pub fn previous(&mut self) {        if self.index > 0 {            self.index -= 1;        } else {            self.index = self.titles.len() - 1;        }    }}
pub struct StatefulList<T> {    pub state: ListState,    pub items: Vec<T>,}
impl<T> StatefulList<T> {    pub fn with_items(items: Vec<T>) -> Self {        Self {            state: ListState::default(),            items,        }    }
    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));    }}
pub struct Signal<S: Iterator> {    source: S,    pub points: Vec<S::Item>,    tick_rate: usize,}
impl<S> Signal<S>where    S: Iterator,{    fn on_tick(&mut self) {        self.points.drain(0..self.tick_rate);        self.points            .extend(self.source.by_ref().take(self.tick_rate));    }}
pub struct Signals {    pub sin1: Signal<SinSignal>,    pub sin2: Signal<SinSignal>,    pub window: [f64; 2],}
impl Signals {    fn on_tick(&mut self) {        self.sin1.on_tick();        self.sin2.on_tick();        self.window[0] += 1.0;        self.window[1] += 1.0;    }}
pub struct Server<'a> {    pub name: &'a str,    pub location: &'a str,    pub coords: (f64, f64),    pub status: &'a str,}
pub struct App<'a> {    pub title: &'a str,    pub should_quit: bool,    pub tabs: TabsState<'a>,    pub show_chart: bool,    pub progress: f64,    pub sparkline: Signal<RandomSignal>,    pub tasks: StatefulList<&'a str>,    pub logs: StatefulList<(&'a str, &'a str)>,    pub signals: Signals,    pub barchart: Vec<(&'a str, u64)>,    pub servers: Vec<Server<'a>>,    pub enhanced_graphics: bool,}
impl<'a> App<'a> {    pub fn new(title: &'a str, enhanced_graphics: bool) -> Self {        let mut rand_signal = RandomSignal::new(0, 100);        let sparkline_points = rand_signal.by_ref().take(300).collect();        let mut sin_signal = SinSignal::new(0.2, 3.0, 18.0);        let sin1_points = sin_signal.by_ref().take(100).collect();        let mut sin_signal2 = SinSignal::new(0.1, 2.0, 10.0);        let sin2_points = sin_signal2.by_ref().take(200).collect();        App {            title,            should_quit: false,            tabs: TabsState::new(vec!["Tab0", "Tab1", "Tab2"]),            show_chart: true,            progress: 0.0,            sparkline: Signal {                source: rand_signal,                points: sparkline_points,                tick_rate: 1,            },            tasks: StatefulList::with_items(TASKS.to_vec()),            logs: StatefulList::with_items(LOGS.to_vec()),            signals: Signals {                sin1: Signal {                    source: sin_signal,                    points: sin1_points,                    tick_rate: 5,                },                sin2: Signal {                    source: sin_signal2,                    points: sin2_points,                    tick_rate: 10,                },                window: [0.0, 20.0],            },            barchart: EVENTS.to_vec(),            servers: vec![                Server {                    name: "NorthAmerica-1",                    location: "New York City",                    coords: (40.71, -74.00),                    status: "Up",                },                Server {                    name: "Europe-1",                    location: "Paris",                    coords: (48.85, 2.35),                    status: "Failure",                },                Server {                    name: "SouthAmerica-1",                    location: "São Paulo",                    coords: (-23.54, -46.62),                    status: "Up",                },                Server {                    name: "Asia-1",                    location: "Singapore",                    coords: (1.35, 103.86),                    status: "Up",                },            ],            enhanced_graphics,        }    }
    pub fn on_up(&mut self) {        self.tasks.previous();    }
    pub fn on_down(&mut self) {        self.tasks.next();    }
    pub fn on_right(&mut self) {        self.tabs.next();    }
    pub fn on_left(&mut self) {        self.tabs.previous();    }
    pub fn on_key(&mut self, c: char) {        match c {            'q' => {                self.should_quit = true;            }            't' => {                self.show_chart = !self.show_chart;            }            _ => {}        }    }
    pub fn on_tick(&mut self) {        // Update progress        self.progress += 0.001;        if self.progress > 1.0 {            self.progress = 0.0;        }
        self.sparkline.on_tick();        self.signals.on_tick();
        let log = self.logs.items.pop().unwrap();        self.logs.items.insert(0, log);
        let event = self.barchart.pop().unwrap();        self.barchart.insert(0, event);    }}use ratatui::{    layout::{Constraint, Layout, Rect},    style::{Color, Modifier, Style},    symbols,    text::{self, Span},    widgets::{        canvas::{self, Canvas, Circle, Map, MapResolution, Rectangle},        Axis, BarChart, Block, Cell, Chart, Dataset, Gauge, LineGauge, List, ListItem, Paragraph,        Row, Sparkline, Table, Tabs, Wrap,    },    Frame,};
use crate::app::App;
pub fn draw(frame: &mut Frame, app: &mut App) {    let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]).split(frame.area());    let tabs = app        .tabs        .titles        .iter()        .map(|t| text::Line::from(Span::styled(*t, Style::default().fg(Color::Green))))        .collect::<Tabs>()        .block(Block::bordered().title(app.title))        .highlight_style(Style::default().fg(Color::Yellow))        .select(app.tabs.index);    frame.render_widget(tabs, chunks[0]);    match app.tabs.index {        0 => draw_first_tab(frame, app, chunks[1]),        1 => draw_second_tab(frame, app, chunks[1]),        2 => draw_third_tab(frame, app, chunks[1]),        _ => {}    };}
fn draw_first_tab(frame: &mut Frame, app: &mut App, area: Rect) {    let chunks = Layout::vertical([        Constraint::Length(9),        Constraint::Min(8),        Constraint::Length(7),    ])    .split(area);    draw_gauges(frame, app, chunks[0]);    draw_charts(frame, app, chunks[1]);    draw_text(frame, chunks[2]);}
fn draw_gauges(frame: &mut Frame, app: &mut App, area: Rect) {    let chunks = Layout::vertical([        Constraint::Length(2),        Constraint::Length(3),        Constraint::Length(2),    ])    .margin(1)    .split(area);    let block = Block::bordered().title("Graphs");    frame.render_widget(block, area);
    let label = format!("{:.2}%", app.progress * 100.0);    let gauge = Gauge::default()        .block(Block::new().title("Gauge:"))        .gauge_style(            Style::default()                .fg(Color::Magenta)                .bg(Color::Black)                .add_modifier(Modifier::ITALIC | Modifier::BOLD),        )        .use_unicode(app.enhanced_graphics)        .label(label)        .ratio(app.progress);    frame.render_widget(gauge, chunks[0]);
    let sparkline = Sparkline::default()        .block(Block::new().title("Sparkline:"))        .style(Style::default().fg(Color::Green))        .data(&app.sparkline.points)        .bar_set(if app.enhanced_graphics {            symbols::bar::NINE_LEVELS        } else {            symbols::bar::THREE_LEVELS        });    frame.render_widget(sparkline, chunks[1]);
    let line_gauge = LineGauge::default()        .block(Block::new().title("LineGauge:"))        .filled_style(Style::default().fg(Color::Magenta))        .line_set(if app.enhanced_graphics {            symbols::line::THICK        } else {            symbols::line::NORMAL        })        .ratio(app.progress);    frame.render_widget(line_gauge, chunks[2]);}
#[allow(clippy::too_many_lines)]fn draw_charts(frame: &mut Frame, app: &mut App, area: Rect) {    let constraints = if app.show_chart {        vec![Constraint::Percentage(50), Constraint::Percentage(50)]    } else {        vec![Constraint::Percentage(100)]    };    let chunks = Layout::horizontal(constraints).split(area);    {        let chunks = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)])            .split(chunks[0]);        {            let chunks =                Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])                    .split(chunks[0]);
            // Draw tasks            let tasks: Vec<ListItem> = app                .tasks                .items                .iter()                .map(|i| ListItem::new(vec![text::Line::from(Span::raw(*i))]))                .collect();            let tasks = List::new(tasks)                .block(Block::bordered().title("List"))                .highlight_style(Style::default().add_modifier(Modifier::BOLD))                .highlight_symbol("> ");            frame.render_stateful_widget(tasks, chunks[0], &mut app.tasks.state);
            // Draw logs            let info_style = Style::default().fg(Color::Blue);            let warning_style = Style::default().fg(Color::Yellow);            let error_style = Style::default().fg(Color::Magenta);            let critical_style = Style::default().fg(Color::Red);            let logs: Vec<ListItem> = app                .logs                .items                .iter()                .map(|&(evt, level)| {                    let s = match level {                        "ERROR" => error_style,                        "CRITICAL" => critical_style,                        "WARNING" => warning_style,                        _ => info_style,                    };                    let content = vec![text::Line::from(vec![                        Span::styled(format!("{level:<9}"), s),                        Span::raw(evt),                    ])];                    ListItem::new(content)                })                .collect();            let logs = List::new(logs).block(Block::bordered().title("List"));            frame.render_stateful_widget(logs, chunks[1], &mut app.logs.state);        }
        let barchart = BarChart::default()            .block(Block::bordered().title("Bar chart"))            .data(&app.barchart)            .bar_width(3)            .bar_gap(2)            .bar_set(if app.enhanced_graphics {                symbols::bar::NINE_LEVELS            } else {                symbols::bar::THREE_LEVELS            })            .value_style(                Style::default()                    .fg(Color::Black)                    .bg(Color::Green)                    .add_modifier(Modifier::ITALIC),            )            .label_style(Style::default().fg(Color::Yellow))            .bar_style(Style::default().fg(Color::Green));        frame.render_widget(barchart, chunks[1]);    }    if app.show_chart {        let x_labels = vec![            Span::styled(                format!("{}", app.signals.window[0]),                Style::default().add_modifier(Modifier::BOLD),            ),            Span::raw(format!(                "{}",                (app.signals.window[0] + app.signals.window[1]) / 2.0            )),            Span::styled(                format!("{}", app.signals.window[1]),                Style::default().add_modifier(Modifier::BOLD),            ),        ];        let datasets = vec![            Dataset::default()                .name("data2")                .marker(symbols::Marker::Dot)                .style(Style::default().fg(Color::Cyan))                .data(&app.signals.sin1.points),            Dataset::default()                .name("data3")                .marker(if app.enhanced_graphics {                    symbols::Marker::Braille                } else {                    symbols::Marker::Dot                })                .style(Style::default().fg(Color::Yellow))                .data(&app.signals.sin2.points),        ];        let chart = Chart::new(datasets)            .block(                Block::bordered().title(Span::styled(                    "Chart",                    Style::default()                        .fg(Color::Cyan)                        .add_modifier(Modifier::BOLD),                )),            )            .x_axis(                Axis::default()                    .title("X Axis")                    .style(Style::default().fg(Color::Gray))                    .bounds(app.signals.window)                    .labels(x_labels),            )            .y_axis(                Axis::default()                    .title("Y Axis")                    .style(Style::default().fg(Color::Gray))                    .bounds([-20.0, 20.0])                    .labels([                        Span::styled("-20", Style::default().add_modifier(Modifier::BOLD)),                        Span::raw("0"),                        Span::styled("20", Style::default().add_modifier(Modifier::BOLD)),                    ]),            );        frame.render_widget(chart, chunks[1]);    }}
fn draw_text(frame: &mut Frame, area: Rect) {    let text = vec![        text::Line::from("This is a paragraph with several lines. You can change style your text the way you want"),        text::Line::from(""),        text::Line::from(vec![            Span::from("For example: "),            Span::styled("under", Style::default().fg(Color::Red)),            Span::raw(" "),            Span::styled("the", Style::default().fg(Color::Green)),            Span::raw(" "),            Span::styled("rainbow", Style::default().fg(Color::Blue)),            Span::raw("."),        ]),        text::Line::from(vec![            Span::raw("Oh and if you didn't "),            Span::styled("notice", Style::default().add_modifier(Modifier::ITALIC)),            Span::raw(" you can "),            Span::styled("automatically", Style::default().add_modifier(Modifier::BOLD)),            Span::raw(" "),            Span::styled("wrap", Style::default().add_modifier(Modifier::REVERSED)),            Span::raw(" your "),            Span::styled("text", Style::default().add_modifier(Modifier::UNDERLINED)),            Span::raw(".")        ]),        text::Line::from(            "One more thing is that it should display unicode characters: 10€"        ),    ];    let block = Block::bordered().title(Span::styled(        "Footer",        Style::default()            .fg(Color::Magenta)            .add_modifier(Modifier::BOLD),    ));    let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true });    frame.render_widget(paragraph, area);}
fn draw_second_tab(frame: &mut Frame, app: &mut App, area: Rect) {    let chunks =        Layout::horizontal([Constraint::Percentage(30), Constraint::Percentage(70)]).split(area);    let up_style = Style::default().fg(Color::Green);    let failure_style = Style::default()        .fg(Color::Red)        .add_modifier(Modifier::RAPID_BLINK | Modifier::CROSSED_OUT);    let rows = app.servers.iter().map(|s| {        let style = if s.status == "Up" {            up_style        } else {            failure_style        };        Row::new(vec![s.name, s.location, s.status]).style(style)    });    let table = Table::new(        rows,        [            Constraint::Length(15),            Constraint::Length(15),            Constraint::Length(10),        ],    )    .header(        Row::new(vec!["Server", "Location", "Status"])            .style(Style::default().fg(Color::Yellow))            .bottom_margin(1),    )    .block(Block::bordered().title("Servers"));    frame.render_widget(table, chunks[0]);
    let map = Canvas::default()        .block(Block::bordered().title("World"))        .paint(|ctx| {            ctx.draw(&Map {                color: Color::White,                resolution: MapResolution::High,            });            ctx.layer();            ctx.draw(&Rectangle {                x: 0.0,                y: 30.0,                width: 10.0,                height: 10.0,                color: Color::Yellow,            });            ctx.draw(&Circle {                x: app.servers[2].coords.1,                y: app.servers[2].coords.0,                radius: 10.0,                color: Color::Green,            });            for (i, s1) in app.servers.iter().enumerate() {                for s2 in &app.servers[i + 1..] {                    ctx.draw(&canvas::Line {                        x1: s1.coords.1,                        y1: s1.coords.0,                        y2: s2.coords.0,                        x2: s2.coords.1,                        color: Color::Yellow,                    });                }            }            for server in &app.servers {                let color = if server.status == "Up" {                    Color::Green                } else {                    Color::Red                };                ctx.print(                    server.coords.1,                    server.coords.0,                    Span::styled("X", Style::default().fg(color)),                );            }        })        .marker(if app.enhanced_graphics {            symbols::Marker::Braille        } else {            symbols::Marker::Dot        })        .x_bounds([-180.0, 180.0])        .y_bounds([-90.0, 90.0]);    frame.render_widget(map, chunks[1]);}
fn draw_third_tab(frame: &mut Frame, _app: &mut App, area: Rect) {    let chunks = Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).split(area);    let colors = [        Color::Reset,        Color::Black,        Color::Red,        Color::Green,        Color::Yellow,        Color::Blue,        Color::Magenta,        Color::Cyan,        Color::Gray,        Color::DarkGray,        Color::LightRed,        Color::LightGreen,        Color::LightYellow,        Color::LightBlue,        Color::LightMagenta,        Color::LightCyan,        Color::White,    ];    let items: Vec<Row> = colors        .iter()        .map(|c| {            let cells = vec![                Cell::from(Span::raw(format!("{c:?}: "))),                Cell::from(Span::styled("Foreground", Style::default().fg(*c))),                Cell::from(Span::styled("Background", Style::default().bg(*c))),            ];            Row::new(cells)        })        .collect();    let table = Table::new(        items,        [            Constraint::Ratio(1, 3),            Constraint::Ratio(1, 3),            Constraint::Ratio(1, 3),        ],    )    .block(Block::bordered().title("Colors"));    frame.render_widget(table, chunks[0]);}use std::{    error::Error,    io,    time::{Duration, Instant},};
use ratatui::{    backend::{Backend, CrosstermBackend},    crossterm::{        event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},        execute,        terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},    },    Terminal,};
use crate::{app::App, ui};
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {    // setup terminal    enable_raw_mode()?;    let mut stdout = io::stdout();    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;    let backend = CrosstermBackend::new(stdout);    let mut terminal = Terminal::new(backend)?;
    // create app and run it    let app = App::new("Crossterm Demo", enhanced_graphics);    let app_result = run_app(&mut terminal, app, tick_rate);
    // restore terminal    disable_raw_mode()?;    execute!(        terminal.backend_mut(),        LeaveAlternateScreen,        DisableMouseCapture    )?;    terminal.show_cursor()?;
    if let Err(err) = app_result {        println!("{err:?}");    }
    Ok(())}
fn run_app<B: Backend>(    terminal: &mut Terminal<B>,    mut app: App,    tick_rate: Duration,) -> io::Result<()> {    let mut last_tick = Instant::now();    loop {        terminal.draw(|frame| ui::draw(frame, &mut app))?;
        let timeout = tick_rate.saturating_sub(last_tick.elapsed());        if event::poll(timeout)? {            if let Event::Key(key) = event::read()? {                if key.kind == KeyEventKind::Press {                    match key.code {                        KeyCode::Left | KeyCode::Char('h') => app.on_left(),                        KeyCode::Up | KeyCode::Char('k') => app.on_up(),                        KeyCode::Right | KeyCode::Char('l') => app.on_right(),                        KeyCode::Down | KeyCode::Char('j') => app.on_down(),                        KeyCode::Char(c) => app.on_key(c),                        _ => {}                    }                }            }        }        if last_tick.elapsed() >= tick_rate {            app.on_tick();            last_tick = Instant::now();        }        if app.should_quit {            return Ok(());        }    }}#![allow(dead_code)]use std::{error::Error, io, sync::mpsc, thread, time::Duration};
use ratatui::{    backend::{Backend, TermionBackend},    termion::{        event::Key,        input::{MouseTerminal, TermRead},        raw::IntoRawMode,        screen::IntoAlternateScreen,    },    Terminal,};
use crate::{app::App, ui};
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {    // setup terminal    let stdout = io::stdout()        .into_raw_mode()        .unwrap()        .into_alternate_screen()        .unwrap();    let stdout = MouseTerminal::from(stdout);    let backend = TermionBackend::new(stdout);    let mut terminal = Terminal::new(backend)?;
    // create app and run it    let app = App::new("Termion demo", enhanced_graphics);    run_app(&mut terminal, app, tick_rate)?;
    Ok(())}
fn run_app<B: Backend>(    terminal: &mut Terminal<B>,    mut app: App,    tick_rate: Duration,) -> Result<(), Box<dyn Error>> {    let events = events(tick_rate);    loop {        terminal.draw(|frame| ui::draw(frame, &mut app))?;
        match events.recv()? {            Event::Input(key) => match key {                Key::Up | Key::Char('k') => app.on_up(),                Key::Down | Key::Char('j') => app.on_down(),                Key::Left | Key::Char('h') => app.on_left(),                Key::Right | Key::Char('l') => app.on_right(),                Key::Char(c) => app.on_key(c),                _ => {}            },            Event::Tick => app.on_tick(),        }        if app.should_quit {            return Ok(());        }    }}
enum Event {    Input(Key),    Tick,}
fn events(tick_rate: Duration) -> mpsc::Receiver<Event> {    let (tx, rx) = mpsc::channel();    let keys_tx = tx.clone();    thread::spawn(move || {        let stdin = io::stdin();        for key in stdin.keys().flatten() {            if let Err(err) = keys_tx.send(Event::Input(key)) {                eprintln!("{err}");                return;            }        }    });    thread::spawn(move || loop {        if let Err(err) = tx.send(Event::Tick) {            eprintln!("{err}");            break;        }        thread::sleep(tick_rate);    });    rx}#![allow(dead_code)]use std::{    error::Error,    time::{Duration, Instant},};
use ratatui::{    backend::TermwizBackend,    termwiz::{        input::{InputEvent, KeyCode},        terminal::Terminal as TermwizTerminal,    },    Terminal,};
use crate::{app::App, ui};
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {    let backend = TermwizBackend::new()?;    let mut terminal = Terminal::new(backend)?;    terminal.hide_cursor()?;
    // create app and run it    let app = App::new("Termwiz Demo", enhanced_graphics);    let app_result = run_app(&mut terminal, app, tick_rate);
    terminal.show_cursor()?;    terminal.flush()?;
    if let Err(err) = app_result {        println!("{err:?}");    }
    Ok(())}
fn run_app(    terminal: &mut Terminal<TermwizBackend>,    mut app: App,    tick_rate: Duration,) -> Result<(), Box<dyn Error>> {    let mut last_tick = Instant::now();    loop {        terminal.draw(|frame| ui::draw(frame, &mut app))?;
        let timeout = tick_rate.saturating_sub(last_tick.elapsed());        if let Some(input) = terminal            .backend_mut()            .buffered_terminal_mut()            .terminal()            .poll_input(Some(timeout))?        {            match input {                InputEvent::Key(key_code) => match key_code.key {                    KeyCode::UpArrow | KeyCode::Char('k') => app.on_up(),                    KeyCode::DownArrow | KeyCode::Char('j') => app.on_down(),                    KeyCode::LeftArrow | KeyCode::Char('h') => app.on_left(),                    KeyCode::RightArrow | KeyCode::Char('l') => app.on_right(),                    KeyCode::Char(c) => app.on_key(c),                    _ => {}                },                InputEvent::Resized { cols, rows } => {                    terminal                        .backend_mut()                        .buffered_terminal_mut()                        .resize(cols, rows);                }                _ => {}            }        }
        if last_tick.elapsed() >= tick_rate {            app.on_tick();            last_tick = Instant::now();        }        if app.should_quit {            return Ok(());        }    }}