Skip to content

Commit

Permalink
feat(backend): add Size struct for when a Rect is too much
Browse files Browse the repository at this point in the history
Add a `Size` struct for the cases where a `Rect`'s `x`/`y` is unused
(always zero).

A `Size` can be made into a `Rect` but not the opposite.

`Size::new` is "clipped" so that the area is under max u16, just like
`Rect`. However, just like `Rect`, a `Size` literal can be constructed
without this constraint. For example, the `window_size()` pixel size is
not constrained by the area.
  • Loading branch information
benjajaja committed Aug 12, 2023
1 parent a14c41f commit 8601a65
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 41 deletions.
14 changes: 6 additions & 8 deletions src/backend/crossterm.rs
Expand Up @@ -20,7 +20,7 @@ use crossterm::{
use crate::{
backend::{Backend, ClearType, WindowSize},
buffer::Cell,
layout::Rect,
layout::Size,
style::{Color, Modifier},
};

Expand Down Expand Up @@ -168,11 +168,9 @@ where
self.buffer.flush()
}

fn size(&self) -> io::Result<Rect> {
let (width, height) =
terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;

Ok(Rect::new(0, 0, width, height))
fn size(&self) -> io::Result<Size> {
let (width, height) = terminal::size()?;
Ok(Size::new(width, height))
}

Check warning on line 174 in src/backend/crossterm.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/crossterm.rs#L171-L174

Added lines #L171 - L174 were not covered by tests

fn window_size(&mut self) -> Result<WindowSize, io::Error> {

Check warning on line 176 in src/backend/crossterm.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/crossterm.rs#L176

Added line #L176 was not covered by tests
Expand All @@ -183,8 +181,8 @@ where
height,
} = terminal::window_size()?;
Ok(WindowSize {
columns_rows: (columns, rows),
window_pixels: (width, height),
columns_rows: Size::new(columns, rows),
window_pixels: Size { width, height },
})

Check warning on line 186 in src/backend/crossterm.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/crossterm.rs#L178-L186

Added lines #L178 - L186 were not covered by tests
}

Expand Down
8 changes: 4 additions & 4 deletions src/backend/mod.rs
Expand Up @@ -27,7 +27,7 @@

use std::io;

use crate::{buffer::Cell, layout::Rect};
use crate::{buffer::Cell, layout::Size};

#[cfg(feature = "termion")]
mod termion;
Expand Down Expand Up @@ -59,8 +59,8 @@ pub enum ClearType {
}

pub struct WindowSize {
pub columns_rows: (u16, u16),
pub window_pixels: (u16, u16),
pub columns_rows: Size,
pub window_pixels: Size,
}

/// The `Backend` trait provides an abstraction over different terminal libraries.
Expand Down Expand Up @@ -115,7 +115,7 @@ pub trait Backend {
}

/// Get the size of the terminal screen in columns/rows as a [`Rect`].
fn size(&self) -> Result<Rect, io::Error>;
fn size(&self) -> Result<Size, io::Error>;

/// Get the size of the terminal screen in columns/rows and pixels as [`(Rect, (width,
/// height))`].
Expand Down
14 changes: 9 additions & 5 deletions src/backend/termion.rs
Expand Up @@ -12,7 +12,7 @@ use std::{
use crate::{
backend::{Backend, ClearType, WindowSize},
buffer::Cell,
layout::Rect,
layout::Size,
style::{Color, Modifier},
};

Expand Down Expand Up @@ -155,15 +155,19 @@ where
)
}

