feat(frame)!: Remove generic Backend parameter (#530)

This change simplifys UI code that uses the Frame type. E.g.:

```rust
fn draw<B: Backend>(frame: &mut Frame<B>) {
    // ...
}
```

Frame was generic over Backend because it stored a reference to the
terminal in the field. Instead it now directly stores the viewport area
and current buffer. These are provided at creation time and are valid
for the duration of the frame.

BREAKING CHANGE: Frame is no longer generic over Backend. Code that
accepted `Frame<Backend>` will now need to accept `Frame`. To migrate
existing code, remove any generic parameters from code that uses an
instance of a Frame. E.g. the above code becomes:

```rust
fn draw(frame: &mut Frame) {
    // ...
}
```
This commit is contained in:
Josh McKinney 2023-09-25 22:30:36 -07:00 committed by GitHub
parent cbf86da0e7
commit 082cbcbc50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 59 additions and 94 deletions

View file

@ -136,7 +136,7 @@ fn run_app<B: Backend>(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)].as_ref())
@ -198,10 +198,7 @@ fn create_groups<'a>(app: &'a App, combine_values_and_labels: bool) -> Vec<BarGr
.collect()
}
fn draw_bar_with_group_labels<B>(f: &mut Frame<B>, app: &App, area: Rect)
where
B: Backend,
{
fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
let groups = create_groups(app, false);
let mut barchart = BarChart::default()
@ -228,10 +225,7 @@ where
}
}
fn draw_horizontal_bars<B>(f: &mut Frame<B>, app: &App, area: Rect)
where
B: Backend,
{
fn draw_horizontal_bars(f: &mut Frame, app: &App, area: Rect) {
let groups = create_groups(app, true);
let mut barchart = BarChart::default()
@ -260,10 +254,7 @@ where
}
}
fn draw_legend<B>(f: &mut Frame<B>, area: Rect)
where
B: Backend,
{
fn draw_legend(f: &mut Frame, area: Rect) {
let text = vec![
Line::from(Span::styled(
TOTAL_REVENUE,

View file

@ -21,7 +21,6 @@ use ratatui::{
// 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 Frame<'a> = ratatui::Frame<'a, CrosstermBackend<Stdout>>;
type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>;
type Result<T> = std::result::Result<T, Box<dyn Error>>;

View file

@ -16,7 +16,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut terminal = Terminal::new(backend)?;
loop {
let _ = terminal.draw(|f| draw(f));
let _ = terminal.draw(draw);
if let Event::Key(key) = event::read()? {
#[allow(clippy::single_match)]
@ -35,7 +35,7 @@ fn main() -> Result<(), Box<dyn Error>> {
Ok(())
}
fn draw<B: Backend>(f: &mut Frame<B>) {
fn draw(f: &mut Frame) {
let app_area = f.size();
let calarea = Rect {

View file

@ -156,7 +156,7 @@ fn run_app<B: Backend>(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())

View file

@ -142,7 +142,7 @@ fn run_app<B: Backend>(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let size = f.size();
let chunks = Layout::default()
.direction(Direction::Vertical)

View file

@ -41,7 +41,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
}
}
fn ui<B: Backend>(frame: &mut Frame<B>) {
fn ui(frame: &mut Frame) {
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![
@ -75,7 +75,7 @@ const NAMED_COLORS: [Color; 16] = [
Color::White,
];
fn render_named_colors<B: Backend>(frame: &mut Frame<B>, area: Rect) {
fn render_named_colors(frame: &mut Frame, area: Rect) {
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Length(3); 10])
@ -94,7 +94,7 @@ fn render_named_colors<B: Backend>(frame: &mut Frame<B>, area: Rect) {
render_bg_named_colors(frame, Color::White, layout[9]);
}
fn render_fg_named_colors<B: Backend>(frame: &mut Frame<B>, bg: Color, area: Rect) {
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);
@ -119,7 +119,7 @@ fn render_fg_named_colors<B: Backend>(frame: &mut Frame<B>, bg: Color, area: Rec
}
}
fn render_bg_named_colors<B: Backend>(frame: &mut Frame<B>, fg: Color, area: Rect) {
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);
@ -144,7 +144,7 @@ fn render_bg_named_colors<B: Backend>(frame: &mut Frame<B>, fg: Color, area: Rec
}
}
fn render_indexed_colors<B: Backend>(frame: &mut Frame<B>, area: Rect) {
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);
@ -243,7 +243,7 @@ fn title_block(title: String) -> Block<'static> {
.title_style(Style::new().reset())
}
fn render_indexed_grayscale<B: Backend>(frame: &mut Frame<B>, area: Rect) {
fn render_indexed_grayscale(frame: &mut Frame, area: Rect) {
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![

View file

@ -64,7 +64,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
}
}
fn ui<B: Backend>(f: &mut Frame<B>) {
fn ui(f: &mut Frame) {
let size = f.size();
let label = Label::default().text("Test");
f.render_widget(label, size);

View file

@ -5,7 +5,7 @@ use ratatui::{
use crate::app::App;
pub fn draw<B: Backend>(f: &mut Frame<B>, app: &mut App) {
pub fn draw(f: &mut Frame, app: &mut App) {
let chunks = Layout::default()
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.split(f.size());
@ -28,10 +28,7 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, app: &mut App) {
};
}
fn draw_first_tab<B>(f: &mut Frame<B>, app: &mut App, area: Rect)
where
B: Backend,
{
fn draw_first_tab(f: &mut Frame, app: &mut App, area: Rect) {
let chunks = Layout::default()
.constraints(
[
@ -47,10 +44,7 @@ where
draw_text(f, chunks[2]);
}
fn draw_gauges<B>(f: &mut Frame<B>, app: &mut App, area: Rect)
where
B: Backend,
{
fn draw_gauges(f: &mut Frame, app: &mut App, area: Rect) {
let chunks = Layout::default()
.constraints(
[
@ -102,10 +96,7 @@ where
f.render_widget(line_gauge, chunks[2]);
}
fn draw_charts<B>(f: &mut Frame<B>, app: &mut App, area: Rect)
where
B: Backend,
{
fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
let constraints = if app.show_chart {
vec![Constraint::Percentage(50), Constraint::Percentage(50)]
} else {
@ -249,10 +240,7 @@ where
}
}
fn draw_text<B>(f: &mut Frame<B>, area: Rect)
where
B: Backend,
{
fn draw_text(f: &mut Frame, area: Rect) {
let text = vec![
text::Line::from("This is a paragraph with several lines. You can change style your text the way you want"),
text::Line::from(""),
@ -290,10 +278,7 @@ where
f.render_widget(paragraph, area);
}
fn draw_second_tab<B>(f: &mut Frame<B>, app: &mut App, area: Rect)
where
B: Backend,
{
fn draw_second_tab(f: &mut Frame, app: &mut App, area: Rect) {
let chunks = Layout::default()
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref())
.direction(Direction::Horizontal)
@ -379,10 +364,7 @@ where
f.render_widget(map, chunks[1]);
}
fn draw_third_tab<B>(f: &mut Frame<B>, _app: &mut App, area: Rect)
where
B: Backend,
{
fn draw_third_tab(f: &mut Frame, _app: &mut App, area: Rect) {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])

View file

@ -103,7 +103,7 @@ fn run_app<B: Backend>(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(

View file

@ -61,7 +61,7 @@ fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
/// Render the application. This is where you would draw the application UI. This example just
/// draws a greeting.
fn render_app(frame: &mut ratatui::Frame<CrosstermBackend<Stdout>>) {
fn render_app(frame: &mut Frame) {
let greeting = Paragraph::new("Hello World! (press 'q' to quit)");
frame.render_widget(greeting, frame.size());
}

View file

@ -216,7 +216,7 @@ fn run_app<B: Backend>(
Ok(())
}
fn ui<B: Backend>(f: &mut Frame<B>, downloads: &Downloads) {
fn ui(f: &mut Frame, downloads: &Downloads) {
let size = f.size();
let block = Block::default().title(block::Title::from("Progress").alignment(Alignment::Center));

View file

@ -37,7 +37,7 @@ fn main() -> Result<(), Box<dyn Error>> {
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
loop {
terminal.draw(|f| ui(f))?;
terminal.draw(ui)?;
if let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
@ -47,7 +47,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
}
}
fn ui<B: Backend>(frame: &mut Frame<B>) {
fn ui(frame: &mut Frame) {
let main_layout = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![
@ -169,8 +169,8 @@ fn ui<B: Backend>(frame: &mut Frame<B>) {
}
/// Renders a single example box
fn render_example_combination<B: Backend>(
frame: &mut Frame<B>,
fn render_example_combination(
frame: &mut Frame,
area: Rect,
title: &str,
constraints: Vec<(Constraint, Constraint)>,
@ -195,11 +195,7 @@ fn render_example_combination<B: Backend>(
}
/// Renders a single example line
fn render_single_example<B: Backend>(
frame: &mut Frame<B>,
area: Rect,
constraints: Vec<Constraint>,
) {
fn render_single_example(frame: &mut Frame, area: Rect, constraints: Vec<Constraint>) {
let red = Paragraph::new(constraint_label(constraints[0])).on_red();
let blue = Paragraph::new(constraint_label(constraints[1])).on_blue();
let green = Paragraph::new("·".repeat(12)).on_green();

View file

@ -198,7 +198,7 @@ fn run_app<B: Backend>(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
fn ui(f: &mut Frame, app: &mut App) {
// Create two chunks with equal horizontal screen space
let chunks = Layout::default()
.direction(Direction::Horizontal)

View file

@ -43,7 +43,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
}
}
fn ui<B: Backend>(frame: &mut Frame<B>) {
fn ui(frame: &mut Frame) {
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Length(1), Constraint::Min(0)])

View file

@ -102,7 +102,7 @@ fn run_tui<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<
}
/// Render the TUI.
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let text = vec![
if app.hook_enabled {
Line::from("HOOK IS CURRENTLY **ENABLED**")

View file

@ -81,7 +81,7 @@ fn run_app<B: Backend>(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let size = f.size();
// Words made "loooong" to demonstrate line breaking.

View file

@ -61,7 +61,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let size = f.size();
let chunks = Layout::default()

View file

@ -94,7 +94,7 @@ fn run_app<B: Backend>(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
fn ui(f: &mut Frame, app: &mut App) {
let size = f.size();
// Words made "loooong" to demonstrate line breaking.

View file

@ -126,7 +126,7 @@ fn run_app<B: Backend>(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(

View file

@ -113,7 +113,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
fn ui(f: &mut Frame, app: &mut App) {
let rects = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.split(f.size());

View file

@ -78,7 +78,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let size = f.size();
let chunks = Layout::default()
.direction(Direction::Vertical)

View file

@ -171,7 +171,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
fn ui(f: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(

View file

@ -195,7 +195,7 @@ pub(crate) enum SegmentSize {
/// ```rust
/// use ratatui::{prelude::*, widgets::*};
///
/// fn render<B: Backend>(frame: &mut Frame<B>, area: Rect) {
/// fn render(frame: &mut Frame, area: Rect) {
/// let layout = Layout::default()
/// .direction(Direction::Vertical)
/// .constraints(vec![Constraint::Length(5), Constraint::Min(0)])

View file

@ -133,7 +133,7 @@
//! ```rust,no_run
//! use ratatui::{prelude::*, widgets::*};
//!
//! fn ui<B: Backend>(f: &mut Frame<B>) {
//! fn ui(f: &mut Frame) {
//! let chunks = Layout::default()
//! .direction(Direction::Vertical)
//! .margin(1)

View file

@ -225,10 +225,11 @@ where
}
/// Get a Frame object which provides a consistent view into the terminal state for rendering.
pub fn get_frame(&mut self) -> Frame<B> {
pub fn get_frame(&mut self) -> Frame {
Frame {
terminal: self,
cursor_position: None,
viewport_area: self.viewport_area,
buffer: self.current_buffer_mut(),
}
}
@ -321,7 +322,7 @@ where
/// ```
pub fn draw<F>(&mut self, f: F) -> io::Result<CompletedFrame>
where
F: FnOnce(&mut Frame<B>),
F: FnOnce(&mut Frame),
{
// Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
// and the terminal (if growing), which may OOB.
@ -562,29 +563,25 @@ fn compute_inline_size<B: Backend>(
/// [`Backend`]: crate::backend::Backend
/// [`Buffer`]: crate::buffer::Buffer
#[derive(Debug, Hash)]
pub struct Frame<'a, B: 'a>
where
B: Backend,
{
/// The terminal that this frame is associated with.
terminal: &'a mut Terminal<B>,
pub struct Frame<'a> {
/// Where should the cursor be after drawing this frame?
///
/// If `None`, the cursor is hidden and its position is controlled by the backend. If `Some((x,
/// y))`, the cursor is shown and placed at `(x, y)` after the call to `Terminal::draw()`.
cursor_position: Option<(u16, u16)>,
/// The area of the viewport
viewport_area: Rect,
/// The buffer that is used to draw the current frame
buffer: &'a mut Buffer,
}
impl<'a, B> Frame<'a, B>
where
B: Backend,
{
impl Frame<'_> {
/// The size of the current frame
///
/// This is guaranteed not to change when rendering.
pub fn size(&self) -> Rect {
self.terminal.viewport_area
self.viewport_area
}
/// Render a [`Widget`] to the current buffer using [`Widget::render`].
@ -609,7 +606,7 @@ where
where
W: Widget,
{
widget.render(area, self.terminal.current_buffer_mut());
widget.render(area, self.buffer);
}
/// Render a [`StatefulWidget`] to the current buffer using [`StatefulWidget::render`].
@ -641,7 +638,7 @@ where
where
W: StatefulWidget,
{
widget.render(area, self.terminal.current_buffer_mut(), state);
widget.render(area, self.buffer, state);
}
/// After drawing this frame, make the cursor visible and put it at the specified (x, y)

View file

@ -485,7 +485,7 @@ impl<'a> Block<'a> {
/// Draw a block nested within another block
/// ```
/// # use ratatui::{prelude::*, widgets::*};
/// # fn render_nested_block<B: Backend>(frame: &mut Frame<B>) {
/// # fn render_nested_block(frame: &mut Frame) {
/// let outer_block = Block::default().title("Outer").borders(Borders::ALL);
/// let inner_block = Block::default().title("Inner").borders(Borders::ALL);
///

View file

@ -10,7 +10,7 @@ use crate::{buffer::Buffer, layout::Rect, widgets::Widget};
/// ```
/// use ratatui::{prelude::*, widgets::*};
///
/// fn draw_on_clear<B: Backend>(f: &mut Frame<B>, area: Rect) {
/// fn draw_on_clear(f: &mut Frame, area: Rect) {
/// let block = Block::default().title("Block").borders(Borders::ALL);
/// f.render_widget(Clear, area); // <- this will clear/reset the area first
/// f.render_widget(block, area); // now render the block widget

View file

@ -143,7 +143,7 @@ pub enum ScrollbarOrientation {
/// ```rust
/// use ratatui::{prelude::*, widgets::*};
///
/// # fn render_paragraph_with_scrollbar<B: Backend>(frame: &mut Frame<B>, area: Rect) {
/// # fn render_paragraph_with_scrollbar(frame: &mut Frame, area: Rect) {
///
/// let vertical_scroll = 0; // from app state
///