From 254dfc1ebcc0050f8684e57152e16f1d8caa52d0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 28 Jun 2024 13:02:36 +0200 Subject: [PATCH] Fix broken mouse coordinates when there's padding on the canvas element (#4729) * Closes https://github.com/emilk/egui/issues/4725 Also fixes touch input being wrong if the web page is scrolled. --- crates/eframe/Cargo.toml | 2 +- crates/eframe/src/web/input.rs | 20 ++++++++++---------- crates/eframe/src/web/mod.rs | 28 +++++++++++++++++++++++++--- crates/eframe/src/web/text_agent.rs | 4 ++-- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index bb244b387..d9bbe6b97 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -241,8 +241,8 @@ web-sys = { workspace = true, features = [ "NodeList", "Performance", "ResizeObserver", - "ResizeObserverEntry", "ResizeObserverBoxOptions", + "ResizeObserverEntry", "ResizeObserverOptions", "ResizeObserverSize", "Storage", diff --git a/crates/eframe/src/web/input.rs b/crates/eframe/src/web/input.rs index a1b6a802d..0f6324533 100644 --- a/crates/eframe/src/web/input.rs +++ b/crates/eframe/src/web/input.rs @@ -1,15 +1,15 @@ -use super::{canvas_origin, AppRunner}; +use super::{canvas_content_rect, AppRunner}; pub fn pos_from_mouse_event( canvas: &web_sys::HtmlCanvasElement, event: &web_sys::MouseEvent, ctx: &egui::Context, ) -> egui::Pos2 { - let rect = canvas.get_bounding_client_rect(); + let rect = canvas_content_rect(canvas); let zoom_factor = ctx.zoom_factor(); egui::Pos2 { - x: (event.client_x() as f32 - rect.left() as f32) / zoom_factor, - y: (event.client_y() as f32 - rect.top() as f32) / zoom_factor, + x: (event.client_x() as f32 - rect.left()) / zoom_factor, + y: (event.client_y() as f32 - rect.top()) / zoom_factor, } } @@ -52,31 +52,31 @@ pub fn pos_from_touch_event( .or_else(|| event.touches().get(0)) .map_or(Default::default(), |touch| { *touch_id_for_pos = Some(egui::TouchId::from(touch.identifier())); - pos_from_touch(canvas_origin(canvas), &touch, egui_ctx) + pos_from_touch(canvas_content_rect(canvas), &touch, egui_ctx) }) } fn pos_from_touch( - canvas_origin: egui::Pos2, + canvas_rect: egui::Rect, touch: &web_sys::Touch, egui_ctx: &egui::Context, ) -> egui::Pos2 { let zoom_factor = egui_ctx.zoom_factor(); egui::Pos2 { - x: (touch.page_x() as f32 - canvas_origin.x) / zoom_factor, - y: (touch.page_y() as f32 - canvas_origin.y) / zoom_factor, + x: (touch.client_x() as f32 - canvas_rect.left()) / zoom_factor, + y: (touch.client_y() as f32 - canvas_rect.top()) / zoom_factor, } } pub fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web_sys::TouchEvent) { - let canvas_origin = canvas_origin(runner.canvas()); + let canvas_rect = canvas_content_rect(runner.canvas()); for touch_idx in 0..event.changed_touches().length() { if let Some(touch) = event.changed_touches().item(touch_idx) { runner.input.raw.events.push(egui::Event::Touch { device_id: egui::TouchDeviceId(0), id: egui::TouchId::from(touch.identifier()), phase, - pos: pos_from_touch(canvas_origin, &touch, runner.egui_ctx()), + pos: pos_from_touch(canvas_rect, &touch, runner.egui_ctx()), force: Some(touch.force()), }); } diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 212c0ae6b..07339d7e0 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -133,9 +133,31 @@ fn get_canvas_element_by_id_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElemen .unwrap_or_else(|| panic!("Failed to find canvas with id {canvas_id:?}")) } -fn canvas_origin(canvas: &web_sys::HtmlCanvasElement) -> egui::Pos2 { - let rect = canvas.get_bounding_client_rect(); - egui::pos2(rect.left() as f32, rect.top() as f32) +/// Returns the canvas in client coordinates. +fn canvas_content_rect(canvas: &web_sys::HtmlCanvasElement) -> egui::Rect { + let bounding_rect = canvas.get_bounding_client_rect(); + + let mut rect = egui::Rect::from_min_max( + egui::pos2(bounding_rect.left() as f32, bounding_rect.top() as f32), + egui::pos2(bounding_rect.right() as f32, bounding_rect.bottom() as f32), + ); + + // We need to subtract padding and border: + if let Some(window) = web_sys::window() { + if let Ok(Some(style)) = window.get_computed_style(canvas) { + let get_property = |name: &str| -> Option { + let property = style.get_property_value(name).ok()?; + property.trim_end_matches("px").parse::().ok() + }; + + rect.min.x += get_property("padding-left").unwrap_or_default(); + rect.min.y += get_property("padding-top").unwrap_or_default(); + rect.max.x -= get_property("padding-right").unwrap_or_default(); + rect.max.y -= get_property("padding-bottom").unwrap_or_default(); + } + } + + rect } fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement, ctx: &egui::Context) -> egui::Vec2 { diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index 68c150f1b..0cd25db13 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -115,8 +115,8 @@ impl TextAgent { let Some(ime) = ime else { return Ok(()) }; let ime_pos = ime.cursor_rect.left_top(); - let canvas_rect = canvas.get_bounding_client_rect(); - let new_pos = ime_pos + egui::vec2(canvas_rect.left() as f32, canvas_rect.top() as f32); + let canvas_rect = super::canvas_content_rect(canvas); + let new_pos = canvas_rect.min + ime_pos.to_vec2(); let style = self.input.style(); style.set_property("top", &format!("{}px", new_pos.y))?;