mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-27 13:01:13 +00:00
docs(examples): Update block example (#351)

This commit is contained in:
parent
1727fa5120
commit
554805d6cb
@ -189,7 +189,7 @@ done
|
|||||||
```
|
```
|
||||||
-->
|
-->
|
||||||
[barchart.gif]: https://vhs.charm.sh/vhs-6ioxdeRBVkVpyXcjIEVaJU.gif
|
[barchart.gif]: https://vhs.charm.sh/vhs-6ioxdeRBVkVpyXcjIEVaJU.gif
|
||||||
[block.gif]: https://vhs.charm.sh/vhs-1sEo9vVkHRwFtu95MOXrTj.gif
|
[block.gif]: https://vhs.charm.sh/vhs-5X6hpReuDBKjD6hLxmDQ6F.gif
|
||||||
[calendar.gif]: https://vhs.charm.sh/vhs-1dBcpMSSP80WkBgm4lBhNo.gif
|
[calendar.gif]: https://vhs.charm.sh/vhs-1dBcpMSSP80WkBgm4lBhNo.gif
|
||||||
[canvas.gif]: https://vhs.charm.sh/vhs-4zeWEPF6bLEFSHuJrvaHlN.gif
|
[canvas.gif]: https://vhs.charm.sh/vhs-4zeWEPF6bLEFSHuJrvaHlN.gif
|
||||||
[chart.gif]: https://vhs.charm.sh/vhs-zRzsE2AwRixQhcWMTAeF1.gif
|
[chart.gif]: https://vhs.charm.sh/vhs-zRzsE2AwRixQhcWMTAeF1.gif
|
||||||
|
@ -1,123 +1,253 @@
|
|||||||
use std::{error::Error, io, time::Duration};
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
io::{stdout, Stdout},
|
||||||
|
ops::ControlFlow,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
event::{self, Event, KeyCode},
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use itertools::Itertools;
|
||||||
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{
|
||||||
|
block::{Position, Title},
|
||||||
|
Block, BorderType, Borders, Padding, Paragraph, Wrap,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
// These type aliases are used to make the code more readable by reducing repetition of the generic
|
||||||
// setup terminal
|
// types. They are not necessary for the functionality of the code.
|
||||||
enable_raw_mode()?;
|
type Frame<'a> = ratatui::Frame<'a, CrosstermBackend<Stdout>>;
|
||||||
let mut stdout = io::stdout();
|
type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>;
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||||
let backend = CrosstermBackend::new(stdout);
|
|
||||||
let mut terminal = Terminal::new(backend)?;
|
|
||||||
|
|
||||||
// create app and run it
|
fn main() -> Result<()> {
|
||||||
let res = run_app(&mut terminal);
|
let mut terminal = setup_terminal()?;
|
||||||
|
let result = run(&mut terminal);
|
||||||
|
restore_terminal(terminal)?;
|
||||||
|
|
||||||
// restore terminal
|
if let Err(err) = result {
|
||||||
disable_raw_mode()?;
|
eprintln!("{err:?}");
|
||||||
execute!(
|
|
||||||
terminal.backend_mut(),
|
|
||||||
LeaveAlternateScreen,
|
|
||||||
DisableMouseCapture
|
|
||||||
)?;
|
|
||||||
terminal.clear()?;
|
|
||||||
terminal.show_cursor()?;
|
|
||||||
|
|
||||||
if let Err(err) = res {
|
|
||||||
println!("{err:?}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
fn setup_terminal() -> Result<Terminal> {
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = stdout();
|
||||||
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let terminal = Terminal::new(backend)?;
|
||||||
|
Ok(terminal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore_terminal(mut terminal: Terminal) -> Result<()> {
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(terminal: &mut Terminal) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(ui)?;
|
terminal.draw(ui)?;
|
||||||
|
if handle_events()?.is_break() {
|
||||||
if event::poll(Duration::from_millis(250))? {
|
return Ok(());
|
||||||
if let Event::Key(key) = event::read()? {
|
|
||||||
if let KeyCode::Char('q') = key.code {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui<B: Backend>(f: &mut Frame<B>) {
|
fn handle_events() -> Result<ControlFlow<()>> {
|
||||||
// Wrapping block for a group
|
if event::poll(Duration::from_millis(100))? {
|
||||||
// Just draw the block and the group on the same area and build the group
|
if let Event::Key(key) = event::read()? {
|
||||||
let outer = f.size();
|
if let KeyCode::Char('q') = key.code {
|
||||||
let outer_block = Block::default()
|
return Ok(ControlFlow::Break(()));
|
||||||
.borders(Borders::ALL)
|
}
|
||||||
.title(block::Title::from("Main block with round corners").alignment(Alignment::Center))
|
}
|
||||||
.border_type(BorderType::Rounded);
|
}
|
||||||
let inner = outer_block.inner(outer);
|
Ok(ControlFlow::Continue(()))
|
||||||
let [top, bottom] = *Layout::default()
|
}
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.margin(1)
|
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
|
||||||
.split(inner)
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let [top_left, top_right] = *Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
|
||||||
.split(top)
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let [bottom_left, bottom_right] = *Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
|
||||||
.split(bottom)
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let top_left_block = Block::default()
|
fn ui(frame: &mut Frame) {
|
||||||
.title("With Green Background")
|
let (title_area, layout) = calculate_layout(frame.size());
|
||||||
.borders(Borders::all())
|
|
||||||
.on_green();
|
render_title(frame, title_area);
|
||||||
let top_right_block = Block::default()
|
|
||||||
|
let paragraph = placeholder_paragraph();
|
||||||
|
|
||||||
|
render_borders(¶graph, Borders::ALL, frame, layout[0][0]);
|
||||||
|
render_borders(¶graph, Borders::NONE, frame, layout[0][1]);
|
||||||
|
render_borders(¶graph, Borders::LEFT, frame, layout[1][0]);
|
||||||
|
render_borders(¶graph, Borders::RIGHT, frame, layout[1][1]);
|
||||||
|
render_borders(¶graph, Borders::TOP, frame, layout[2][0]);
|
||||||
|
render_borders(¶graph, Borders::BOTTOM, frame, layout[2][1]);
|
||||||
|
|
||||||
|
render_border_type(¶graph, BorderType::Plain, frame, layout[3][0]);
|
||||||
|
render_border_type(¶graph, BorderType::Rounded, frame, layout[3][1]);
|
||||||
|
render_border_type(¶graph, BorderType::Double, frame, layout[4][0]);
|
||||||
|
render_border_type(¶graph, BorderType::Thick, frame, layout[4][1]);
|
||||||
|
|
||||||
|
render_styled_block(¶graph, frame, layout[5][0]);
|
||||||
|
render_styled_borders(¶graph, frame, layout[5][1]);
|
||||||
|
render_styled_title(¶graph, frame, layout[6][0]);
|
||||||
|
render_styled_title_content(¶graph, frame, layout[6][1]);
|
||||||
|
render_multiple_titles(¶graph, frame, layout[7][0]);
|
||||||
|
render_multiple_title_positions(¶graph, frame, layout[7][1]);
|
||||||
|
render_padding(¶graph, frame, layout[8][0]);
|
||||||
|
render_nested_blocks(¶graph, frame, layout[8][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the layout of the UI elements.
|
||||||
|
///
|
||||||
|
/// Returns a tuple of the title area and the main areas.
|
||||||
|
fn calculate_layout(area: Rect) -> (Rect, Vec<Vec<Rect>>) {
|
||||||
|
let layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(vec![Constraint::Length(1), Constraint::Min(0)])
|
||||||
|
.split(area);
|
||||||
|
let title_area = layout[0];
|
||||||
|
let main_areas = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(vec![Constraint::Max(4); 9])
|
||||||
|
.split(layout[1])
|
||||||
|
.iter()
|
||||||
|
.map(|&area| {
|
||||||
|
Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
|
.split(area)
|
||||||
|
.to_vec()
|
||||||
|
})
|
||||||
|
.collect_vec();
|
||||||
|
(title_area, main_areas)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_title(frame: &mut Frame, area: Rect) {
|
||||||
|
frame.render_widget(
|
||||||
|
Paragraph::new("Block example. Press q to quit")
|
||||||
|
.dark_gray()
|
||||||
|
.alignment(Alignment::Center),
|
||||||
|
area,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn placeholder_paragraph() -> Paragraph<'static> {
|
||||||
|
let text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
|
||||||
|
Paragraph::new(text.dark_gray()).wrap(Wrap { trim: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_borders(paragraph: &Paragraph, border: Borders, frame: &mut Frame, area: Rect) {
|
||||||
|
let block = Block::new()
|
||||||
|
.borders(border)
|
||||||
|
.title(format!("Borders::{border:#?}", border = border));
|
||||||
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_border_type(
|
||||||
|
paragraph: &Paragraph,
|
||||||
|
border_type: BorderType,
|
||||||
|
frame: &mut Frame,
|
||||||
|
area: Rect,
|
||||||
|
) {
|
||||||
|
let block = Block::new()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(border_type)
|
||||||
|
.title(format!("BorderType::{border_type:#?}"));
|
||||||
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
|
}
|
||||||
|
fn render_styled_borders(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||||
|
let block = Block::new()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(Style::new().blue().on_white().bold().italic())
|
||||||
|
.title("Styled borders");
|
||||||
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_styled_block(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||||
|
let block = Block::new()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::new().blue().on_white().bold().italic())
|
||||||
|
.title("Styled block");
|
||||||
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this currently renders incorrectly, see https://github.com/ratatui-org/ratatui/issues/349
|
||||||
|
fn render_styled_title(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||||
|
let block = Block::new()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Styled title")
|
||||||
|
.title_style(Style::new().blue().on_white().bold().italic());
|
||||||
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_styled_title_content(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||||
|
let title = Line::from(vec![
|
||||||
|
"Styled ".blue().on_white().bold().italic(),
|
||||||
|
"title content".red().on_white().bold().italic(),
|
||||||
|
]);
|
||||||
|
let block = Block::new().borders(Borders::ALL).title(title);
|
||||||
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_multiple_titles(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||||
|
let block = Block::new()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Multiple".blue().on_white().bold().italic())
|
||||||
|
.title("Titles".red().on_white().bold().italic());
|
||||||
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_multiple_title_positions(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||||
|
let block = Block::new()
|
||||||
|
.borders(Borders::ALL)
|
||||||
.title(
|
.title(
|
||||||
block::Title::from("With styled title".white().on_red().bold())
|
Title::from("top left")
|
||||||
|
.position(Position::Top)
|
||||||
|
.alignment(Alignment::Left),
|
||||||
|
)
|
||||||
|
.title(
|
||||||
|
Title::from("top center")
|
||||||
|
.position(Position::Top)
|
||||||
|
.alignment(Alignment::Center),
|
||||||
|
)
|
||||||
|
.title(
|
||||||
|
Title::from("top right")
|
||||||
|
.position(Position::Top)
|
||||||
.alignment(Alignment::Right),
|
.alignment(Alignment::Right),
|
||||||
)
|
)
|
||||||
.borders(Borders::ALL);
|
.title(
|
||||||
let bottom_left_block = Paragraph::new("Text inside padded block").block(
|
Title::from("bottom left")
|
||||||
Block::default()
|
.position(Position::Bottom)
|
||||||
.title("With borders")
|
.alignment(Alignment::Left),
|
||||||
.borders(Borders::ALL)
|
)
|
||||||
.padding(Padding {
|
.title(
|
||||||
left: 4,
|
Title::from("bottom center")
|
||||||
right: 4,
|
.position(Position::Bottom)
|
||||||
top: 2,
|
.alignment(Alignment::Center),
|
||||||
bottom: 2,
|
)
|
||||||
}),
|
.title(
|
||||||
);
|
Title::from("bottom right")
|
||||||
let bottom_right_block = Block::default()
|
.position(Position::Bottom)
|
||||||
.title("With styled borders and doubled borders")
|
.alignment(Alignment::Right),
|
||||||
.border_style(Style::default().fg(Color::Cyan))
|
);
|
||||||
.borders(Borders::LEFT | Borders::RIGHT)
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
.border_type(BorderType::Double)
|
}
|
||||||
.padding(Padding::uniform(1));
|
|
||||||
let bottom_inner_block = Block::default()
|
fn render_padding(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||||
.title("Block inside padded block")
|
let block = Block::new()
|
||||||
.borders(Borders::ALL);
|
.borders(Borders::ALL)
|
||||||
|
.title("Padding")
|
||||||
f.render_widget(outer_block, outer);
|
.padding(Padding::new(5, 10, 1, 2));
|
||||||
f.render_widget(Clear, top_left);
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
f.render_widget(top_left_block, top_left);
|
}
|
||||||
f.render_widget(top_right_block, top_right);
|
|
||||||
f.render_widget(bottom_left_block, bottom_left);
|
fn render_nested_blocks(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||||
let bottom_right_inner = bottom_right_block.inner(bottom_right);
|
let outer_block = Block::new().borders(Borders::ALL).title("Outer block");
|
||||||
f.render_widget(bottom_right_block, bottom_right);
|
let inner_block = Block::new().borders(Borders::ALL).title("Inner block");
|
||||||
f.render_widget(bottom_inner_block, bottom_right_inner);
|
let inner = outer_block.inner(area);
|
||||||
|
frame.render_widget(outer_block, area);
|
||||||
|
frame.render_widget(paragraph.clone().block(inner_block), inner);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
|
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
|
||||||
# To run this script, install vhs and run `vhs ./examples/block.tape`
|
# To run this script, install vhs and run `vhs ./examples/block.tape`
|
||||||
Output "target/block.gif"
|
Output "target/block.gif"
|
||||||
|
Set Theme "Builtin Dark"
|
||||||
Set Width 1200
|
Set Width 1200
|
||||||
Set Height 800
|
Set Height 1200
|
||||||
Hide
|
Hide
|
||||||
Type "cargo run --example=block"
|
Type "cargo run --example=block"
|
||||||
Enter
|
Enter
|
||||||
Sleep 1s
|
Sleep 2s
|
||||||
Show
|
Show
|
||||||
Sleep 5s
|
Sleep 2s
|
||||||
|
Loading…
x
Reference in New Issue
Block a user