From 809617739825e73a8bddc700919f520555a0df7c Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 20 Sep 2023 10:53:59 -0700 Subject: [PATCH] fix: more tweaks based on PR comments https://github.com/ratatui-org/ratatui/pull/500#pullrequestreview-1630556339 - add comments - remove dead code (in colors and weather) - make RGB swatch into a widget - simplify the RGB logic in the swatch / weather gauge - move layout() helper function to root module - remove unused keyboard bindings - remove unused imports in theme - remove unused lipsum crate - turn on doc scraping --- Cargo.toml | 4 +- examples/demo2/app.rs | 2 - examples/demo2/colors.rs | 78 +++++++++++-------------------- examples/demo2/main.rs | 1 + examples/demo2/root.rs | 24 ++++++++-- examples/demo2/tabs/about.rs | 8 +++- examples/demo2/tabs/email.rs | 4 +- examples/demo2/tabs/recipe.rs | 4 +- examples/demo2/tabs/traceroute.rs | 10 ++-- examples/demo2/tabs/weather.rs | 37 ++++----------- examples/demo2/term.rs | 20 -------- examples/demo2/theme.rs | 2 - 12 files changed, 75 insertions(+), 119 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 720f606e3..a97acfc89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,6 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [ ] } criterion = { version = "0.5.1", features = ["html_reports"] } fakeit = "1.1" -lipsum = "0.9.0" rand = "0.8.5" palette = "0.7.3" pretty_assertions = "1.4.0" @@ -154,8 +153,7 @@ doc-scrape-examples = false [[example]] name = "demo2" required-features = ["crossterm", "widget-calendar"] -# this runs for all of the terminal backends, so it can't be built using --all-features or scraped -doc-scrape-examples = false +doc-scrape-examples = true [[example]] name = "gauge" diff --git a/examples/demo2/app.rs b/examples/demo2/app.rs index 7a5d0956a..fcf8a0196 100644 --- a/examples/demo2/app.rs +++ b/examples/demo2/app.rs @@ -76,8 +76,6 @@ impl App { context.tab_index = context.tab_index.saturating_add(1) % TAB_COUNT; context.row_index = 0; } - KeyCode::Left | KeyCode::Char('h') => {} - KeyCode::Right | KeyCode::Char('l') => {} KeyCode::Up | KeyCode::Char('k') => { context.row_index = context.row_index.saturating_sub(1); } diff --git a/examples/demo2/colors.rs b/examples/demo2/colors.rs index d82caeedb..1e6d1be4d 100644 --- a/examples/demo2/colors.rs +++ b/examples/demo2/colors.rs @@ -1,60 +1,34 @@ -use palette::{ - convert::{FromColorUnclamped, IntoColorUnclamped}, - Okhsv, Srgb, -}; +use palette::{IntoColor, Okhsv, Srgb}; use ratatui::{prelude::*, widgets::*}; -#[allow(dead_code)] -fn render_16_color_swatch(area: Rect, buf: &mut Buffer) { - let sym = "██"; - Paragraph::new(vec![ - Line::from(vec![sym.black(), sym.red(), sym.green(), sym.yellow()]), - Line::from(vec![sym.blue(), sym.magenta(), sym.cyan(), sym.gray()]), - Line::from(vec![ - sym.dark_gray(), - sym.light_red(), - sym.light_green(), - sym.light_yellow(), - ]), - Line::from(vec![ - sym.light_blue(), - sym.light_magenta(), - sym.light_cyan(), - sym.white(), - ]), - ]) - .render(area, buf); -} +/// A widget that renders a color swatch of RGB colors. +/// +/// The widget is rendered as a rectangle with the hue changing along the x-axis from 0.0 to 360.0 +/// and the value changing along the y-axis (from 1.0 to 0.0). Each pixel is rendered as a block +/// character with the top half slightly lighter than the bottom half. +pub struct RgbSwatch; -#[allow(dead_code)] -fn render_256_color_swatch(area: Rect, buf: &mut Buffer) { - for (xi, x) in (16..52).zip(area.left()..area.right()) { - for (yi, y) in (0..3).zip(area.top()..area.bottom()) { - let fg = Color::Indexed(yi * 72 + xi); - let bg = Color::Indexed(yi * 72 + xi + 36); - buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg); +impl Widget for RgbSwatch { + fn render(self, area: Rect, buf: &mut Buffer) { + for (yi, y) in (area.top()..area.bottom()).enumerate() { + let value = area.height as f32 - yi as f32; + let value_fg = value / (area.height as f32); + let value_bg = (value - 0.5) / (area.height as f32); + for (xi, x) in (area.left()..area.right()).enumerate() { + let hue = xi as f32 * 360.0 / area.width as f32; + let fg = color_from_oklab(hue, Okhsv::max_saturation(), value_fg); + let bg = color_from_oklab(hue, Okhsv::max_saturation(), value_bg); + buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg); + } } - let fg = Color::Indexed(xi.saturating_add(216)); - buf.get_mut(x, area.bottom() - 1).set_char('█').set_fg(fg); } } -pub fn render_rgb_swatch(area: Rect, buf: &mut Buffer) { - for (xi, x) in (area.left()..area.right()).enumerate() { - for (yi, y) in (area.top()..area.bottom()).enumerate() { - let yi = area.height as usize - yi - 1; - let hue = xi as f32 * 360.0 / area.width as f32; - let value_bg = (yi as f32 - 0.0) / (area.height as f32); - let value_fg = (yi as f32 + 0.5) / (area.height as f32); - let fg = Okhsv::::new(hue, Okhsv::max_saturation(), value_fg); - let fg: Srgb = fg.into_color_unclamped(); - let fg: Srgb = fg.into_format(); - let fg = Color::Rgb(fg.red, fg.green, fg.blue); - let bg = Okhsv::new(hue, Okhsv::max_saturation(), value_bg); - let bg = Srgb::::from_color_unclamped(bg); - let bg: Srgb = bg.into_format(); - let bg = Color::Rgb(bg.red, bg.green, bg.blue); - buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg); - } - } +/// Convert a hue and value into an RGB color via the OkLab color space. +/// +/// See https://bottosson.github.io/posts/oklab/ for more details. +pub fn color_from_oklab(hue: f32, saturation: f32, value: f32) -> Color { + let color: Srgb = Okhsv::new(hue, saturation, value).into_color(); + let color = color.into_format(); + Color::Rgb(color.red, color.green, color.blue) } diff --git a/examples/demo2/main.rs b/examples/demo2/main.rs index bafa8e901..14c75d654 100644 --- a/examples/demo2/main.rs +++ b/examples/demo2/main.rs @@ -1,5 +1,6 @@ use anyhow::Result; pub use app::*; +pub use colors::*; pub use root::*; pub use term::*; pub use theme::*; diff --git a/examples/demo2/root.rs b/examples/demo2/root.rs index 805aa269e..14b60dd67 100644 --- a/examples/demo2/root.rs +++ b/examples/demo2/root.rs @@ -1,7 +1,9 @@ +use std::rc::Rc; + use itertools::Itertools; use ratatui::{prelude::*, widgets::*}; -use crate::{layout, tabs::*, AppContext, THEME}; +use crate::{tabs::*, AppContext, THEME}; pub struct Root<'a> { context: &'a AppContext, @@ -53,8 +55,6 @@ impl Root<'_> { let keys = [ ("Q/Esc", "Quit"), ("Tab", "Next Tab"), - ("←/h", "Left"), - ("→/l", "Right"), ("↑/k", "Up"), ("↓/j", "Down"), ]; @@ -73,3 +73,21 @@ impl Root<'_> { .render(area, buf); } } + +/// simple helper method to split an area into multiple sub-areas +pub fn layout(area: Rect, direction: Direction, heights: Vec) -> Rc<[Rect]> { + let constraints = heights + .iter() + .map(|&h| { + if h > 0 { + Constraint::Length(h) + } else { + Constraint::Min(0) + } + }) + .collect_vec(); + Layout::default() + .direction(direction) + .constraints(constraints) + .split(area) +} diff --git a/examples/demo2/tabs/about.rs b/examples/demo2/tabs/about.rs index 73d22ed56..bd9610dbe 100644 --- a/examples/demo2/tabs/about.rs +++ b/examples/demo2/tabs/about.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use ratatui::{prelude::*, widgets::*}; -use crate::{colors, layout, THEME}; +use crate::{layout, RgbSwatch, THEME}; const RATATUI_LOGO: [&str; 32] = [ " ███ ", @@ -50,7 +50,7 @@ impl AboutTab { impl Widget for AboutTab { fn render(self, area: Rect, buf: &mut Buffer) { - colors::render_rgb_swatch(area, buf); + RgbSwatch.render(area, buf); let area = layout(area, Direction::Horizontal, vec![34, 0]); render_crate_description(area[1], buf); render_logo(self.selected_row, area[0], buf); @@ -91,6 +91,10 @@ fn render_crate_description(area: Rect, buf: &mut Buffer) { .render(area, buf); } +/// Use half block characters to render a logo based on the RATATUI_LOGO const. +/// +/// The logo is rendered in three colors, one for the rat, one for the terminal, and one for the +/// rat's eye. The eye color alternates between two colors based on the selected row. pub fn render_logo(selected_row: usize, area: Rect, buf: &mut Buffer) { let eye_color = if selected_row % 2 == 0 { THEME.logo.rat_eye diff --git a/examples/demo2/tabs/email.rs b/examples/demo2/tabs/email.rs index 83d9c5ee6..4ac463dcb 100644 --- a/examples/demo2/tabs/email.rs +++ b/examples/demo2/tabs/email.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use ratatui::{prelude::*, widgets::*}; use unicode_width::UnicodeWidthStr; -use crate::{colors, layout, THEME}; +use crate::{layout, RgbSwatch, THEME}; #[derive(Debug, Default)] pub struct Email { @@ -54,7 +54,7 @@ impl EmailTab { impl Widget for EmailTab { fn render(self, area: Rect, buf: &mut Buffer) { - colors::render_rgb_swatch(area, buf); + RgbSwatch.render(area, buf); let area = area.inner(&Margin { vertical: 1, horizontal: 2, diff --git a/examples/demo2/tabs/recipe.rs b/examples/demo2/tabs/recipe.rs index 5fd5aa63f..e5d17ef5d 100644 --- a/examples/demo2/tabs/recipe.rs +++ b/examples/demo2/tabs/recipe.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use ratatui::{prelude::*, widgets::*}; -use crate::{colors, layout, THEME}; +use crate::{layout, RgbSwatch, THEME}; #[derive(Debug, Default, Clone, Copy)] struct Ingredient { @@ -99,7 +99,7 @@ impl RecipeTab { impl Widget for RecipeTab { fn render(self, area: Rect, buf: &mut Buffer) { - colors::render_rgb_swatch(area, buf); + RgbSwatch.render(area, buf); let area = area.inner(&Margin { vertical: 1, horizontal: 2, diff --git a/examples/demo2/tabs/traceroute.rs b/examples/demo2/tabs/traceroute.rs index 61c027ba6..cb1867bd5 100644 --- a/examples/demo2/tabs/traceroute.rs +++ b/examples/demo2/tabs/traceroute.rs @@ -4,7 +4,7 @@ use ratatui::{ widgets::{canvas::*, *}, }; -use crate::{colors, layout, THEME}; +use crate::{layout, RgbSwatch, THEME}; #[derive(Debug)] pub struct TracerouteTab { @@ -21,7 +21,7 @@ impl TracerouteTab { impl Widget for TracerouteTab { fn render(self, area: Rect, buf: &mut Buffer) { - colors::render_rgb_swatch(area, buf); + RgbSwatch.render(area, buf); let area = area.inner(&Margin { vertical: 1, horizontal: 2, @@ -112,8 +112,10 @@ fn render_map(selected_row: usize, area: Rect, buf: &mut Buffer) { .style(theme.style), ) .marker(Marker::Dot) - .x_bounds([113.0, 154.0]) // australia - .y_bounds([-42.0, -11.0]) // australia + // picked to show Australia for the demo as it's the most interesting part of the map + // (and the only part with hops ;)) + .x_bounds([113.0, 154.0]) + .y_bounds([-42.0, -11.0]) .paint(|context| { context.draw(&map); if let Some(path) = path { diff --git a/examples/demo2/tabs/weather.rs b/examples/demo2/tabs/weather.rs index e14911e98..439f7f0c7 100644 --- a/examples/demo2/tabs/weather.rs +++ b/examples/demo2/tabs/weather.rs @@ -1,12 +1,12 @@ use itertools::Itertools; -use palette::{convert::IntoColorUnclamped, Okhsv, Srgb}; +use palette::Okhsv; use ratatui::{ prelude::*, widgets::{calendar::CalendarEventStore, *}, }; use time::OffsetDateTime; -use crate::{colors, layout, THEME}; +use crate::{color_from_oklab, layout, RgbSwatch, THEME}; pub struct WeatherTab { pub selected_row: usize, @@ -20,7 +20,7 @@ impl WeatherTab { impl Widget for WeatherTab { fn render(self, area: Rect, buf: &mut Buffer) { - colors::render_rgb_swatch(area, buf); + RgbSwatch.render(area, buf); let area = area.inner(&Margin { vertical: 1, horizontal: 2, @@ -118,37 +118,20 @@ pub fn render_gauges(progress: usize, area: Rect, buf: &mut Buffer) { render_line_gauge(percent, area, buf); } -// fn render_gauge(percent: f64, label: &str, area: Rect, buf: &mut Buffer) { -// // let bg = Color::Rgb(32, 96, 48); -// // let fg = Color::Rgb(64, 192, 96); -// let bg = Color::Red; -// let fg = Color::Yellow; -// Gauge::default() -// .ratio(percent / 100.0) -// .label(format!("Processing: {}", label)) -// .gauge_style(Style::new().fg(fg).bg(bg)) -// .use_unicode(false) -// .render(area, buf); -// } - fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) { - let hue = 90.0 - (percent * 0.6); - let fg = Okhsv::::new(hue, Okhsv::max_saturation(), 1.0); - let bg = Okhsv::::new(hue, Okhsv::max_saturation(), 0.5); - let fg: Srgb = fg.into_color_unclamped(); - let fg: Srgb = fg.into_format(); - let fg = Color::Rgb(fg.red, fg.green, fg.blue); - let bg: Srgb = bg.into_color_unclamped(); - let bg: Srgb = bg.into_format(); - let bg = Color::Rgb(bg.red, bg.green, bg.blue); - let progress_label = if percent < 100.0 { + // cycle color hue based on the percent for a neat effect yellow -> red + let hue = 90.0 - (percent as f32 * 0.6); + let value = Okhsv::max_value(); + let fg = color_from_oklab(hue, Okhsv::max_saturation(), value); + let bg = color_from_oklab(hue, Okhsv::max_saturation(), value * 0.5); + let label = if percent < 100.0 { format!("Downloading: {}%", percent) } else { "Download Complete!".into() }; LineGauge::default() .ratio(percent / 100.0) - .label(progress_label) + .label(label) .style(Style::new().light_blue()) .gauge_style(Style::new().fg(fg).bg(bg)) .line_set(symbols::line::THICK) diff --git a/examples/demo2/term.rs b/examples/demo2/term.rs index 7d12b868d..3dad0f68f 100644 --- a/examples/demo2/term.rs +++ b/examples/demo2/term.rs @@ -1,7 +1,6 @@ use std::{ io::{self, stdout, Stdout}, ops::{Deref, DerefMut}, - rc::Rc, time::Duration, }; @@ -11,7 +10,6 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, ExecutableCommand, }; -use itertools::Itertools; use ratatui::prelude::*; /// A wrapper around the terminal that handles setting up and tearing down the terminal @@ -71,21 +69,3 @@ impl Drop for Term { let _ = Term::stop(); } } - -/// simple helper method to split an area into multiple sub-areas -pub fn layout(area: Rect, direction: Direction, heights: Vec) -> Rc<[Rect]> { - let constraints = heights - .iter() - .map(|&h| { - if h > 0 { - Constraint::Length(h) - } else { - Constraint::Min(0) - } - }) - .collect_vec(); - Layout::default() - .direction(direction) - .constraints(constraints) - .split(area) -} diff --git a/examples/demo2/theme.rs b/examples/demo2/theme.rs index 4a0171e32..972e978b4 100644 --- a/examples/demo2/theme.rs +++ b/examples/demo2/theme.rs @@ -1,5 +1,3 @@ -#![allow(unused)] -use palette::{named::LIGHTGRAY, white_point::B}; use ratatui::prelude::*; pub struct Theme {