Skip to content
Closed
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 beamterm-renderer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ features = [
'console',
'CanvasRenderingContext2d',
'Clipboard',
'CssStyleDeclaration',
'Document',
'Element',
'HtmlCanvasElement',
Expand Down
40 changes: 32 additions & 8 deletions beamterm-renderer/src/gl/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub struct Renderer {
canvas: web_sys::HtmlCanvasElement,
state: GlState,
canvas_padding_color: (f32, f32, f32),
canvas_size: (i32, i32),
pixel_ratio: f32,
}

impl Renderer {
Expand All @@ -34,6 +36,7 @@ impl Renderer {
///
/// # Parameters
/// * `canvas_id` - CSS selector for the canvas element (e.g., "canvas" or "#my-canvas")
/// * `pixel_ratio` - Canvas resolution multiplier for HiDPI displays
///
/// # Returns
/// * `Ok(Renderer)` - Successfully created renderer
Expand All @@ -42,9 +45,9 @@ impl Renderer {
/// # Errors
/// * `Error::UnableToRetrieveCanvas` - Canvas element not found
/// * `Error::FailedToRetrieveWebGl2RenderingContext` - WebGL2 not supported or failed to initialize
pub fn create(canvas_id: &str) -> Result<Self, Error> {
pub fn create(canvas_id: &str, pixel_ratio: f32) -> Result<Self, Error> {
let canvas = js::get_canvas_by_id(canvas_id)?;
Self::create_with_canvas(canvas)
Self::create_with_canvas(canvas, pixel_ratio)
}

/// Sets the background color for the canvas area outside the terminal grid.
Expand All @@ -60,6 +63,12 @@ impl Renderer {
self
}

/// Sets the pixel ratio and resizes the canvas accordingly.
pub fn set_pixel_ratio(&mut self, pixel_ratio: f32) {
self.pixel_ratio = pixel_ratio;
self.resize(self.canvas_size.0, self.canvas_size.1);
}

/// Creates a new renderer from an existing HTML canvas element.
///
/// This method takes ownership of an existing canvas element and initializes
Expand All @@ -68,12 +77,13 @@ impl Renderer {
///
/// # Parameters
/// * `canvas` - HTML canvas element to use for rendering
/// * `pixel_ratio` - Canvas resolution multiplier for HiDPI displays
///
/// # Returns
/// * `Ok(Renderer)` - Successfully created renderer
/// * `Err(Error)` - Failed to create WebGL context or initialize renderer
pub fn create_with_canvas(canvas: HtmlCanvasElement) -> Result<Self, Error> {
let (width, height) = (canvas.width(), canvas.height());
pub fn create_with_canvas(canvas: HtmlCanvasElement, pixel_ratio: f32) -> Result<Self, Error> {
let (width, height) = (canvas.width() as i32, canvas.height() as i32);

// initialize WebGL context
let gl = js::get_webgl2_context(&canvas)?;
Expand All @@ -84,6 +94,8 @@ impl Renderer {
canvas,
state,
canvas_padding_color: (0.0, 0.0, 0.0),
canvas_size: (width, height),
pixel_ratio,
};
renderer.resize(width as _, height as _);
Ok(renderer)
Expand All @@ -99,9 +111,21 @@ impl Renderer {
/// * `width` - New canvas width in pixels
/// * `height` - New canvas height in pixels
pub fn resize(&mut self, width: i32, height: i32) {
self.canvas.set_width(width as u32);
self.canvas.set_height(height as u32);
self.state.viewport(&self.gl, 0, 0, width, height);
self.canvas_size = (width, height);

let physical_width = (width as f32 * self.pixel_ratio).round() as i32;
let physical_height = (height as f32 * self.pixel_ratio).round() as i32;

self.canvas.set_width(physical_width as _);
self.canvas.set_height(physical_height as _);
self.canvas
.style()
.set_property("width", &format!("{width}px"));
self.canvas
.style()
.set_property("height", &format!("{height}px"));
self.state
.viewport(&self.gl, 0, 0, physical_width, physical_height);
}

/// Clears the framebuffer with the specified color.
Expand Down Expand Up @@ -164,7 +188,7 @@ impl Renderer {
/// # Returns
/// Tuple containing (width, height) in pixels
pub fn canvas_size(&self) -> (i32, i32) {
(self.canvas.width() as i32, self.canvas.height() as i32)
self.canvas_size
}

/// Checks if the WebGL context has been lost.
Expand Down
25 changes: 23 additions & 2 deletions beamterm-renderer/src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ impl Terminal {
}
}

/// Sets the pixel ratio of the renderer.
///
/// Call this when `window.devicePixelRatio` changes
/// (e.g., moving window between displays with different DPI).
pub fn set_pixel_ratio(&mut self, pixel_ratio: f32) {
self.renderer.set_pixel_ratio(pixel_ratio);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this calls Renderer::resize but the terminal grid isn't resized (ref Terminal::resize)

}

/// Returns the terminal dimensions in cells.
pub fn terminal_size(&self) -> (u16, u16) {
self.grid.borrow().terminal_size()
Expand Down Expand Up @@ -428,6 +436,7 @@ pub struct TerminalBuilder {
input_handler: Option<InputHandler>,
canvas_padding_color: u32,
enable_debug_api: bool,
pixel_ratio: f32,
}

#[derive(Debug)]
Expand All @@ -454,6 +463,7 @@ impl TerminalBuilder {
input_handler: None,
canvas_padding_color: 0x000000,
enable_debug_api: false,
pixel_ratio: 1.0,
}
}

Expand Down Expand Up @@ -596,12 +606,23 @@ impl TerminalBuilder {
self.mouse_selection_handler(options)
}

/// Sets the pixel ratio of the canvas.
///
/// To get native-resolution output in HiDPI displays, set this value to `window.devicePixelRatio`.
/// This also allows us to render in higher fps sacrificing the resolution.
pub fn pixel_ratio(mut self, pixel_ratio: f32) -> Self {
self.pixel_ratio = pixel_ratio;
self
}

/// Builds the terminal with the configured options.
pub fn build(self) -> Result<Terminal, Error> {
// setup renderer
let renderer = match self.canvas {
CanvasSource::Id(id) => Renderer::create(&id)?,
CanvasSource::Element(element) => Renderer::create_with_canvas(element)?,
CanvasSource::Id(id) => Renderer::create(&id, self.pixel_ratio)?,
CanvasSource::Element(element) => {
Renderer::create_with_canvas(element, self.pixel_ratio)?
},
};
let renderer = renderer.canvas_padding_color(self.canvas_padding_color);

Expand Down
10 changes: 8 additions & 2 deletions beamterm-renderer/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ impl BeamtermRenderer {
) -> Result<BeamtermRenderer, JsValue> {
console_error_panic_hook::set_once();

let renderer = Renderer::create(canvas_id)
let renderer = Renderer::create(canvas_id, 1.0)
.map_err(|e| JsValue::from_str(&format!("Failed to create renderer: {e}")))?;

let gl = renderer.gl();
Expand Down Expand Up @@ -534,7 +534,7 @@ impl BeamtermRenderer {
) -> Result<BeamtermRenderer, JsValue> {
console_error_panic_hook::set_once();

let renderer = Renderer::create(canvas_id)
let renderer = Renderer::create(canvas_id, 1.0)
.map_err(|e| JsValue::from_str(&format!("Failed to create renderer: {e}")))?;

let font_families: Vec<CompactString> = font_family
Expand Down Expand Up @@ -860,6 +860,12 @@ impl BeamtermRenderer {

Ok(())
}

/// Set the pixel ratio for HiDPI displays
#[wasm_bindgen(js_name = "setPixelRatio")]
pub fn set_pixel_ratio(&mut self, pixel_ratio: f32) {
self.renderer.set_pixel_ratio(pixel_ratio);
}
}

// Convert between Rust and WASM types
Expand Down
5 changes: 2 additions & 3 deletions examples/canvas_waves/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ edition = "2024"
publish = false

[dependencies]
ratzilla = { git = "https://github.com/junkdog/ratzilla", branch = "beamterm-latest" }
#ratzilla = { path = "../../../ratzilla" }
# ratzilla = { git = "https://github.com/junkdog/ratzilla", branch = "beamterm-latest" }
ratzilla = { path = "../../../ratzilla" }
console_error_panic_hook = "0.1.7"
tachyonfx = { version = "0.22.0", default-features = false, features = ["wasm"] }
web-time = "1.1.0"
web-sys = { version = "0.3", features = ["Window", "Location", "UrlSearchParams"] }

9 changes: 7 additions & 2 deletions examples/canvas_waves/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

mod wave_effect;

use ratzilla::ratatui::Terminal;
use ratzilla::backend::webgl2::{FontAtlasData, WebGl2Backend, WebGl2BackendOptions};
use ratzilla::ratatui::Terminal;
use ratzilla::WebRenderer;
use tachyonfx::{EffectRenderer, IntoEffect};
use wave_effect::WaveInterference;
Expand All @@ -27,12 +27,17 @@ fn main() -> std::io::Result<()> {
_ => FontAtlasData::default(),
};

let pixel_ratio = get_query_param("pixel_ratio")
.and_then(|p| p.parse::<f32>().ok())
.unwrap_or(1.0);

let backend = WebGl2Backend::new_with_options(
WebGl2BackendOptions::new()
.font_atlas(font_atlas)
.measure_performance(true)
.grid_id("container")
.enable_console_debug_api()
.enable_auto_pixel_ratio(),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function does not exist

)?;
let terminal = Terminal::new(backend)?;

Expand All @@ -51,4 +56,4 @@ fn main() -> std::io::Result<()> {

fn hack_10pt() -> FontAtlasData {
FontAtlasData::from_binary(include_bytes!("../data/hack-10pt.atlas")).unwrap()
}
}
44 changes: 44 additions & 0 deletions examples/canvas_waves/src/wave_effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,50 @@ impl Shader for WaveInterference {
));
});

// Draw text in the center with border
let text = "This is Beamterm";
let text_len = text.len() as u16;
let center_x = area.x + (area.width.saturating_sub(text_len + 4)) / 2;
let center_y = area.y + area.height / 2;

let border_left = center_x;
let border_right = center_x + text_len + 3;
let border_top = center_y.saturating_sub(1);
let border_bottom = center_y + 1;

// Draw border
for x in border_left..=border_right {
for y in border_top..=border_bottom {
if x >= area.x + area.width || y >= area.y + area.height {
continue;
}
let ch = match (x, y) {
(x, y) if x == border_left && y == border_top => '┌',
(x, y) if x == border_right && y == border_top => '┐',
(x, y) if x == border_left && y == border_bottom => '└',
(x, y) if x == border_right && y == border_bottom => '┘',
(_, y) if y == border_top || y == border_bottom => '─',
(x, _) if x == border_left || x == border_right => '│',
_ => ' ',
};
if let Some(cell) = buf.cell_mut((x, y)) {
cell.set_char(ch);
cell.set_fg(ratzilla::ratatui::style::Color::White);
}
}
}

// Draw text
for (i, ch) in text.chars().enumerate() {
let x = center_x + 2 + i as u16;
if x < area.x + area.width {
if let Some(cell) = buf.cell_mut((x, center_y)) {
cell.set_char(ch);
cell.set_fg(ratzilla::ratatui::style::Color::White);
}
}
}

None
}

Expand Down