Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ web-sys = { version = "0.3.81", features = [
'WebGlTexture',
'WebGlUniformLocation',
'WebGlVertexArrayObject',
'DomRect',
'Window',
] }
compact_str = "0.9.0"
Expand Down
78 changes: 72 additions & 6 deletions src/backend/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

use crate::{backend::utils::*, error::Error, CursorShape};

/// Default cell size used as a fallback when measurement fails.
const DEFAULT_CELL_SIZE: (f64, f64) = (10.0, 20.0);

/// Options for the [`DomBackend`].
#[derive(Debug, Default)]
pub struct DomBackendOptions {
Expand All @@ -41,7 +44,7 @@
///
/// - If the grid ID is not set, it returns `"grid"`.
/// - If the grid ID is set, it returns the grid ID suffixed with
/// `"_ratzilla_grid"`.

Check warning on line 47 in src/backend/dom.rs

View workflow job for this annotation

GitHub Actions / clippy

doc list item overindented

warning: doc list item overindented --> src/backend/dom.rs:47:9 | 47 | /// `"_ratzilla_grid"`. | ^^^^ help: try using ` ` (2 spaces) | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#doc_overindented_list_items = note: `#[warn(clippy::doc_overindented_list_items)]` on by default
pub fn grid_id(&self) -> String {
match &self.grid_id {
Some(id) => format!("{id}_ratzilla_grid"),
Expand Down Expand Up @@ -83,6 +86,8 @@
last_cursor_position: Option<Position>,
/// Buffer size to pass to [`ratatui::Terminal`]
size: Size,
/// Measured cell dimensions in pixels (width, height).
cell_size: (f64, f64),
}

impl DomBackend {
Expand All @@ -109,23 +114,75 @@
pub fn new_with_options(options: DomBackendOptions) -> Result<Self, Error> {
let window = window().ok_or(Error::UnableToRetrieveWindow)?;
let document = window.document().ok_or(Error::UnableToRetrieveDocument)?;
let grid_parent = get_element_by_id_or_body(options.grid_id.as_ref())?;
let cell_size =
Self::measure_cell_size(&document, &grid_parent).unwrap_or(DEFAULT_CELL_SIZE);
let size = Self::calculate_size(&grid_parent, cell_size);
let mut backend = Self {
initialized: Rc::new(RefCell::new(false)),
cells: vec![],
grid: document.create_element("div")?,
grid_parent: get_element_by_id_or_body(options.grid_id.as_ref())?,
grid_parent,
options,
window,
document,
cursor_position: None,
last_cursor_position: None,
size: get_size(),
size,
cell_size,
};
backend.add_on_resize_listener();
backend.reset_grid()?;
Ok(backend)
}

/// Measures the pixel dimensions of a single terminal cell.
///
/// Creates a temporary `<pre><span>` probe element that inherits the
/// page's CSS (font-family, font-size, etc.), measures it with
/// `getBoundingClientRect()`, then removes the probe.
fn measure_cell_size(document: &Document, parent: &Element) -> Result<(f64, f64), Error> {
let pre = document.create_element("pre")?;
pre.set_attribute(
"style",
"margin: 0; padding: 0; border: 0; line-height: normal;",
)?;
let span = document.create_element("span")?;
span.set_inner_html("\u{2588}");
span.set_attribute("style", "display: inline-block; width: 1ch;")?;
pre.append_child(&span)?;
parent.append_child(&pre)?;

let rect = span.get_bounding_client_rect();
let width = rect.width();
let height = rect.height();

parent.remove_child(&pre)?;

if width > 0.0 && height > 0.0 {
Ok((width, height))
} else {
Ok(DEFAULT_CELL_SIZE)
}
}

/// Calculates the grid size in cells based on the parent element's dimensions and cell size.
fn calculate_size(parent: &Element, cell_size: (f64, f64)) -> Size {
let rect = parent.get_bounding_client_rect();
let (parent_w, parent_h) = (rect.width(), rect.height());

// Fall back to window dimensions if the parent has no size
// (e.g. empty <body> with no explicit height)
let (w, h) = if parent_w > 0.0 && parent_h > 0.0 {
(parent_w, parent_h)
} else {
let (ww, wh) = get_raw_window_size();
(ww as f64, wh as f64)
};

Size::new((w / cell_size.0) as u16, (h / cell_size.1) as u16)
}

/// Add a listener to the window resize event.
fn add_on_resize_listener(&mut self) {
let initialized = self.initialized.clone();
Expand Down Expand Up @@ -160,7 +217,8 @@

// Create a <pre> element for the line
let pre = self.document.create_element("pre")?;
pre.set_attribute("style", "height: 15px;")?;
let line_height = format!("height: {}px;", self.cell_size.1);
pre.set_attribute("style", &line_height)?;

// Append all elements (spans and anchors) to the <pre>
for elem in line_cells {
Expand Down Expand Up @@ -201,8 +259,10 @@
self.grid_parent.set_inner_html("");
self.reset_grid()?;

// update size
self.size = get_size();
// re-measure cell size and update grid dimensions
self.cell_size = Self::measure_cell_size(&self.document, &self.grid_parent)
.unwrap_or(DEFAULT_CELL_SIZE);
self.size = Self::calculate_size(&self.grid_parent, self.cell_size);
}

self.grid_parent
Expand All @@ -220,15 +280,15 @@
.map_err(Error::from)?;

// don't display the next cell if a fullwidth glyph preceeds it
if cell.symbol().len() > 1 && cell.symbol().width() == 2 {
if (cell_position + 1) < self.cells.len() {
let next_elem = &self.cells[cell_position + 1];
next_elem.set_inner_html("");
next_elem
.set_attribute("style", &get_cell_style_as_css(&Cell::new("")))
.map_err(Error::from)?;
}
}

Check warning on line 291 in src/backend/dom.rs

View workflow job for this annotation

GitHub Actions / clippy

this `if` statement can be collapsed

warning: this `if` statement can be collapsed --> src/backend/dom.rs:283:13 | 283 | / if cell.symbol().len() > 1 && cell.symbol().width() == 2 { 284 | | if (cell_position + 1) < self.cells.len() { 285 | | let next_elem = &self.cells[cell_position + 1]; 286 | | next_elem.set_inner_html(""); ... | 291 | | } | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#collapsible_if = note: `#[warn(clippy::collapsible_if)]` on by default help: collapse nested if block | 283 ~ if cell.symbol().len() > 1 && cell.symbol().width() == 2 284 ~ && (cell_position + 1) < self.cells.len() { 285 | let next_elem = &self.cells[cell_position + 1]; ... 289 | .map_err(Error::from)?; 290 ~ } |
}

Ok(())
Expand Down Expand Up @@ -302,7 +362,13 @@
}

fn window_size(&mut self) -> IoResult<WindowSize> {
unimplemented!()
Ok(WindowSize {
columns_rows: self.size,
pixels: Size::new(
(self.size.width as f64 * self.cell_size.0) as u16,
(self.size.height as f64 * self.cell_size.1) as u16,
),
})
}

fn get_cursor_position(&mut self) -> IoResult<Position> {
Expand Down
Loading