mirror of
https://github.com/ratatui-org/ratatui
synced 2024-12-02 01:19:35 +00:00
ed51c4b342
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>
255 lines
8.9 KiB
Rust
255 lines
8.9 KiB
Rust
//! # [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.
|
|
|
|
use color_eyre::Result;
|
|
use itertools::Itertools;
|
|
use ratatui::{
|
|
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
|
layout::{Alignment, Constraint, Layout, Rect},
|
|
style::{Color, Style, Stylize},
|
|
text::Line,
|
|
widgets::{Block, Borders, Paragraph},
|
|
DefaultTerminal, Frame,
|
|
};
|
|
|
|
fn main() -> Result<()> {
|
|
color_eyre::install()?;
|
|
let terminal = ratatui::init();
|
|
let app_result = run(terminal);
|
|
ratatui::restore();
|
|
app_result
|
|
}
|
|
|
|
fn run(mut terminal: DefaultTerminal) -> Result<()> {
|
|
loop {
|
|
terminal.draw(draw)?;
|
|
if let Event::Key(key) = event::read()? {
|
|
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 layout = Layout::vertical([Constraint::Length(1); 2])
|
|
.split(inner)
|
|
.iter()
|
|
.flat_map(|area| {
|
|
Layout::horizontal([Constraint::Ratio(1, 8); 8])
|
|
.split(*area)
|
|
.to_vec()
|
|
})
|
|
.collect_vec();
|
|
for (i, &fg) in NAMED_COLORS.iter().enumerate() {
|
|
let color_name = fg.to_string();
|
|
let paragraph = Paragraph::new(color_name).fg(fg).bg(bg);
|
|
frame.render_widget(paragraph, layout[i]);
|
|
}
|
|
}
|
|
|
|
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 layout = Layout::vertical([Constraint::Length(1); 2])
|
|
.split(inner)
|
|
.iter()
|
|
.flat_map(|area| {
|
|
Layout::horizontal([Constraint::Ratio(1, 8); 8])
|
|
.split(*area)
|
|
.to_vec()
|
|
})
|
|
.collect_vec();
|
|
for (i, &bg) in NAMED_COLORS.iter().enumerate() {
|
|
let color_name = bg.to_string();
|
|
let paragraph = Paragraph::new(color_name).fg(fg).bg(bg);
|
|
frame.render_widget(paragraph, layout[i]);
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|