docs(examples): Update block example (#351)

![Block example](https://vhs.charm.sh/vhs-5X6hpReuDBKjD6hLxmDQ6F.gif)
This commit is contained in:
Josh McKinney 2023-08-04 00:46:37 -07:00 committed by GitHub
parent 1727fa5120
commit 554805d6cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 235 additions and 104 deletions

View File

@ -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

View File

@ -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(&paragraph, Borders::ALL, frame, layout[0][0]);
render_borders(&paragraph, Borders::NONE, frame, layout[0][1]);
render_borders(&paragraph, Borders::LEFT, frame, layout[1][0]);
render_borders(&paragraph, Borders::RIGHT, frame, layout[1][1]);
render_borders(&paragraph, Borders::TOP, frame, layout[2][0]);
render_borders(&paragraph, Borders::BOTTOM, frame, layout[2][1]);
render_border_type(&paragraph, BorderType::Plain, frame, layout[3][0]);
render_border_type(&paragraph, BorderType::Rounded, frame, layout[3][1]);
render_border_type(&paragraph, BorderType::Double, frame, layout[4][0]);
render_border_type(&paragraph, BorderType::Thick, frame, layout[4][1]);
render_styled_block(&paragraph, frame, layout[5][0]);
render_styled_borders(&paragraph, frame, layout[5][1]);
render_styled_title(&paragraph, frame, layout[6][0]);
render_styled_title_content(&paragraph, frame, layout[6][1]);
render_multiple_titles(&paragraph, frame, layout[7][0]);
render_multiple_title_positions(&paragraph, frame, layout[7][1]);
render_padding(&paragraph, frame, layout[8][0]);
render_nested_blocks(&paragraph, 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);
} }

View File

@ -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