fn size(&self) -> io::Result<Rect> {
fn size(&self) -> io::Result<Size> {

Check warning on line 158 in src/backend/termion.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/termion.rs#L158

Added line #L158 was not covered by tests
let terminal = termion::terminal_size()?;
Ok(Rect::new(0, 0, terminal.0, terminal.1))
Ok(Size::new(terminal.0, terminal.1))
}

Check warning on line 161 in src/backend/termion.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/termion.rs#L160-L161

Added lines #L160 - L161 were not covered by tests

fn window_size(&mut self) -> Result<WindowSize, io::Error> {
let (pixel_width, pixel_height) = termion::terminal_size_pixels()?;

Check warning on line 164 in src/backend/termion.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/termion.rs#L163-L164

Added lines #L163 - L164 were not covered by tests
Ok(WindowSize {
columns_rows: termion::terminal_size()?,
window_pixels: termion::terminal_size_pixels()?,
columns_rows: self.size()?,
window_pixels: Size {
width: pixel_width,
height: pixel_height,
},
})
}

Expand Down
13 changes: 8 additions & 5 deletions src/backend/termwiz.rs
Expand Up @@ -17,7 +17,7 @@ use termwiz::{
use crate::{
backend::{Backend, WindowSize},
buffer::Cell,
layout::Rect,
layout::Size,
style::{Color, Modifier},
};

Expand Down Expand Up @@ -168,9 +168,9 @@ impl Backend for TermwizBackend {
Ok(())
}

fn size(&self) -> Result<Rect, io::Error> {
fn size(&self) -> Result<Size, io::Error> {
let (cols, rows) = self.buffered_terminal.dimensions();
Ok(Rect::new(0, 0, u16_max(cols), u16_max(rows)))
Ok(Size::new(u16_max(cols), u16_max(rows)))
}

fn window_size(&mut self) -> Result<WindowSize, io::Error> {

Check warning on line 176 in src/backend/termwiz.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L176

Added line #L176 was not covered by tests
Expand All @@ -185,8 +185,11 @@ impl Backend for TermwizBackend {
.get_screen_size()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
Ok(WindowSize {
columns_rows: (u16_max(cols), u16_max(rows)),
window_pixels: (u16_max(xpixel), u16_max(ypixel)),
columns_rows: Size::new(u16_max(cols), u16_max(rows)),
window_pixels: Size {
width: u16_max(xpixel),
height: u16_max(ypixel),

Check warning on line 191 in src/backend/termwiz.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L178-L191

Added lines #L178 - L191 were not covered by tests
},
})

Check warning on line 193 in src/backend/termwiz.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L193

Added line #L193 was not covered by tests
}

Expand Down
13 changes: 8 additions & 5 deletions src/backend/test.rs
Expand Up @@ -11,7 +11,7 @@ use unicode_width::UnicodeWidthStr;
use crate::{
backend::{Backend, WindowSize},
buffer::{Buffer, Cell},
layout::Rect,
layout::{Rect, Size},
};

/// A backend used for the integration tests.
Expand Down Expand Up @@ -173,15 +173,18 @@ impl Backend for TestBackend {
Ok(())
}

fn size(&self) -> Result<Rect, io::Error> {
Ok(Rect::new(0, 0, self.width, self.height))
fn size(&self) -> Result<Size, io::Error> {
Ok(Size::new(self.width, self.height))
}

fn window_size(&mut self) -> Result<WindowSize, io::Error> {
// Some arbitrary window pixel size, probably doesn't need much testing.
static WINDOW_PIXEL_SIZE: (u16, u16) = (640, 480);
static WINDOW_PIXEL_SIZE: Size = Size {
width: 640,
height: 480,
};
Ok(WindowSize {
columns_rows: (self.width, self.height),
columns_rows: self.size()?,
window_pixels: WINDOW_PIXEL_SIZE,

Check warning on line 188 in src/backend/test.rs

View check run for this annotation

Codecov / codecov/patch

src/backend/test.rs#L180-L188

Added lines #L180 - L188 were not covered by tests
})
}
Expand Down
35 changes: 35 additions & 0 deletions src/layout.rs
Expand Up @@ -555,6 +555,41 @@ impl Rect {
}
}

/// A simple size struct
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
pub struct Size {
pub width: u16,
pub height: u16,
}

impl Size {
/// Creates a new size, with width and height limited to keep the area under max u16.
/// If clipped, aspect ratio will be preserved.
pub fn new(width: u16, height: u16) -> Size {
let max_area = u16::max_value();
let (clipped_width, clipped_height) =
if u32::from(width) * u32::from(height) > u32::from(max_area) {
let aspect_ratio = f64::from(width) / f64::from(height);
let max_area_f = f64::from(max_area);
let height_f = (max_area_f / aspect_ratio).sqrt();
let width_f = height_f * aspect_ratio;
(width_f as u16, height_f as u16)
} else {
(width, height)
};
Size {
width: clipped_width,
height: clipped_height,
}
}
}

impl From<Size> for Rect {
fn from(size: Size) -> Self {
Rect::new(0, 0, size.width, size.height)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
22 changes: 11 additions & 11 deletions src/terminal.rs
Expand Up @@ -3,7 +3,7 @@ use std::io;
use crate::{
backend::{Backend, ClearType},
buffer::Buffer,
layout::Rect,
layout::{Rect, Size},
widgets::{StatefulWidget, Widget},
};

Expand Down Expand Up @@ -40,7 +40,7 @@ where
viewport: Viewport,
viewport_area: Rect,
/// Last known size of the terminal. Used to detect if the internal buffers have to be resized.
last_known_size: Rect,
last_known_size: Size,
/// Last known position of the cursor. Used to find the new area when the viewport is inlined
/// and the terminal resized.
last_known_cursor_pos: (u16, u16),
Expand Down Expand Up @@ -142,7 +142,7 @@ where
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CompletedFrame<'a> {
pub buffer: &'a Buffer,
pub area: Rect,
pub size: Size,
}

impl<B> Drop for Terminal<B>
Expand Down Expand Up @@ -177,10 +177,10 @@ where
pub fn with_options(mut backend: B, options: TerminalOptions) -> io::Result<Terminal<B>> {
let size = match options.viewport {
Viewport::Fullscreen | Viewport::Inline(_) => backend.size()?,
Viewport::Fixed(area) => area,
Viewport::Fixed(area) => Size::new(area.width, area.height),
};
let (viewport_area, cursor_pos) = match options.viewport {
Viewport::Fullscreen => (size, (0, 0)),
Viewport::Fullscreen => (size.into(), (0, 0)),
Viewport::Inline(height) => compute_inline_size(&mut backend, height, size, 0)?,
Viewport::Fixed(area) => (area, (area.left(), area.top())),
};
Expand Down Expand Up @@ -231,9 +231,9 @@ where
/// Updates the Terminal so that internal buffers match the requested size. Requested size will
/// be saved so the size can remain consistent when rendering.
/// This leads to a full clear of the screen.
pub fn resize(&mut self, size: Rect) -> io::Result<()> {
pub fn resize(&mut self, size: Size) -> io::Result<()> {
let next_area = match self.viewport {
Viewport::Fullscreen => size,
Viewport::Fullscreen => size.into(),
Viewport::Inline(height) => {
let offset_in_previous_viewport = self
.last_known_cursor_pos
Expand Down Expand Up @@ -303,7 +303,7 @@ where

Ok(CompletedFrame {
buffer: &self.buffers[1 - self.current],
area: self.last_known_size,
size: self.last_known_size,
})
}

Expand Down Expand Up @@ -357,7 +357,7 @@ where
}

/// Queries the real size of the backend.
pub fn size(&self) -> io::Result<Rect> {
pub fn size(&self) -> io::Result<Size> {
self.backend.size()
}

Expand Down Expand Up @@ -420,7 +420,7 @@ where
let height = height.min(self.last_known_size.height);
self.backend.append_lines(height)?;
let missing_lines =
height.saturating_sub(self.last_known_size.bottom() - self.viewport_area.top());
height.saturating_sub(self.last_known_size.height - self.viewport_area.top());

Check warning on line 423 in src/terminal.rs

View check run for this annotation

Codecov / codecov/patch

src/terminal.rs#L423

Added line #L423 was not covered by tests
let area = Rect {
x: self.viewport_area.left(),
y: self.viewport_area.top().saturating_sub(missing_lines),
Expand Down Expand Up @@ -456,7 +456,7 @@ where
fn compute_inline_size<B: Backend>(
backend: &mut B,
height: u16,
size: Rect,
size: Size,

Check warning on line 459 in src/terminal.rs

View check run for this annotation

Codecov / codecov/patch

src/terminal.rs#L459

Added line #L459 was not covered by tests
offset_in_previous_viewport: u16,
) -> io::Result<(Rect, (u16, u16))> {
let pos = backend.get_cursor()?;
Expand Down
6 changes: 3 additions & 3 deletions tests/terminal.rs
Expand Up @@ -2,7 +2,7 @@ use std::error::Error;

use ratatui::{
backend::{Backend, TestBackend},
layout::Rect,
layout::Size,
widgets::Paragraph,
Terminal,
};
Expand Down Expand Up @@ -37,13 +37,13 @@ fn terminal_draw_returns_the_completed_frame() -> Result<(), Box<dyn Error>> {
f.render_widget(paragraph, f.size());
})?;
assert_eq!(frame.buffer.get(0, 0).symbol, "T");
assert_eq!(frame.area, Rect::new(0, 0, 10, 10));
assert_eq!(frame.size, Size::new(10, 10));
terminal.backend_mut().resize(8, 8);
let frame = terminal.draw(|f| {
let paragraph = Paragraph::new("test");
f.render_widget(paragraph, f.size());
})?;
assert_eq!(frame.buffer.get(0, 0).symbol, "t");
assert_eq!(frame.area, Rect::new(0, 0, 8, 8));
assert_eq!(frame.size, Size::new(8, 8));
Ok(())
}

0 comments on commit 8601a65

Please sign in to comment.