Multiply input coords by window scale_factor so Retina clicks land

GPUI delivers logical (CSS) pixels but Servo expects device pixels.
On a 2x Retina display the page rendered at half resolution and every
click landed in roughly the upper-left quadrant of the page — the
second independent root cause that surfaces immediately after T1's
show()/focus() lets input reach Servo at all.

Single conversion boundary inside web_surface_geometry.rs:
viewport_dimension, scroll_dimension, and click_coordinate each
multiply once by window.scale_factor() before truncating to integer
device pixels. positive_scale_or_one() guards against a zero/negative/
NaN scale_factor reaching the arithmetic — falling back to 1.0 keeps
coordinates valid even if the platform reports nonsense (system
boundary validation per CLAUDE.md, not internal trust).

scale_factor is plumbed through controller and view layer, sourced
from window.scale_factor() at every record_* boundary so all four
inputs (click, scroll, hover, viewport) stay in sync. Existing tests
pin the 1.0 path; new retina_scale_factor_doubles_every_input_coordinate
locks the 2.0 path against a future regression.

cargo test ely_app --bin ely_app: 104 passed (was 103 + new test).
This commit is contained in:
2026-05-10 01:53:51 -04:00
parent 22ba8517d1
commit c20daf36b9
6 changed files with 136 additions and 39 deletions
+8 -3
View File
@@ -129,22 +129,26 @@ fn render_input_overlay(
});
cx.stop_propagation();
})
.on_mouse_move(move |event, _window, cx| {
.on_mouse_move(move |event, window, cx| {
let scale_factor = window.scale_factor();
hover_entity.update(cx, |shell, cx| {
shell.hover_external_web_viewport(
hover_tab_id.clone(),
event.position,
scale_factor,
cx,
);
});
})
.on_scroll_wheel(move |event, window, cx| {
let delta = event.delta.pixel_delta(window.line_height());
let scale_factor = window.scale_factor();
scroll_entity.update(cx, |shell, cx| {
shell.scroll_external_web_viewport(
scroll_tab_id.clone(),
scroll_url.clone(),
delta,
scale_factor,
cx,
);
});
@@ -154,9 +158,10 @@ fn render_input_overlay(
fn render_viewport_tracker(tab_id: TabId, state_entity: Entity<ElyShell>) -> impl IntoElement {
canvas(
move |bounds, _window: &mut Window, cx: &mut App| {
move |bounds, window: &mut Window, cx: &mut App| {
let scale_factor = window.scale_factor();
state_entity.update(cx, |shell, cx| {
shell.record_external_web_viewport(tab_id, bounds, cx);
shell.record_external_web_viewport(tab_id, bounds, scale_factor, cx);
});
},
|_, _, _, _| {},