widget_impl/widget_impl.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
//! # [Ratatui] Widgets implementation examples
//!
//! This example demonstrates various ways to implement widget traits in Ratatui on a type, a
//! reference, and a mutable reference. It also shows how to use the `WidgetRef` trait to render
//! boxed widgets.
//!
//! 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-org/ratatui
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
use std::time::{Duration, Instant};
use color_eyre::Result;
use crossterm::event::{self, Event, KeyCode};
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Position, Rect, Size},
style::{Color, Style},
widgets::{Widget, WidgetRef},
DefaultTerminal,
};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = App::default().run(terminal);
ratatui::restore();
result
}
#[derive(Default)]
struct App {
should_quit: bool,
timer: Timer,
#[cfg(feature = "unstable-widget-ref")]
boxed_squares: BoxedSquares,
green_square: RightAlignedSquare,
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
while !self.should_quit {
self.draw(&mut terminal)?;
self.handle_events()?;
}
Ok(())
}
fn draw(&mut self, tui: &mut DefaultTerminal) -> Result<()> {
tui.draw(|frame| frame.render_widget(self, frame.area()))?;
Ok(())
}
fn handle_events(&mut self) -> Result<()> {
// Handle events at least 50 frames per second (gifs are usually 50fps)
let timeout = Duration::from_secs_f64(1.0 / 50.0);
if !event::poll(timeout)? {
return Ok(());
}
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
_ => {}
}
}
Ok(())
}
}
/// Implement the `Widget` trait on a mutable reference to the `App` type.
///
/// This allows the `App` type to be rendered as a widget. The `App` type owns several other widgets
/// that are rendered as part of the app. The `Widget` trait is implemented on a mutable reference
/// to the `App` type, which allows this to be rendered without consuming the `App` type, and allows
/// the sub-widgets to be mutable.
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
let constraints = Constraint::from_lengths([1, 1, 2, 1]);
let [greeting, timer, squares, position] = Layout::vertical(constraints).areas(area);
// render an ephemeral greeting widget
Greeting::new("Ratatui!").render(greeting, buf);
// render a reference to the timer widget
self.timer.render(timer, buf);
// render a boxed widget containing red and blue squares
#[cfg(feature = "unstable-widget-ref")]
self.boxed_squares.render(squares, buf);
// render a mutable reference to the green square widget
self.green_square.render(squares, buf);
// Display the dynamically updated position of the green square
let square_position = format!("Green square is at {}", self.green_square.last_position);
square_position.render(position, buf);
}
}
/// An ephemeral greeting widget.
///
/// This widget is implemented on the type itself, which means that it is consumed when it is
/// rendered. This is useful for widgets that are cheap to create, don't need to be reused, and
/// don't need to store any state between renders. This is the simplest way to implement a widget in
/// Ratatui, but in most cases, it is better to implement the `Widget` trait on a reference to the
/// type, as shown in the other examples below.
///
/// This was the way most widgets were implemented in Ratatui before `Widget` was implemented on
/// references in [PR #903] (merged in Ratatui 0.26.0).
///
/// [PR #903]: https://github.com/ratatui-org/ratatui/pull/903
struct Greeting {
name: String,
}
impl Greeting {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
}
impl Widget for Greeting {
fn render(self, area: Rect, buf: &mut Buffer) {
let greeting = format!("Hello, {}!", self.name);
greeting.render(area, buf);
}
}
/// A timer widget that displays the elapsed time since the timer was started.
#[derive(Debug)]
struct Timer {
start: Instant,
}
impl Default for Timer {
fn default() -> Self {
Self {
start: Instant::now(),
}
}
}
/// This implements `Widget` on a reference to the type, which means that it can be reused and
/// doesn't need to be consumed when it is rendered. This is useful for widgets that need to store
/// state and be updated over time.
///
/// This approach was probably always available in Ratatui, but it wasn't widely used until `Widget`
/// was implemented on references in [PR #903] (merged in Ratatui 0.26.0). This is because all the
/// built-in widgets previously would consume themselves when rendered.
impl Widget for &Timer {
fn render(self, area: Rect, buf: &mut Buffer) {
let elapsed = self.start.elapsed().as_secs_f32();
let message = format!("Elapsed: {elapsed:.1?}s");
message.render(area, buf);
}
}
/// A widget that contains a list of several different widgets.
struct BoxedSquares {
squares: Vec<Box<dyn WidgetRef>>,
}
impl Default for BoxedSquares {
fn default() -> Self {
let red_square: Box<dyn WidgetRef> = Box::new(RedSquare);
let blue_square: Box<dyn WidgetRef> = Box::new(BlueSquare);
Self {
squares: vec![red_square, blue_square],
}
}
}
/// A widget that renders a red square.
struct RedSquare;
/// A widget that renders a blue square.
struct BlueSquare;
/// This implements the `Widget` trait on a reference to the type. It contains a list of boxed
/// widgets that implement the `WidgetRef` trait. This is useful for widgets that contain a list of
/// other widgets that can be different types.
impl Widget for &BoxedSquares {
fn render(self, area: Rect, buf: &mut Buffer) {
let constraints = vec![Constraint::Length(4); self.squares.len()];
let areas = Layout::horizontal(constraints).split(area);
for (widget, area) in self.squares.iter().zip(areas.iter()) {
widget.render_ref(*area, buf);
}
}
}
/// `RedSquare` and `BlueSquare` are widgets that render a red and blue square, respectively. They
/// implement the `WidgetRef` trait instead of the `Widget` trait, which which allows them to be
/// rendered as boxed widgets. It's not possible to use Widget for this as a dynamic reference to a
/// widget cannot generally be moved out of the box.
impl WidgetRef for RedSquare {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
fill(area, buf, "█", Color::Red);
}
}
impl WidgetRef for BlueSquare {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
fill(area, buf, "█", Color::Blue);
}
}
/// A widget that renders a green square aligned to the right of the area.
#[derive(Default)]
struct RightAlignedSquare {
last_position: Position,
}
/// This widget is implemented on a mutable reference to the type, which means that it can store
/// state and update it when it is rendered. This is useful for widgets that need to store the
/// result of some calculation that can only be done when the widget is rendered.
///
/// The x and y coordinates of the square are stored in the widget and updated when the widget is
/// rendered. This allows the square to be aligned to the right of the area. These coordinates could
/// be used to perform hit testing (e.g. checking if a mouse click is inside the square). This app
/// just displays the coordinates as a string.
///
/// This approach was probably always available in Ratatui, but it wasn't widely used either. This
/// is an alternative to implementing the `StatefulWidget` trait, for situations where you want to
/// store the state in the widget itself instead of a separate struct.
impl Widget for &mut RightAlignedSquare {
/// Render a green square aligned to the right of the area and store the position.
fn render(self, area: Rect, buf: &mut Buffer) {
const WIDTH: u16 = 4;
let x = area.right() - WIDTH; // Align to the right
self.last_position = Position { x, y: area.y };
let size = Size::new(WIDTH, area.height);
let area = Rect::from((self.last_position, size));
fill(area, buf, "█", Color::Green);
}
}
/// Fill the area with the specified symbol and style.
///
/// This probably should be a method on the `Buffer` type, but it is defined here for simplicity.
/// <https://github.com/ratatui-org/ratatui/issues/1146>
fn fill<S: Into<Style>>(area: Rect, buf: &mut Buffer, symbol: &str, style: S) {
let style = style.into();
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
buf[(x, y)].set_symbol(symbol).set_style(style);
}
}
}