Skip to content

Commit

Permalink
feat(backend): backend provides window_size, add Size struct
Browse files Browse the repository at this point in the history
For image (sixel, iTerm2, Kitty...) support that handles graphics in
terms of `Rect` so that the image area can be included in layouts.

For example: an image is loaded with a known pixel-size, and drawn, but
the image protocol has no mechanism of knowing the actual cell/character
area that been drawn on. It is then impossible to skip overdrawing the
area.

Returning the window size in pixel-width / pixel-height, together with
colums / rows, it can be possible to account the pixel size of each cell
/ character, and then known the `Rect` of a given image, and also resize
the image so that it fits exactly in a `Rect`.

Crossterm and termwiz also both return both sizes from one syscall,
while termion does two.

Add a `Size` struct for the cases where a `Rect`'s `x`/`y` is unused
(always zero).

`Size` is not "clipped" for `area < u16::max_value()` like `Rect`. This
is why there are `From` implementations between the two.
  • Loading branch information
benjajaja committed Aug 26, 2023
1 parent ad3413e commit 0a4b51f
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 28 deletions.
25 changes: 20 additions & 5 deletions src/backend/crossterm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ use crossterm::{
};

use crate::{
backend::{Backend, ClearType},
backend::{Backend, ClearType, WindowSize},
buffer::Cell,
layout::Rect,
layout::Size,
prelude::Rect,
style::{Color, Modifier},
};

Expand Down Expand Up @@ -169,12 +170,26 @@ where
}

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

let (width, height) = terminal::size()?;

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

View check run for this annotation

Codecov / codecov/patch

src/backend/crossterm.rs#L173

Added line #L173 was not covered by tests
Ok(Rect::new(0, 0, width, height))
}

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

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

View check run for this annotation

Codecov / codecov/patch

src/backend/crossterm.rs#L177

Added line #L177 was not covered by tests
let crossterm::terminal::WindowSize {
columns,
rows,
width,
height,
} = terminal::window_size()?;
Ok(WindowSize {
columns_rows: Size {
width: columns,
height: rows,
},
window_pixels: Size { width, height },
})
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/crossterm.rs#L179-L191

Added lines #L179 - L191 were not covered by tests

fn flush(&mut self) -> io::Result<()> {
self.buffer.flush()
}
Expand Down
16 changes: 14 additions & 2 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use std::io;

use strum::{Display, EnumString};

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

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

pub struct WindowSize {
pub columns_rows: Size,
pub window_pixels: Size,
}

/// The `Backend` trait provides an abstraction over different terminal libraries.
/// It defines the methods required to draw content, manipulate the cursor, and
/// clear the terminal screen.
Expand Down Expand Up @@ -111,9 +116,16 @@ pub trait Backend {
}
}

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

/// Get the size of the terminal screen in columns/rows and pixels as [`WindowSize`].
///
/// The reason for this not returning only the pixel size, given the redundancy with the
/// `size()` method, is that the underlying backends most likely get both values with one
/// syscall, and the user is also most likely to need columns,rows together with pixel size.
fn window_size(&mut self) -> Result<WindowSize, io::Error>;

/// Flush any buffered content to the terminal screen.
fn flush(&mut self) -> Result<(), io::Error>;
}
Expand Down
11 changes: 9 additions & 2 deletions src/backend/termion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use std::{
};

use crate::{
backend::{Backend, ClearType},
backend::{Backend, ClearType, WindowSize},
buffer::Cell,
layout::Rect,
prelude::Rect,
style::{Color, Modifier},
};

Expand Down Expand Up @@ -160,6 +160,13 @@ where
Ok(Rect::new(0, 0, terminal.0, terminal.1))
}

