Skip to content

Commit d077903

Browse files
authored
feat(backend): backend provides window_size, add Size struct (#276)
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.
1 parent 51fdcbe commit d077903

File tree

6 files changed

+109
-28
lines changed

6 files changed

+109
-28
lines changed

src/backend/crossterm.rs

+20-5
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ use crossterm::{
1818
};
1919

2020
use crate::{
21-
backend::{Backend, ClearType},
21+
backend::{Backend, ClearType, WindowSize},
2222
buffer::Cell,
23-
layout::Rect,
23+
layout::Size,
24+
prelude::Rect,
2425
style::{Color, Modifier},
2526
};
2627

@@ -169,12 +170,26 @@ where
169170
}
170171

171172
fn size(&self) -> io::Result<Rect> {
172-
let (width, height) =
173-
terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
174-
173+
let (width, height) = terminal::size()?;
175174
Ok(Rect::new(0, 0, width, height))
176175
}
177176

177+
fn window_size(&mut self) -> Result<WindowSize, io::Error> {
178+
let crossterm::terminal::WindowSize {
179+
columns,
180+
rows,
181+
width,
182+
height,
183+
} = terminal::window_size()?;
184+
Ok(WindowSize {
185+
columns_rows: Size {
186+
width: columns,
187+
height: rows,
188+
},
189+
pixels: Size { width, height },
190+
})
191+
}
192+
178193
fn flush(&mut self) -> io::Result<()> {
179194
self.buffer.flush()
180195
}

src/backend/mod.rs

+21-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use std::io;
2929

3030
use strum::{Display, EnumString};
3131

32-
use crate::{buffer::Cell, layout::Rect};
32+
use crate::{buffer::Cell, layout::Size, prelude::Rect};
3333

3434
#[cfg(feature = "termion")]
3535
mod termion;
@@ -60,6 +60,18 @@ pub enum ClearType {
6060
UntilNewLine,
6161
}
6262

63+
/// The window sizes in columns,rows and optionally pixel width,height.
64+
pub struct WindowSize {
65+
/// Size in character/cell columents,rows.
66+
pub columns_rows: Size,
67+
/// Size in pixel width,height.
68+
///
69+
/// The `pixels` fields may not be implemented by all terminals and return `0,0`.
70+
/// See https://man7.org/linux/man-pages/man4/tty_ioctl.4.html under section
71+
/// "Get and set window size" / TIOCGWINSZ where the fields are commented as "unused".
72+
pub pixels: Size,
73+
}
74+
6375
/// The `Backend` trait provides an abstraction over different terminal libraries.
6476
/// It defines the methods required to draw content, manipulate the cursor, and
6577
/// clear the terminal screen.
@@ -111,9 +123,16 @@ pub trait Backend {
111123
}
112124
}
113125

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

129+
/// Get the size of the terminal screen in columns/rows and pixels as [`WindowSize`].
130+
///
131+
/// The reason for this not returning only the pixel size, given the redundancy with the
132+
/// `size()` method, is that the underlying backends most likely get both values with one
133+
/// syscall, and the user is also most likely to need columns,rows together with pixel size.
134+
fn window_size(&mut self) -> Result<WindowSize, io::Error>;
135+
117136
/// Flush any buffered content to the terminal screen.
118137
fn flush(&mut self) -> Result<(), io::Error>;
119138
}

src/backend/termion.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ use std::{
1010
};
1111

1212
use crate::{
13-
backend::{Backend, ClearType},
13+
backend::{Backend, ClearType, WindowSize},
1414
buffer::Cell,
15-
layout::Rect,
15+
prelude::Rect,
1616
style::{Color, Modifier},
1717
};
1818

@@ -160,6 +160,13 @@ where
160160
Ok(Rect::new(0, 0, terminal.0, terminal.1))
161161
}
162162

163+
fn window_size(&mut self) -> Result<WindowSize, io::Error> {
164+
Ok(WindowSize {
165+
columns_rows: termion::terminal_size()?.into(),
166+
pixels: termion::terminal_size_pixels()?.into(),
167+
})
168+
}
169+
163170
fn flush(&mut self) -> io::Result<()> {
164171
self.stdout.flush()
165172
}

