From ed071f37232fae47a2193235d57934cc5c678baa Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 4 Dec 2024 13:34:12 -0800 Subject: [PATCH] docs: add mouse-drawing example (#1546) Demonstrates how to handle mouse events --- Cargo.lock | 20 ++++ examples/README.md | 4 + examples/apps/mouse-drawing/Cargo.toml | 17 ++++ examples/apps/mouse-drawing/README.md | 9 ++ examples/apps/mouse-drawing/src/main.rs | 123 ++++++++++++++++++++++++ 5 files changed, 173 insertions(+) create mode 100644 examples/apps/mouse-drawing/Cargo.toml create mode 100644 examples/apps/mouse-drawing/README.md create mode 100644 examples/apps/mouse-drawing/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index ab95edda..6bbb7a7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1561,6 +1561,15 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "line_drawing" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1478a313008a3e6c8149995e90a99ee9094034b5c5c3da1eeb81183cb61d1d" +dependencies = [ + "num-traits", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1680,6 +1689,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mouse-drawing" +version = "0.0.0" +dependencies = [ + "color-eyre", + "crossterm", + "line_drawing", + "rand 0.8.5", + "ratatui", +] + [[package]] name = "nix" version = "0.26.4" diff --git a/examples/README.md b/examples/README.md index 218bbb6e..f096592c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -48,3 +48,7 @@ This is the original demo example from the main README. It is available for each This is the demo example from the main README and crate page. [Source](./apps/demo2/). ![Demo2](https://github.com/ratatui/ratatui/blob/images/examples/demo2.gif?raw=true) + +## Mouse Drawing demo + +Shows how to handle mouse events. [Source](./apps/mouse-drawing/). diff --git a/examples/apps/mouse-drawing/Cargo.toml b/examples/apps/mouse-drawing/Cargo.toml new file mode 100644 index 00000000..a447e5aa --- /dev/null +++ b/examples/apps/mouse-drawing/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mouse-drawing" +publish = false +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[dependencies] +color-eyre.workspace = true +crossterm.workspace = true +## a collection of line drawing algorithms (e.g. Bresenham's line algorithm) +line_drawing = "1.0.0" +rand = "0.8.5" +ratatui.workspace = true + +[lints] +workspace = true diff --git a/examples/apps/mouse-drawing/README.md b/examples/apps/mouse-drawing/README.md new file mode 100644 index 00000000..ef971898 --- /dev/null +++ b/examples/apps/mouse-drawing/README.md @@ -0,0 +1,9 @@ +# Mouse drawing demo + +This example shows how to receive mouse and handle mouse events. + +To run this demo: + +```shell +cargo run -p mouse-drawing +``` diff --git a/examples/apps/mouse-drawing/src/main.rs b/examples/apps/mouse-drawing/src/main.rs new file mode 100644 index 00000000..f4d82ab4 --- /dev/null +++ b/examples/apps/mouse-drawing/src/main.rs @@ -0,0 +1,123 @@ +/// A Ratatui example that demonstrates how to handle mouse events. +/// +/// This example demonstrates how to handle mouse events in Ratatui. You can draw lines by +/// clicking and dragging the mouse. +/// +/// This example runs with the Ratatui library code in the branch that you are currently +/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui +/// release. +/// +/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest +use color_eyre::Result; +use crossterm::{ + event::{ + self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, MouseEvent, + MouseEventKind, + }, + execute, +}; +use ratatui::{ + layout::{Position, Rect, Size}, + style::{Color, Stylize}, + symbols, + text::Line, + DefaultTerminal, Frame, +}; + +fn main() -> Result<()> { + color_eyre::install()?; + let terminal = ratatui::init(); + let result = MouseDrawingApp::default().run(terminal); + ratatui::restore(); + result +} + +#[derive(Default)] +struct MouseDrawingApp { + // Whether the app should exit + pub should_exit: bool, + // The last known mouse position + pub mouse_position: Option, + // The points that have been clicked / drawn by dragging the mouse + pub points: Vec<(Position, Color)>, + // The color to draw with + pub current_color: Color, +} + +impl MouseDrawingApp { + fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + execute!(std::io::stdout(), EnableMouseCapture)?; + while !self.should_exit { + terminal.draw(|frame| self.render(frame))?; + self.handle_events()?; + } + execute!(std::io::stdout(), DisableMouseCapture)?; + Ok(()) + } + + fn handle_events(&mut self) -> Result<()> { + match event::read()? { + Event::Key(event) => self.on_key_event(event), + Event::Mouse(event) => self.on_mouse_event(event), + _ => {} + } + Ok(()) + } + + /// Quit the app if the user presses 'q' or 'Esc' + fn on_key_event(&mut self, event: KeyEvent) { + match event.code { + KeyCode::Char(' ') => { + self.current_color = Color::Rgb(rand::random(), rand::random(), rand::random()); + } + KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true, + _ => {} + } + } + + /// Adds any points which were clicked or dragged to the `points` vector. + fn on_mouse_event(&mut self, event: MouseEvent) { + let position = Position::new(event.column, event.row); + match event.kind { + MouseEventKind::Down(_) => self.points.push((position, self.current_color)), + MouseEventKind::Drag(_) => self.draw_line(position), + _ => {} + } + self.mouse_position = Some(position); + } + + /// Draw a line between the last point and the given position + fn draw_line(&mut self, position: Position) { + if let Some(start) = self.points.last() { + let (x0, y0) = (i32::from(start.0.x), i32::from(start.0.y)); + let (x1, y1) = (i32::from(position.x), i32::from(position.y)); + for (x, y) in line_drawing::Bresenham::new((x0, y0), (x1, y1)) { + let point = (Position::new(x as u16, y as u16), self.current_color); + self.points.push(point); + } + } + } + + fn render(&self, frame: &mut Frame) { + // call order is important here as later elements are drawn on top of earlier elements + self.render_points(frame); + self.render_mouse_cursor(frame); + let value = "Mouse Example ('Esc' to quit. Click / drag to draw. 'Space' to change color)"; + let title = Line::from(value).centered(); + frame.render_widget(title, frame.area()); + } + + fn render_points(&self, frame: &mut Frame<'_>) { + for (position, color) in &self.points { + let area = Rect::from((*position, Size::new(1, 1))).clamp(frame.area()); + frame.render_widget(symbols::block::FULL.fg(*color), area); + } + } + + fn render_mouse_cursor(&self, frame: &mut Frame<'_>) { + if let Some(position) = self.mouse_position { + let area = Rect::from((position, Size::new(1, 1))).clamp(frame.area()); + frame.render_widget("╳".bg(self.current_color), area); + } + } +}