fn window_size(&mut self) -> Result<WindowSize, io::Error> {
Ok(WindowSize {
columns_rows: termion::terminal_size()?.into(),
window_pixels: termion::terminal_size_pixels()?.into(),

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termion.rs#L163-L166

Added lines #L163 - L166 were not covered by tests
})
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termion.rs#L168

Added line #L168 was not covered by tests

fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
}
Expand Down
49 changes: 32 additions & 17 deletions src/backend/termwiz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ use termwiz::{
cell::{AttributeChange, Blink, Intensity, Underline},
color::{AnsiColor, ColorAttribute, SrgbaTuple},
surface::{Change, CursorVisibility, Position},
terminal::{buffered::BufferedTerminal, SystemTerminal, Terminal},
terminal::{buffered::BufferedTerminal, ScreenSize, SystemTerminal, Terminal},
};

use crate::{
backend::Backend,
backend::{Backend, WindowSize},
buffer::Cell,
layout::Rect,
layout::Size,
prelude::Rect,
style::{Color, Modifier},
};

Expand Down Expand Up @@ -169,22 +170,31 @@ impl Backend for TermwizBackend {
}

fn size(&self) -> Result<Rect, io::Error> {
let (term_width, term_height) = self.buffered_terminal.dimensions();
let max = u16::max_value();
Ok(Rect::new(
0,
0,
if term_width > usize::from(max) {
max
} else {
term_width as u16
let (cols, rows) = self.buffered_terminal.dimensions();
Ok(Rect::new(0, 0, u16_max(cols), u16_max(rows)))
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L173-L175

Added lines #L173 - L175 were not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L177

Added line #L177 was not covered by tests
let ScreenSize {
cols,
rows,
xpixel,
ypixel,
} = self
.buffered_terminal
.terminal()
.get_screen_size()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(WindowSize {
columns_rows: Size {
width: u16_max(cols),
height: u16_max(rows),

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L179-L191

Added lines #L179 - L191 were not covered by tests
},
if term_height > usize::from(max) {
max
} else {
term_height as u16
window_pixels: Size {
width: u16_max(xpixel),
height: u16_max(ypixel),

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L193-L195

Added lines #L193 - L195 were not covered by tests
},
))
})

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L197

Added line #L197 was not covered by tests
}

fn flush(&mut self) -> Result<(), io::Error> {
Expand Down Expand Up @@ -221,3 +231,8 @@ impl From<Color> for ColorAttribute {
}
}
}

#[inline]
fn u16_max(i: usize) -> u16 {
u16::try_from(i).unwrap_or(u16::MAX)
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L236-L238

Added lines #L236 - L238 were not covered by tests
16 changes: 14 additions & 2 deletions src/backend/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use std::{
use unicode_width::UnicodeWidthStr;

use crate::{
backend::Backend,
backend::{Backend, WindowSize},
buffer::{Buffer, Cell},
layout::Rect,
layout::{Rect, Size},
};

/// A backend used for the integration tests.
Expand Down Expand Up @@ -179,6 +179,18 @@ impl Backend for TestBackend {
Ok(Rect::new(0, 0, 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: Size = Size {
width: 640,
height: 480,
};
Ok(WindowSize {
columns_rows: (self.width, self.height).into(),
window_pixels: WINDOW_PIXEL_SIZE,
})
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/test.rs#L182-L192

Added lines #L182 - L192 were not covered by tests

fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}
Expand Down
13 changes: 13 additions & 0 deletions src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,19 @@ fn try_split(area: Rect, layout: &Layout) -> Result<Rc<[Rect]>, AddConstraintErr
Ok(results)
}

/// A simple size struct
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]

Check warning on line 651 in src/layout.rs

View check run for this annotation

Codecov / codecov/patch

src/layout.rs#L651

Added line #L651 was not covered by tests
pub struct Size {
pub width: u16,
pub height: u16,
}

impl From<(u16, u16)> for Size {
fn from((width, height): (u16, u16)) -> Self {
Size { width, height }
}

Check warning on line 660 in src/layout.rs

View check run for this annotation

Codecov / codecov/patch

src/layout.rs#L658-L660

Added lines #L658 - L660 were not covered by tests
}

#[cfg(test)]
mod tests {
use strum::ParseError;
Expand Down

0 comments on commit 0a4b51f

Please sign in to comment.