src/backend/termwiz.rs

+32-17
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ use termwiz::{
1111
cell::{AttributeChange, Blink, Intensity, Underline},
1212
color::{AnsiColor, ColorAttribute, SrgbaTuple},
1313
surface::{Change, CursorVisibility, Position},
14-
terminal::{buffered::BufferedTerminal, SystemTerminal, Terminal},
14+
terminal::{buffered::BufferedTerminal, ScreenSize, SystemTerminal, Terminal},
1515
};
1616

1717
use crate::{
18-
backend::Backend,
18+
backend::{Backend, WindowSize},
1919
buffer::Cell,
20-
layout::Rect,
20+
layout::Size,
21+
prelude::Rect,
2122
style::{Color, Modifier},
2223
};
2324

@@ -169,22 +170,31 @@ impl Backend for TermwizBackend {
169170
}
170171

171172
fn size(&self) -> Result<Rect, io::Error> {
172-
let (term_width, term_height) = self.buffered_terminal.dimensions();
173-
let max = u16::max_value();
174-
Ok(Rect::new(
175-
0,
176-
0,
177-
if term_width > usize::from(max) {
178-
max
179-
} else {
180-
term_width as u16
173+
let (cols, rows) = self.buffered_terminal.dimensions();
174+
Ok(Rect::new(0, 0, u16_max(cols), u16_max(rows)))
175+
}
176+
177+
fn window_size(&mut self) -> Result<WindowSize, io::Error> {
178+
let ScreenSize {
179+
cols,
180+
rows,
181+
xpixel,
182+
ypixel,
183+
} = self
184+
.buffered_terminal
185+
.terminal()
186+
.get_screen_size()
187+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
188+
Ok(WindowSize {
189+
columns_rows: Size {
190+
width: u16_max(cols),
191+
height: u16_max(rows),
181192
},
182-
if term_height > usize::from(max) {
183-
max
184-
} else {
185-
term_height as u16
193+
pixels: Size {
194+
width: u16_max(xpixel),
195+
height: u16_max(ypixel),
186196
},
187-
))
197+
})
188198
}
189199

190200
fn flush(&mut self) -> Result<(), io::Error> {
@@ -221,3 +231,8 @@ impl From<Color> for ColorAttribute {
221231
}
222232
}
223233
}
234+
235+
#[inline]
236+
fn u16_max(i: usize) -> u16 {
237+
u16::try_from(i).unwrap_or(u16::MAX)
238+
}

src/backend/test.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use std::{
99
use unicode_width::UnicodeWidthStr;
1010

1111
use crate::{
12-
backend::Backend,
12+
backend::{Backend, WindowSize},
1313
buffer::{Buffer, Cell},
14-
layout::Rect,
14+
layout::{Rect, Size},
1515
};
1616

1717
/// A backend used for the integration tests.
@@ -179,6 +179,18 @@ impl Backend for TestBackend {
179179
Ok(Rect::new(0, 0, self.width, self.height))
180180
}
181181

182+
fn window_size(&mut self) -> Result<WindowSize, io::Error> {
183+
// Some arbitrary window pixel size, probably doesn't need much testing.
184+
static WINDOW_PIXEL_SIZE: Size = Size {
185+
width: 640,
186+
height: 480,
187+
};
188+
Ok(WindowSize {
189+
columns_rows: (self.width, self.height).into(),
190+
pixels: WINDOW_PIXEL_SIZE,
191+
})
192+
}
193+
182194
fn flush(&mut self) -> Result<(), io::Error> {
183195
Ok(())
184196
}

src/layout.rs

+13
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,19 @@ fn try_split(area: Rect, layout: &Layout) -> Result<Rc<[Rect]>, AddConstraintErr
647647
Ok(results)
648648
}
649649

650+
/// A simple size struct
651+
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
652+
pub struct Size {
653+
pub width: u16,
654+
pub height: u16,
655+
}
656+
657+
impl From<(u16, u16)> for Size {
658+
fn from((width, height): (u16, u16)) -> Self {
659+
Size { width, height }
660+
}
661+
}
662+
650663
#[cfg(test)]
651664
mod tests {
652665
use strum::ParseError;

0 commit comments

Comments
 (0)