ratatui/examples/colors.rs

250 lines
8.9 KiB
Rust
Raw Normal View History

//! # [Ratatui] Colors 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 all the colors supported by ratatui. It will render a grid of foreground
// and background colors with their names and indexes.
feat(terminal): Add ratatui::init() and restore() methods (#1289) These are simple opinionated methods for creating a terminal that is useful to use in most apps. The new init method creates a crossterm backend writing to stdout, enables raw mode, enters the alternate screen, and sets a panic handler that restores the terminal on panic. A minimal hello world now looks a bit like: ```rust use ratatui::{ crossterm::event::{self, Event}, text::Text, Frame, }; fn main() { let mut terminal = ratatui::init(); loop { terminal .draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area())) .expect("Failed to draw"); if matches!(event::read().expect("failed to read event"), Event::Key(_)) { break; } } ratatui::restore(); } ``` A type alias `DefaultTerminal` is added to represent this terminal type and to simplify any cases where applications need to pass this terminal around. It is equivalent to: `Terminal<CrosstermBackend<Stdout>>` We also added `ratatui::try_init()` and `try_restore()`, for situations where you might want to handle initialization errors yourself instead of letting the panic handler fire and cleanup. Simple Apps should prefer the `init` and `restore` functions over these functions. Corresponding functions to allow passing a `TerminalOptions` with a `Viewport` (e.g. inline, fixed) are also available (`init_with_options`, and `try_init_with_options`). The existing code to create a backend and terminal will remain and is not deprecated by this approach. This just provides a simple one line initialization using the common options. --------- Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
use color_eyre::Result;
use itertools::Itertools;
use ratatui::{
feat(terminal): Add ratatui::init() and restore() methods (#1289) These are simple opinionated methods for creating a terminal that is useful to use in most apps. The new init method creates a crossterm backend writing to stdout, enables raw mode, enters the alternate screen, and sets a panic handler that restores the terminal on panic. A minimal hello world now looks a bit like: ```rust use ratatui::{ crossterm::event::{self, Event}, text::Text, Frame, }; fn main() { let mut terminal = ratatui::init(); loop { terminal .draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area())) .expect("Failed to draw"); if matches!(event::read().expect("failed to read event"), Event::Key(_)) { break; } } ratatui::restore(); } ``` A type alias `DefaultTerminal` is added to represent this terminal type and to simplify any cases where applications need to pass this terminal around. It is equivalent to: `Terminal<CrosstermBackend<Stdout>>` We also added `ratatui::try_init()` and `try_restore()`, for situations where you might want to handle initialization errors yourself instead of letting the panic handler fire and cleanup. Simple Apps should prefer the `init` and `restore` functions over these functions. Corresponding functions to allow passing a `TerminalOptions` with a `Viewport` (e.g. inline, fixed) are also available (`init_with_options`, and `try_init_with_options`). The existing code to create a backend and terminal will remain and is not deprecated by this approach. This just provides a simple one line initialization using the common options. --------- Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
crossterm::event::{self, Event, KeyCode, KeyEventKind},
layout::{Alignment, Constraint, Layout, Rect},
style::{Color, Style, Stylize},
text::Line,
widgets::{Block, Borders, Paragraph},
feat(terminal): Add ratatui::init() and restore() methods (#1289) These are simple opinionated methods for creating a terminal that is useful to use in most apps. The new init method creates a crossterm backend writing to stdout, enables raw mode, enters the alternate screen, and sets a panic handler that restores the terminal on panic. A minimal hello world now looks a bit like: ```rust use ratatui::{ crossterm::event::{self, Event}, text::Text, Frame, }; fn main() { let mut terminal = ratatui::init(); loop { terminal .draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area())) .expect("Failed to draw"); if matches!(event::read().expect("failed to read event"), Event::Key(_)) { break; } } ratatui::restore(); } ``` A type alias `DefaultTerminal` is added to represent this terminal type and to simplify any cases where applications need to pass this terminal around. It is equivalent to: `Terminal<CrosstermBackend<Stdout>>` We also added `ratatui::try_init()` and `try_restore()`, for situations where you might want to handle initialization errors yourself instead of letting the panic handler fire and cleanup. Simple Apps should prefer the `init` and `restore` functions over these functions. Corresponding functions to allow passing a `TerminalOptions` with a `Viewport` (e.g. inline, fixed) are also available (`init_with_options`, and `try_init_with_options`). The existing code to create a backend and terminal will remain and is not deprecated by this approach. This just provides a simple one line initialization using the common options. --------- Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
DefaultTerminal, Frame,
};
fn main() -> Result<()> {
feat(terminal): Add ratatui::init() and restore() methods (#1289) These are simple opinionated methods for creating a terminal that is useful to use in most apps. The new init method creates a crossterm backend writing to stdout, enables raw mode, enters the alternate screen, and sets a panic handler that restores the terminal on panic. A minimal hello world now looks a bit like: ```rust use ratatui::{ crossterm::event::{self, Event}, text::Text, Frame, }; fn main() { let mut terminal = ratatui::init(); loop { terminal .draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area())) .expect("Failed to draw"); if matches!(event::read().expect("failed to read event"), Event::Key(_)) { break; } } ratatui::restore(); } ``` A type alias `DefaultTerminal` is added to represent this terminal type and to simplify any cases where applications need to pass this terminal around. It is equivalent to: `Terminal<CrosstermBackend<Stdout>>` We also added `ratatui::try_init()` and `try_restore()`, for situations where you might want to handle initialization errors yourself instead of letting the panic handler fire and cleanup. Simple Apps should prefer the `init` and `restore` functions over these functions. Corresponding functions to allow passing a `TerminalOptions` with a `Viewport` (e.g. inline, fixed) are also available (`init_with_options`, and `try_init_with_options`). The existing code to create a backend and terminal will remain and is not deprecated by this approach. This just provides a simple one line initialization using the common options. --------- Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = run(terminal);
ratatui::restore();
app_result
}
feat(terminal): Add ratatui::init() and restore() methods (#1289) These are simple opinionated methods for creating a terminal that is useful to use in most apps. The new init method creates a crossterm backend writing to stdout, enables raw mode, enters the alternate screen, and sets a panic handler that restores the terminal on panic. A minimal hello world now looks a bit like: ```rust use ratatui::{ crossterm::event::{self, Event}, text::Text, Frame, }; fn main() { let mut terminal = ratatui::init(); loop { terminal .draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area())) .expect("Failed to draw"); if matches!(event::read().expect("failed to read event"), Event::Key(_)) { break; } } ratatui::restore(); } ``` A type alias `DefaultTerminal` is added to represent this terminal type and to simplify any cases where applications need to pass this terminal around. It is equivalent to: `Terminal<CrosstermBackend<Stdout>>` We also added `ratatui::try_init()` and `try_restore()`, for situations where you might want to handle initialization errors yourself instead of letting the panic handler fire and cleanup. Simple Apps should prefer the `init` and `restore` functions over these functions. Corresponding functions to allow passing a `TerminalOptions` with a `Viewport` (e.g. inline, fixed) are also available (`init_with_options`, and `try_init_with_options`). The existing code to create a backend and terminal will remain and is not deprecated by this approach. This just provides a simple one line initialization using the common options. --------- Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
feat(terminal): Add ratatui::init() and restore() methods (#1289) These are simple opinionated methods for creating a terminal that is useful to use in most apps. The new init method creates a crossterm backend writing to stdout, enables raw mode, enters the alternate screen, and sets a panic handler that restores the terminal on panic. A minimal hello world now looks a bit like: ```rust use ratatui::{ crossterm::event::{self, Event}, text::Text, Frame, }; fn main() { let mut terminal = ratatui::init(); loop { terminal .draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area())) .expect("Failed to draw"); if matches!(event::read().expect("failed to read event"), Event::Key(_)) { break; } } ratatui::restore(); } ``` A type alias `DefaultTerminal` is added to represent this terminal type and to simplify any cases where applications need to pass this terminal around. It is equivalent to: `Terminal<CrosstermBackend<Stdout>>` We also added `ratatui::try_init()` and `try_restore()`, for situations where you might want to handle initialization errors yourself instead of letting the panic handler fire and cleanup. Simple Apps should prefer the `init` and `restore` functions over these functions. Corresponding functions to allow passing a `TerminalOptions` with a `Viewport` (e.g. inline, fixed) are also available (`init_with_options`, and `try_init_with_options`). The existing code to create a backend and terminal will remain and is not deprecated by this approach. This just provides a simple one line initialization using the common options. --------- Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
terminal.draw(draw)?;
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
return Ok(());
}
}
}
}
feat(terminal): Add ratatui::init() and restore() methods (#1289) These are simple opinionated methods for creating a terminal that is useful to use in most apps. The new init method creates a crossterm backend writing to stdout, enables raw mode, enters the alternate screen, and sets a panic handler that restores the terminal on panic. A minimal hello world now looks a bit like: ```rust use ratatui::{ crossterm::event::{self, Event}, text::Text, Frame, }; fn main() { let mut terminal = ratatui::init(); loop { terminal .draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area())) .expect("Failed to draw"); if matches!(event::read().expect("failed to read event"), Event::Key(_)) { break; } } ratatui::restore(); } ``` A type alias `DefaultTerminal` is added to represent this terminal type and to simplify any cases where applications need to pass this terminal around. It is equivalent to: `Terminal<CrosstermBackend<Stdout>>` We also added `ratatui::try_init()` and `try_restore()`, for situations where you might want to handle initialization errors yourself instead of letting the panic handler fire and cleanup. Simple Apps should prefer the `init` and `restore` functions over these functions. Corresponding functions to allow passing a `TerminalOptions` with a `Viewport` (e.g. inline, fixed) are also available (`init_with_options`, and `try_init_with_options`). The existing code to create a backend and terminal will remain and is not deprecated by this approach. This just provides a simple one line initialization using the common options. --------- Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
fn draw(frame: &mut Frame) {
let layout = Layout::vertical([
Constraint::Length(30),
Constraint::Length(17),
Constraint::Length(2),
])
.split(frame.area());
render_named_colors(frame, layout[0]);
render_indexed_colors(frame, layout[1]);
render_indexed_grayscale(frame, layout[2]);
}
const NAMED_COLORS: [Color; 16] = [
Color::Black,
Color::Red,
Color::Green,
Color::Yellow,
Color::Blue,
Color::Magenta,
Color::Cyan,
Color::Gray,
Color::DarkGray,
Color::LightRed,
Color::LightGreen,
Color::LightYellow,
Color::LightBlue,
Color::LightMagenta,
Color::LightCyan,
Color::White,
];
fn render_named_colors(frame: &mut Frame, area: Rect) {
let layout = Layout::vertical([Constraint::Length(3); 10]).split(area);
render_fg_named_colors(frame, Color::Reset, layout[0]);
render_fg_named_colors(frame, Color::Black, layout[1]);
render_fg_named_colors(frame, Color::DarkGray, layout[2]);
render_fg_named_colors(frame, Color::Gray, layout[3]);
render_fg_named_colors(frame, Color::White, layout[4]);
render_bg_named_colors(frame, Color::Reset, layout[5]);
render_bg_named_colors(frame, Color::Black, layout[6]);
render_bg_named_colors(frame, Color::DarkGray, layout[7]);
render_bg_named_colors(frame, Color::Gray, layout[8]);
render_bg_named_colors(frame, Color::White, layout[9]);
}
fn render_fg_named_colors(frame: &mut Frame, bg: Color, area: Rect) {
let block = title_block(format!("Foreground colors on {bg} background"));
let inner = block.inner(area);
frame.render_widget(block, area);
let vertical = Layout::vertical([Constraint::Length(1); 2]).split(inner);
let areas = vertical.iter().flat_map(|area| {
Layout::horizontal([Constraint::Ratio(1, 8); 8])
.split(*area)
.to_vec()
});
for (fg, area) in NAMED_COLORS.into_iter().zip(areas) {
let color_name = fg.to_string();
let paragraph = Paragraph::new(color_name).fg(fg).bg(bg);
frame.render_widget(paragraph, area);
}
}
fn render_bg_named_colors(frame: &mut Frame, fg: Color, area: Rect) {
let block = title_block(format!("Background colors with {fg} foreground"));
let inner = block.inner(area);
frame.render_widget(block, area);
let vertical = Layout::vertical([Constraint::Length(1); 2]).split(inner);
let areas = vertical.iter().flat_map(|area| {
Layout::horizontal([Constraint::Ratio(1, 8); 8])
.split(*area)
.to_vec()
});
for (bg, area) in NAMED_COLORS.into_iter().zip(areas) {
let color_name = bg.to_string();
let paragraph = Paragraph::new(color_name).fg(fg).bg(bg);
frame.render_widget(paragraph, area);
}
}
fn render_indexed_colors(frame: &mut Frame, area: Rect) {
let block = title_block("Indexed colors".into());
let inner = block.inner(area);
frame.render_widget(block, area);
let layout = Layout::vertical([
Constraint::Length(1), // 0 - 15
Constraint::Length(1), // blank
Constraint::Min(6), // 16 - 123
Constraint::Length(1), // blank
Constraint::Min(6), // 124 - 231
Constraint::Length(1), // blank
])
.split(inner);
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
let color_layout = Layout::horizontal([Constraint::Length(5); 16]).split(layout[0]);
for i in 0..16 {
let color = Color::Indexed(i);
let color_index = format!("{i:0>2}");
let bg = if i < 1 { Color::DarkGray } else { Color::Black };
let paragraph = Paragraph::new(Line::from(vec![
color_index.fg(color).bg(bg),
"██".bg(color).fg(color),
]));
frame.render_widget(paragraph, color_layout[i as usize]);
}
// 16 17 18 19 20 21 52 53 54 55 56 57 88 89 90 91 92 93
// 22 23 24 25 26 27 58 59 60 61 62 63 94 95 96 97 98 99
// 28 29 30 31 32 33 64 65 66 67 68 69 100 101 102 103 104 105
// 34 35 36 37 38 39 70 71 72 73 74 75 106 107 108 109 110 111
// 40 41 42 43 44 45 76 77 78 79 80 81 112 113 114 115 116 117
// 46 47 48 49 50 51 82 83 84 85 86 87 118 119 120 121 122 123
//
// 124 125 126 127 128 129 160 161 162 163 164 165 196 197 198 199 200 201
// 130 131 132 133 134 135 166 167 168 169 170 171 202 203 204 205 206 207
// 136 137 138 139 140 141 172 173 174 175 176 177 208 209 210 211 212 213
// 142 143 144 145 146 147 178 179 180 181 182 183 214 215 216 217 218 219
// 148 149 150 151 152 153 184 185 186 187 188 189 220 221 222 223 224 225
// 154 155 156 157 158 159 190 191 192 193 194 195 226 227 228 229 230 231
// the above looks complex but it's so the colors are grouped into blocks that display nicely
let index_layout = [layout[2], layout[4]]
.iter()
// two rows of 3 columns
.flat_map(|area| {
Layout::horizontal([Constraint::Length(27); 3])
.split(*area)
.to_vec()
})
// each with 6 rows
.flat_map(|area| {
Layout::vertical([Constraint::Length(1); 6])
.split(area)
.to_vec()
})
// each with 6 columns
.flat_map(|area| {
Layout::horizontal([Constraint::Min(4); 6])
.split(area)
.to_vec()
})
.collect_vec();
for i in 16..=231 {
let color = Color::Indexed(i);
let color_index = format!("{i:0>3}");
let paragraph = Paragraph::new(Line::from(vec![
color_index.fg(color).bg(Color::Reset),
".".bg(color).fg(color),
// There's a bug in VHS that seems to bleed backgrounds into the next
// character. This is a workaround to make the bug less obvious.
"███".reversed(),
]));
frame.render_widget(paragraph, index_layout[i as usize - 16]);
}
}
fn title_block(title: String) -> Block<'static> {
Block::new()
.borders(Borders::TOP)
.title_alignment(Alignment::Center)
.border_style(Style::new().dark_gray())
.title_style(Style::new().reset())
.title(title)
}
fn render_indexed_grayscale(frame: &mut Frame, area: Rect) {
let layout = Layout::vertical([
Constraint::Length(1), // 232 - 243
Constraint::Length(1), // 244 - 255
])
.split(area)
.iter()
.flat_map(|area| {
Layout::horizontal([Constraint::Length(6); 12])
.split(*area)
.to_vec()
})
.collect_vec();
for i in 232..=255 {
let color = Color::Indexed(i);
let color_index = format!("{i:0>3}");
// make the dark colors easier to read
let bg = if i < 244 { Color::Gray } else { Color::Black };
let paragraph = Paragraph::new(Line::from(vec![
color_index.fg(color).bg(bg),
"██".bg(color).fg(color),
// There's a bug in VHS that seems to bleed backgrounds into the next
// character. This is a workaround to make the bug less obvious.
"███████".reversed(),
]));
frame.render_widget(paragraph, layout[i as usize - 232]);
}
}