Skip to content

Commit 01dac64

Browse files
authored
feat(backend): add CellSized trait for querying cell dimensions in physical and CSS pixels (#165)
1 parent a2af82e commit 01dac64

8 files changed

Lines changed: 100 additions & 4 deletions

File tree

examples/shared/src/backend.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use ratzilla::{
44
error::Error,
55
event::{KeyEvent, MouseEvent},
66
ratatui::{backend::Backend, prelude::backend::ClearType, Terminal, TerminalOptions},
7-
CanvasBackend, DomBackend, WebEventHandler, WebGl2Backend,
7+
CanvasBackend, CellSized, DomBackend, WebEventHandler, WebGl2Backend,
88
};
99
use std::{convert::TryFrom, fmt, io};
1010
use web_sys::{window, Url};
@@ -78,6 +78,24 @@ impl RatzillaBackend {
7878
}
7979
}
8080

81+
impl CellSized for RatzillaBackend {
82+
fn cell_size_px(&self) -> (f32, f32) {
83+
match self {
84+
RatzillaBackend::Dom(backend) => backend.cell_size_px(),
85+
RatzillaBackend::Canvas(backend) => backend.cell_size_px(),
86+
RatzillaBackend::WebGl2(backend) => backend.cell_size_px(),
87+
}
88+
}
89+
90+
fn cell_size_css_px(&self) -> (f32, f32) {
91+
match self {
92+
RatzillaBackend::Dom(backend) => backend.cell_size_css_px(),
93+
RatzillaBackend::Canvas(backend) => backend.cell_size_css_px(),
94+
RatzillaBackend::WebGl2(backend) => backend.cell_size_css_px(),
95+
}
96+
}
97+
}
98+
8199
impl Backend for RatzillaBackend {
82100
type Error = io::Error;
83101

@@ -244,6 +262,16 @@ impl From<RatzillaBackend> for FpsTrackingBackend {
244262
}
245263
}
246264

