Canvas
Demonstrates the Canvas widget
and related shapes in the
canvas module.
git clone https://github.com/ratatui/ratatui.git --branch latestcd ratatuicargo run --example=canvas --features=crossterm
//! # [Ratatui] Canvas 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::time::{Duration, Instant};
use color_eyre::Result;use ratatui::{    crossterm::event::{self, Event, KeyCode},    layout::{Constraint, Layout, Rect},    style::{Color, Stylize},    symbols::Marker,    widgets::{        canvas::{Canvas, Circle, Map, MapResolution, Rectangle},        Block, Widget,    },    DefaultTerminal, Frame,};
fn main() -> Result<()> {    color_eyre::install()?;    let terminal = ratatui::init();    let app_result = App::new().run(terminal);    ratatui::restore();    app_result}
struct App {    x: f64,    y: f64,    ball: Circle,    playground: Rect,    vx: f64,    vy: f64,    tick_count: u64,    marker: Marker,}
impl App {    fn new() -> Self {        Self {            x: 0.0,            y: 0.0,            ball: Circle {                x: 20.0,                y: 40.0,                radius: 10.0,                color: Color::Yellow,            },            playground: Rect::new(10, 10, 200, 100),            vx: 1.0,            vy: 1.0,            tick_count: 0,            marker: Marker::Dot,        }    }
    pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {        let tick_rate = Duration::from_millis(16);        let mut last_tick = Instant::now();        loop {            terminal.draw(|frame| self.draw(frame))?;            let timeout = tick_rate.saturating_sub(last_tick.elapsed());            if event::poll(timeout)? {                if let Event::Key(key) = event::read()? {                    match key.code {                        KeyCode::Char('q') => break Ok(()),                        KeyCode::Down | KeyCode::Char('j') => self.y += 1.0,                        KeyCode::Up | KeyCode::Char('k') => self.y -= 1.0,                        KeyCode::Right | KeyCode::Char('l') => self.x += 1.0,                        KeyCode::Left | KeyCode::Char('h') => self.x -= 1.0,                        _ => {}                    }                }            }
            if last_tick.elapsed() >= tick_rate {                self.on_tick();                last_tick = Instant::now();            }        }    }
    fn on_tick(&mut self) {        self.tick_count += 1;        // only change marker every 180 ticks (3s) to avoid stroboscopic effect        if (self.tick_count % 180) == 0 {            self.marker = match self.marker {                Marker::Dot => Marker::Braille,                Marker::Braille => Marker::Block,                Marker::Block => Marker::HalfBlock,                Marker::HalfBlock => Marker::Bar,                Marker::Bar => Marker::Dot,            };        }        // bounce the ball by flipping the velocity vector        let ball = &self.ball;        let playground = self.playground;        if ball.x - ball.radius < f64::from(playground.left())            || ball.x + ball.radius > f64::from(playground.right())        {            self.vx = -self.vx;        }        if ball.y - ball.radius < f64::from(playground.top())            || ball.y + ball.radius > f64::from(playground.bottom())        {            self.vy = -self.vy;        }
        self.ball.x += self.vx;        self.ball.y += self.vy;    }
    fn draw(&self, frame: &mut Frame) {        let horizontal =            Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]);        let vertical = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]);        let [map, right] = horizontal.areas(frame.area());        let [pong, boxes] = vertical.areas(right);
        frame.render_widget(self.map_canvas(), map);        frame.render_widget(self.pong_canvas(), pong);        frame.render_widget(self.boxes_canvas(boxes), boxes);    }
    fn map_canvas(&self) -> impl Widget + '_ {        Canvas::default()            .block(Block::bordered().title("World"))            .marker(self.marker)            .paint(|ctx| {                ctx.draw(&Map {                    color: Color::Green,                    resolution: MapResolution::High,                });                ctx.print(self.x, -self.y, "You are here".yellow());            })            .x_bounds([-180.0, 180.0])            .y_bounds([-90.0, 90.0])    }
    fn pong_canvas(&self) -> impl Widget + '_ {        Canvas::default()            .block(Block::bordered().title("Pong"))            .marker(self.marker)            .paint(|ctx| {                ctx.draw(&self.ball);            })            .x_bounds([10.0, 210.0])            .y_bounds([10.0, 110.0])    }
    fn boxes_canvas(&self, area: Rect) -> impl Widget {        let left = 0.0;        let right = f64::from(area.width);        let bottom = 0.0;        let top = f64::from(area.height).mul_add(2.0, -4.0);        Canvas::default()            .block(Block::bordered().title("Rects"))            .marker(self.marker)            .x_bounds([left, right])            .y_bounds([bottom, top])            .paint(|ctx| {                for i in 0..=11 {                    ctx.draw(&Rectangle {                        x: f64::from(i * i + 3 * i) / 2.0 + 2.0,                        y: 2.0,                        width: f64::from(i),                        height: f64::from(i),                        color: Color::Red,                    });                    ctx.draw(&Rectangle {                        x: f64::from(i * i + 3 * i) / 2.0 + 2.0,                        y: 21.0,                        width: f64::from(i),                        height: f64::from(i),                        color: Color::Blue,                    });                }                for i in 0..100 {                    if i % 10 != 0 {                        ctx.print(f64::from(i) + 1.0, 0.0, format!("{i}", i = i % 10));                    }                    if i % 2 == 0 && i % 10 != 0 {                        ctx.print(0.0, f64::from(i), format!("{i}", i = i % 10));                    }                }            })    }}