Colors (RGB)
Demonstrates the available RGB
Color options. These can be used
in any style field. Source: colors_rgb.rs. Uses a half block technique to render
two square-ish pixels in the space of a single rectangular terminal cell.
git clone https://github.com/ratatui/ratatui.git --branch latestcd ratatuicargo run --example=colors_rgb --features=crossterm
//! # [Ratatui] `Colors_RGB` 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
// This example shows the full range of RGB colors that can be displayed in the terminal.//// Requires a terminal that supports 24-bit color (true color) and unicode.//// This example also demonstrates how implementing the Widget trait on a mutable reference// allows the widget to update its state while it is being rendered. This allows the fps// widget to update the fps calculation and the colors widget to update a cached version of// the colors to render instead of recalculating them every frame.//// This is an alternative to using the `StatefulWidget` trait and a separate state struct. It// is useful when the state is only used by the widget and doesn't need to be shared with// other widgets.
use std::time::{Duration, Instant};
use color_eyre::Result;use palette::{convert::FromColorUnclamped, Okhsv, Srgb};use ratatui::{    buffer::Buffer,    crossterm::event::{self, Event, KeyCode, KeyEventKind},    layout::{Constraint, Layout, Position, Rect},    style::Color,    text::Text,    widgets::Widget,    DefaultTerminal,};
fn main() -> Result<()> {    color_eyre::install()?;    let terminal = ratatui::init();    let app_result = App::default().run(terminal);    ratatui::restore();    app_result}
#[derive(Debug, Default)]struct App {    /// The current state of the app (running or quit)    state: AppState,
    /// A widget that displays the current frames per second    fps_widget: FpsWidget,
    /// A widget that displays the full range of RGB colors that can be displayed in the terminal.    colors_widget: ColorsWidget,}
#[derive(Debug, Default, PartialEq, Eq)]enum AppState {    /// The app is running    #[default]    Running,
    /// The user has requested the app to quit    Quit,}
/// A widget that displays the current frames per second#[derive(Debug)]struct FpsWidget {    /// The number of elapsed frames that have passed - used to calculate the fps    frame_count: usize,
    /// The last instant that the fps was calculated    last_instant: Instant,
    /// The current frames per second    fps: Option<f32>,}
/// A widget that displays the full range of RGB colors that can be displayed in the terminal.////// This widget is animated and will change colors over time.#[derive(Debug, Default)]struct ColorsWidget {    /// The colors to render - should be double the height of the area as we render two rows of    /// pixels for each row of the widget using the half block character. This is computed any time    /// the size of the widget changes.    colors: Vec<Vec<Color>>,
    /// the number of elapsed frames that have passed - used to animate the colors by shifting the    /// x index by the frame number    frame_count: usize,}
impl App {    /// Run the app    ///    /// This is the main event loop for the app.    pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {        while self.is_running() {            terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;            self.handle_events()?;        }        Ok(())    }
    const fn is_running(&self) -> bool {        matches!(self.state, AppState::Running)    }
    /// Handle any events that have occurred since the last time the app was rendered.    ///    /// Currently, this only handles the q key to quit the app.    fn handle_events(&mut self) -> Result<()> {        // Ensure that the app only blocks for a period that allows the app to render at        // approximately 60 FPS (this doesn't account for the time to render the frame, and will        // also update the app immediately any time an event occurs)        let timeout = Duration::from_secs_f32(1.0 / 60.0);        if event::poll(timeout)? {            if let Event::Key(key) = event::read()? {                if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {                    self.state = AppState::Quit;                };            }        }        Ok(())    }}
/// Implement the Widget trait for &mut App so that it can be rendered////// This is implemented on a mutable reference so that the app can update its state while it is/// being rendered. This allows the fps widget to update the fps calculation and the colors widget/// to update the colors to render.impl Widget for &mut App {    fn render(self, area: Rect, buf: &mut Buffer) {        use Constraint::{Length, Min};        let [top, colors] = Layout::vertical([Length(1), Min(0)]).areas(area);        let [title, fps] = Layout::horizontal([Min(0), Length(8)]).areas(top);        Text::from("colors_rgb example. Press q to quit")            .centered()            .render(title, buf);        self.fps_widget.render(fps, buf);        self.colors_widget.render(colors, buf);    }}
/// Default impl for `FpsWidget`////// Manual impl is required because we need to initialize the `last_instant` field to the current/// instant.impl Default for FpsWidget {    fn default() -> Self {        Self {            frame_count: 0,            last_instant: Instant::now(),            fps: None,        }    }}
/// Widget impl for `FpsWidget`////// This is implemented on a mutable reference so that we can update the frame count and fps/// calculation while rendering.impl Widget for &mut FpsWidget {    fn render(self, area: Rect, buf: &mut Buffer) {        self.calculate_fps();        if let Some(fps) = self.fps {            let text = format!("{fps:.1} fps");            Text::from(text).render(area, buf);        }    }}
impl FpsWidget {    /// Update the fps calculation.    ///    /// This updates the fps once a second, but only if the widget has rendered at least 2 frames    /// since the last calculation. This avoids noise in the fps calculation when rendering on slow    /// machines that can't render at least 2 frames per second.    #[allow(clippy::cast_precision_loss)]    fn calculate_fps(&mut self) {        self.frame_count += 1;        let elapsed = self.last_instant.elapsed();        if elapsed > Duration::from_secs(1) && self.frame_count > 2 {            self.fps = Some(self.frame_count as f32 / elapsed.as_secs_f32());            self.frame_count = 0;            self.last_instant = Instant::now();        }    }}
/// Widget impl for `ColorsWidget`////// This is implemented on a mutable reference so that we can update the frame count and store a/// cached version of the colors to render instead of recalculating them every frame.impl Widget for &mut ColorsWidget {    /// Render the widget    fn render(self, area: Rect, buf: &mut Buffer) {        self.setup_colors(area);        let colors = &self.colors;        for (xi, x) in (area.left()..area.right()).enumerate() {            // animate the colors by shifting the x index by the frame number            let xi = (xi + self.frame_count) % (area.width as usize);            for (yi, y) in (area.top()..area.bottom()).enumerate() {                // render a half block character for each row of pixels with the foreground color                // set to the color of the pixel and the background color set to the color of the                // pixel below it                let fg = colors[yi * 2][xi];                let bg = colors[yi * 2 + 1][xi];                buf[Position::new(x, y)].set_char('▀').set_fg(fg).set_bg(bg);            }        }        self.frame_count += 1;    }}
impl ColorsWidget {    /// Setup the colors to render.    ///    /// This is called once per frame to setup the colors to render. It caches the colors so that    /// they don't need to be recalculated every frame.    #[allow(clippy::cast_precision_loss)]    fn setup_colors(&mut self, size: Rect) {        let Rect { width, height, .. } = size;        // double the height because each screen row has two rows of half block pixels        let height = height as usize * 2;        let width = width as usize;        // only update the colors if the size has changed since the last time we rendered        if self.colors.len() == height && self.colors[0].len() == width {            return;        }        self.colors = Vec::with_capacity(height);        for y in 0..height {            let mut row = Vec::with_capacity(width);            for x in 0..width {                let hue = x as f32 * 360.0 / width as f32;                let value = (height - y) as f32 / height as f32;                let saturation = Okhsv::max_saturation();                let color = Okhsv::new(hue, saturation, value);                let color = Srgb::<f32>::from_color_unclamped(color);                let color: Srgb<u8> = color.into_format();                let color = Color::Rgb(color.red, color.green, color.blue);                row.push(color);            }            self.colors.push(row);        }    }}