Skip to content

Commit

Permalink
backend provides window_size
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`.
  • Loading branch information
benjajaja committed Jun 25, 2023
1 parent 8467002 commit 4893685
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 18 deletions.
7 changes: 6 additions & 1 deletion Cargo.toml
Expand Up @@ -38,7 +38,7 @@ cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
bitflags = "2.3"
cassowary = "0.3"
# crossterm = { version = "0.26", optional = true }
crossterm = { git = "https://github.com/benjajaja/crossterm", branch = "font-size", optional = true }
crossterm = { git = "https://github.com/benjajaja/crossterm", rev = "ad84a3829d55eac27cfdb7791b2e34c1732040ad", optional = true }
indoc = "2.0"
serde = { version = "1", optional = true, features = ["derive"] }
termion = { version = "2.0", optional = true }
Expand Down Expand Up @@ -158,3 +158,8 @@ doc-scrape-examples = true
name = "inline"
required-features = ["crossterm"]
doc-scrape-examples = true

[[example]]
name = "window_size"
required-features = ["crossterm"]
doc-scrape-examples = true
80 changes: 80 additions & 0 deletions examples/window_size.rs
@@ -0,0 +1,80 @@
use std::{error::Error, io};

use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{
backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout, Rect},
text::Line,
widgets::{Block, Borders, Paragraph},
Frame, Terminal,
};

fn main() -> Result<(), Box<dyn Error>> {
// setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;

// create app and run it
let res = run_app(&mut terminal);

// restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;

if let Err(err) = res {
println!("{err:?}");
}

Ok(())
}

fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
let (Rect { width, height, .. }, window_size) = terminal.backend_mut().window_size()?;
let char_size = (window_size.0 / width, window_size.1 / height);
loop {
terminal.draw(|f| ui(f, window_size, char_size))?;

if let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
return Ok(());
}
}
}
}

fn ui<B: Backend>(
f: &mut Frame<B>,
(window_width, window_height): (u16, u16),
(cell_width, cell_height): (u16, u16),
) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(80), Constraint::Percentage(10)].as_ref())
.split(f.size());

let block = Block::default().title("Block").borders(Borders::ALL);
let inner_area = block.inner(chunks[0]);
let p = Paragraph::new(vec![
Line::from(format!("Window size: {window_width},{window_height}")),
Line::from(format!("Cell size: {cell_width},{cell_height}")),
Line::from(format!(
"Block size: {},{}",
cell_width * inner_area.width,
cell_height * inner_area.height
)),
])
.block(block);
f.render_widget(p, chunks[0]);
}
8 changes: 8 additions & 0 deletions src/backend/crossterm.rs
Expand Up @@ -167,6 +167,14 @@ where
Ok(Rect::new(0, 0, width, height))
}

fn window_size(&mut self) -> Result<(Rect, (u16, u16)), io::Error> {
let window_size = terminal::window_size()?;
Ok((
Rect::new(0, 0, window_size.columns, window_size.rows),
(window_size.width, window_size.height),
))
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/crossterm.rs#L170-L176

Added lines #L170 - L176 were not covered by tests

fn flush(&mut self) -> io::Result<()> {
self.buffer.flush()
}
Expand Down
4 changes: 4 additions & 0 deletions src/backend/mod.rs
Expand Up @@ -112,6 +112,10 @@ pub trait Backend {
/// Get the size of the terminal screen as a [`Rect`].
fn size(&self) -> Result<Rect, io::Error>;

/// Get the size of the terminal screen as well as windows sixe in pixels as a [`Rect`] and a
/// `(u16, u16)` tuple.
fn window_size(&mut self) -> Result<(Rect, (u16, u16)), io::Error>;

/// Flush any buffered content to the terminal screen.
fn flush(&mut self) -> Result<(), io::Error>;
}
6 changes: 6 additions & 0 deletions src/backend/termion.rs
Expand Up @@ -159,6 +159,12 @@ where
Ok(Rect::new(0, 0, terminal.0, terminal.1))
}

fn window_size(&mut self) -> Result<(Rect, (u16, u16)), io::Error> {
let terminal = termion::terminal_size()?;
let pixels = termion::terminal_size_pixels()?;
Ok((Rect::new(0, 0, terminal.0, terminal.1), pixels))
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termion.rs#L162-L166

Added lines #L162 - L166 were not covered by tests

fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
}
Expand Down
48 changes: 31 additions & 17 deletions src/backend/termwiz.rs
Expand Up @@ -11,7 +11,7 @@ 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::{
Expand Down Expand Up @@ -169,22 +169,21 @@ 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
},
if term_height > usize::from(max) {
max
} else {
term_height as u16
},
))
Ok(self.buffered_terminal.dimensions().into())
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L172-L173

Added lines #L172 - L173 were not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L175

Added line #L175 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.to_string()))?;
Ok(((cols, rows).into(), (u16_max(xpixel), u16_max(ypixel))))

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L177-L186

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

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

impl From<(usize, usize)> for Rect {
fn from((cols, rows): (usize, usize)) -> Rect {
Rect::new(0, 0, u16_max(cols), u16_max(rows))
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L225-L227

Added lines #L225 - L227 were not covered by tests
}

fn u16_max(i: usize) -> u16 {
let max = u16::max_value();
if i > usize::from(max) {
max

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L230-L233

Added lines #L230 - L233 were not covered by tests
} else {
i as u16

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L235

Added line #L235 was not covered by tests
}
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/termwiz.rs#L237

Added line #L237 was not covered by tests
4 changes: 4 additions & 0 deletions src/backend/test.rs
Expand Up @@ -177,6 +177,10 @@ impl Backend for TestBackend {
Ok(Rect::new(0, 0, self.width, self.height))
}

fn window_size(&mut self) -> Result<(Rect, (u16, u16)), io::Error> {
Ok((Rect::new(0, 0, self.width, self.height), (7, 12)))
}

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

View check run for this annotation

Codecov / codecov/patch

src/backend/test.rs#L180-L182

Added lines #L180 - L182 were not covered by tests

fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}
Expand Down

0 comments on commit 4893685

Please sign in to comment.