265+
impl CellSized for FpsTrackingBackend {
266+
fn cell_size_px(&self) -> (f32, f32) {
267+
self.inner.cell_size_px()
268+
}
269+
270+
fn cell_size_css_px(&self) -> (f32, f32) {
271+
self.inner.cell_size_css_px()
272+
}
273+
}
274+
247275
impl Backend for FpsTrackingBackend {
248276
type Error = io::Error;
249277

src/backend/canvas.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::io::{Error as IoError, Result as IoResult};
44

55
use crate::{
66
backend::{
7+
cell_sized::CellSized,
78
color::{actual_bg_color, actual_fg_color},
89
event_callback::{
910
create_mouse_event, EventCallback, MouseConfig, KEY_EVENT_TYPES, MOUSE_EVENT_TYPES,
@@ -438,6 +439,17 @@ impl CanvasBackend {
438439
}
439440
}
440441

442+
impl CellSized for CanvasBackend {
443+
fn cell_size_px(&self) -> (f32, f32) {
444+
let dpr = get_device_pixel_ratio();
445+
(CELL_WIDTH as f32 * dpr, CELL_HEIGHT as f32 * dpr)
446+
}
447+
448+
fn cell_size_css_px(&self) -> (f32, f32) {
449+
(CELL_WIDTH as f32, CELL_HEIGHT as f32)
450+
}
451+
}
452+
441453
impl Backend for CanvasBackend {
442454
type Error = IoError;
443455

src/backend/cell_sized.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// A type that knows the pixel dimensions of its terminal cells.
2+
///
3+
/// # Physical vs. CSS pixels
4+
///
5+
/// On HiDPI / Retina displays the device pixel ratio (DPR) causes one
6+
/// CSS pixel to map to multiple physical (device) pixels.
7+
///
8+
/// - **Physical pixels** (`cell_size_px`): the actual device pixels
9+
/// occupied by a cell. Useful for pixel-perfect rendering and GPU work.
10+
/// - **CSS pixels** (`cell_size_css_px`): the logical size as seen by
11+
/// the browser layout engine. Useful for DOM positioning, mouse
12+
/// coordinate translation, and canvas drawing.
13+
pub trait CellSized {
14+
/// Returns the size of a cell in physical (device) pixels as `(width, height)`.
15+
fn cell_size_px(&self) -> (f32, f32);
16+
17+
/// Returns the size of a cell in CSS (logical) pixels as `(width, height)`.
18+
fn cell_size_css_px(&self) -> (f32, f32);
19+
}

src/backend/dom.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use unicode_width::UnicodeWidthStr;
1616

1717
use crate::{
1818
backend::{
19+
cell_sized::CellSized,
1920
event_callback::{
2021
create_mouse_event, EventCallback, MouseConfig, KEY_EVENT_TYPES, MOUSE_EVENT_TYPES,
2122
},
@@ -265,6 +266,17 @@ impl DomBackend {
265266
}
266267
}
267268

269+
impl CellSized for DomBackend {
270+
fn cell_size_px(&self) -> (f32, f32) {
271+
let dpr = get_device_pixel_ratio();
272+
(self.cell_size.0 as f32 * dpr, self.cell_size.1 as f32 * dpr)
273+
}
274+
275+
fn cell_size_css_px(&self) -> (f32, f32) {
276+
(self.cell_size.0 as f32, self.cell_size.1 as f32)
277+
}
278+
}
279+
268280
impl Backend for DomBackend {
269281
type Error = IoError;
270282

src/backend/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,7 @@ pub(super) mod event_callback;
7777
/// Backend utilities.
7878
pub(crate) mod utils;
7979

80+
/// Cell size metrics for backends.
81+
pub mod cell_sized;
8082
/// Cursor shapes.
8183
pub mod cursor;

src/backend/utils.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ pub(crate) fn get_window() -> Result<Window, Error> {
246246
window().ok_or(Error::UnableToRetrieveWindow)
247247
}
248248

249+
/// Returns the device pixel ratio from the window.
250+
pub(crate) fn get_device_pixel_ratio() -> f32 {
251+
get_window().map(|w| w.device_pixel_ratio()).unwrap_or(1.0) as f32
252+
}
253+
249254
/// Returns an element by its ID or the body element if no ID is provided.
250255
pub(crate) fn get_element_by_id_or_body(id: Option<&String>) -> Result<web_sys::Element, Error> {
251256
match id {

src/backend/webgl2.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ use std::{
2727
mem::swap,
2828
rc::Rc,
2929
};
30-
use web_sys::{wasm_bindgen::JsCast, window, Element};
30+
use web_sys::{wasm_bindgen::JsCast, Element};
3131

32+
use crate::backend::cell_sized::CellSized;
3233
/// Re-export beamterm's atlas data type. Used by [`FontAtlasConfig::Static`].
3334
pub use beamterm_renderer::FontAtlasData;
3435

@@ -205,7 +206,7 @@ impl WebGl2BackendOptions {
205206
/// Sets up a default mouse handler using [`WebGl2BackendOptions::on_hyperlink_click`].
206207
pub fn enable_hyperlinks(self) -> Self {
207208
self.on_hyperlink_click(|url| {
208-
if let Some(w) = window() {
209+
if let Ok(w) = get_window() {
209210
w.open_with_url_and_target(url, "_blank")
210211
.unwrap_or_default();
211212
}
@@ -425,8 +426,13 @@ impl WebGl2Backend {
425426
///
426427
/// For static atlases, this is the cell size from the atlas data.
427428
/// For dynamic atlases, this is measured from the rasterized font.
429+
#[deprecated(
430+
since = "0.4.0",
431+
note = "Use cell_size_px instead, which returns physical pixel dimensions"
432+
)]
428433
pub fn cell_size(&self) -> (i32, i32) {
429-
self.beamterm.cell_size()
434+
let (w, h) = self.cell_size_px();
435+
(w as i32, h as i32)
430436
}
431437

432438
/// Resizes the canvas and terminal grid to the specified logical pixel dimensions.
@@ -672,6 +678,17 @@ impl WebGl2Backend {
672678
}
673679
}
674680

681+
impl CellSized for WebGl2Backend {
682+
fn cell_size_px(&self) -> (f32, f32) {
683+
let (w, h) = self.beamterm.cell_size();
684+
(w as f32, h as f32)
685+
}
686+
687+
fn cell_size_css_px(&self) -> (f32, f32) {
688+
self.beamterm.grid().borrow().css_cell_size()
689+
}
690+
}
691+
675692
impl Backend for WebGl2Backend {
676693
type Error = IoError;
677694

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub use web_sys;
2727

2828
pub use backend::{
2929
canvas::CanvasBackend,
30+
cell_sized::CellSized,
3031
cursor::CursorShape,
3132
dom::DomBackend,
3233
webgl2::{FontAtlasConfig, SelectionMode, WebGl2Backend},

0 commit comments

Comments
 (0)