mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-25 22:20:31 +00:00
8061813f32
Consensus is that explicit imports make it easier to understand the example code. This commit removes the prelude import from all examples and replaces it with the necessary imports, and expands other glob imports (widget::*, Constraint::*, KeyCode::*, etc.) everywhere else. Prelude glob imports not in examples are not covered by this PR. See https://github.com/ratatui-org/ratatui/issues/1150 for more details.
258 lines
9 KiB
Rust
258 lines
9 KiB
Rust
//! # [Ratatui] Block 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-org/ratatui
|
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
|
|
|
use std::{
|
|
error::Error,
|
|
io::{stdout, Stdout},
|
|
ops::ControlFlow,
|
|
time::Duration,
|
|
};
|
|
|
|
use itertools::Itertools;
|
|
use ratatui::{
|
|
backend::CrosstermBackend,
|
|
crossterm::{
|
|
event::{self, Event, KeyCode},
|
|
execute,
|
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
|
},
|
|
layout::{Alignment, Constraint, Layout, Rect},
|
|
style::{Style, Stylize},
|
|
terminal::Frame,
|
|
text::Line,
|
|
widgets::{
|
|
block::{Position, Title},
|
|
Block, BorderType, Borders, Padding, Paragraph, Wrap,
|
|
},
|
|
};
|
|
|
|
// These type aliases are used to make the code more readable by reducing repetition of the generic
|
|
// types. They are not necessary for the functionality of the code.
|
|
type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>;
|
|
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
|
|
|
fn main() -> Result<()> {
|
|
let mut terminal = setup_terminal()?;
|
|
let result = run(&mut terminal);
|
|
restore_terminal(terminal)?;
|
|
|
|
if let Err(err) = result {
|
|
eprintln!("{err:?}");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
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 {
|
|
terminal.draw(ui)?;
|
|
if handle_events()?.is_break() {
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_events() -> Result<ControlFlow<()>> {
|
|
if event::poll(Duration::from_millis(100))? {
|
|
if let Event::Key(key) = event::read()? {
|
|
if key.code == KeyCode::Char('q') {
|
|
return Ok(ControlFlow::Break(()));
|
|
}
|
|
}
|
|
}
|
|
Ok(ControlFlow::Continue(()))
|
|
}
|
|
|
|
fn ui(frame: &mut Frame) {
|
|
let (title_area, layout) = calculate_layout(frame.size());
|
|
|
|
render_title(frame, title_area);
|
|
|
|
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 main_layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
|
|
let block_layout = Layout::vertical([Constraint::Max(4); 9]);
|
|
let [title_area, main_area] = main_layout.areas(area);
|
|
let main_areas = block_layout
|
|
.split(main_area)
|
|
.iter()
|
|
.map(|&area| {
|
|
Layout::horizontal([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:#?}"));
|
|
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::bordered()
|
|
.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::bordered()
|
|
.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::bordered()
|
|
.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::bordered()
|
|
.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::bordered().title(title);
|
|
frame.render_widget(paragraph.clone().block(block), area);
|
|
}
|
|
|
|
fn render_multiple_titles(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
|
let block = Block::bordered()
|
|
.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::bordered()
|
|
.title(
|
|
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),
|
|
)
|
|
.title(
|
|
Title::from("bottom left")
|
|
.position(Position::Bottom)
|
|
.alignment(Alignment::Left),
|
|
)
|
|
.title(
|
|
Title::from("bottom center")
|
|
.position(Position::Bottom)
|
|
.alignment(Alignment::Center),
|
|
)
|
|
.title(
|
|
Title::from("bottom right")
|
|
.position(Position::Bottom)
|
|
.alignment(Alignment::Right),
|
|
);
|
|
frame.render_widget(paragraph.clone().block(block), area);
|
|
}
|
|
|
|
fn render_padding(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
|
let block = Block::bordered()
|
|
.padding(Padding::new(5, 10, 1, 2))
|
|
.title("Padding");
|
|
frame.render_widget(paragraph.clone().block(block), area);
|
|
}
|
|
|
|
fn render_nested_blocks(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
|
let outer_block = Block::bordered().title("Outer block");
|
|
let inner_block = Block::bordered().title("Inner block");
|
|
let inner = outer_block.inner(area);
|
|
frame.render_widget(outer_block, area);
|
|
frame.render_widget(paragraph.clone().block(inner_block), inner);
|
|
}
|