mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-25 14:10:31 +00:00
56455e0fee
Previously the layout used the floor of the calculated start and width as the value to use for the split Rects. This resulted in gaps between the split rects. This change modifies the layout to round to the nearest column instead of taking the floor of the start and width. This results in the start and end of each rect being rounded the same way and being strictly adjacent without gaps. Because there is a required constraint that ensures that the last end is equal to the area end, there is no longer the need to fixup the last item width when the fill (as e.g. width = x.99 now rounds to x+1 not x). The colors example has been updated to use Ratio(1, 8) instead of Percentage(13), as this now renders without gaps for all possible sizes, whereas previously it would have left odd gaps between columns.
295 lines
10 KiB
Rust
295 lines
10 KiB
Rust
/// 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 std::{
|
|
error::Error,
|
|
io::{self, Stdout},
|
|
result,
|
|
time::Duration,
|
|
};
|
|
|
|
use crossterm::{
|
|
event::{self, Event, KeyCode},
|
|
execute,
|
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
|
};
|
|
use itertools::Itertools;
|
|
use ratatui::{prelude::*, widgets::*};
|
|
|
|
type Result<T> = result::Result<T, Box<dyn Error>>;
|
|
|
|
fn main() -> Result<()> {
|
|
let mut terminal = setup_terminal()?;
|
|
let res = run_app(&mut terminal);
|
|
restore_terminal(terminal)?;
|
|
if let Err(err) = res {
|
|
eprintln!("{err:?}");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
|
loop {
|
|
terminal.draw(ui)?;
|
|
|
|
if event::poll(Duration::from_millis(250))? {
|
|
if let Event::Key(key) = event::read()? {
|
|
if let KeyCode::Char('q') = key.code {
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ui<B: Backend>(frame: &mut Frame<B>) {
|
|
let layout = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints(vec![
|
|
Constraint::Length(30),
|
|
Constraint::Length(17),
|
|
Constraint::Length(2),
|
|
])
|
|
.split(frame.size());
|
|
|
|
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<B: Backend>(frame: &mut Frame<B>, area: Rect) {
|
|
let layout = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints(vec![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<B: Backend>(frame: &mut Frame<B>, 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::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints(vec![Constraint::Length(1); 2])
|
|
.split(inner)
|
|
.iter()
|
|
.flat_map(|area| {
|
|
Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints(vec![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<B: Backend>(frame: &mut Frame<B>, 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::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints(vec![Constraint::Length(1); 2])
|
|
.split(inner)
|
|
.iter()
|
|
.flat_map(|area| {
|
|
Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints(vec![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<B: Backend>(frame: &mut Frame<B>, area: Rect) {
|
|
let block = title_block("Indexed colors".into());
|
|
let inner = block.inner(area);
|
|
frame.render_widget(block, area);
|
|
|
|
let layout = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints(vec![
|
|
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::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints(vec![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::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints(vec![Constraint::Length(27); 3])
|
|
.split(*area)
|
|
.to_vec()
|
|
})
|
|
// each with 6 rows
|
|
.flat_map(|area| {
|
|
Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints(vec![Constraint::Length(1); 6])
|
|
.split(area)
|
|
.to_vec()
|
|
})
|
|
// each with 6 columns
|
|
.flat_map(|area| {
|
|
Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints(vec![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::default()
|
|
.borders(Borders::TOP)
|
|
.border_style(Style::new().dark_gray())
|
|
.title(title)
|
|
.title_alignment(Alignment::Center)
|
|
.title_style(Style::new().reset())
|
|
}
|
|
|
|
fn render_indexed_grayscale<B: Backend>(frame: &mut Frame<B>, area: Rect) {
|
|
let layout = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints(vec![
|
|
Constraint::Length(1), // 232 - 243
|
|
Constraint::Length(1), // 244 - 255
|
|
])
|
|
.split(area)
|
|
.iter()
|
|
.flat_map(|area| {
|
|
Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints(vec![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]);
|
|
}
|
|
}
|
|
|
|
fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
|
|
enable_raw_mode()?;
|
|
let mut stdout = io::stdout();
|
|
execute!(stdout, EnterAlternateScreen)?;
|
|
let backend = CrosstermBackend::new(stdout);
|
|
let mut terminal = Terminal::new(backend)?;
|
|
terminal.hide_cursor()?;
|
|
Ok(terminal)
|
|
}
|
|
|
|
fn restore_terminal(mut terminal: Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
|
disable_raw_mode()?;
|
|
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
|
terminal.show_cursor()?;
|
|
Ok(())
|
|
}
|