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:
@@ -7,10 +7,10 @@ pub(super) struct WebSurfaceSize {
|
||||
}
|
||||
|
||||
impl WebSurfaceSize {
|
||||
pub(super) fn from_bounds(bounds: Bounds<Pixels>) -> Option<Self> {
|
||||
pub(super) fn from_bounds(bounds: Bounds<Pixels>, scale_factor: f32) -> Option<Self> {
|
||||
Some(Self {
|
||||
width: viewport_dimension(bounds.size.width)?,
|
||||
height: viewport_dimension(bounds.size.height)?,
|
||||
width: viewport_dimension(bounds.size.width, scale_factor)?,
|
||||
height: viewport_dimension(bounds.size.height, scale_factor)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -25,10 +25,11 @@ impl WebSurfaceClickPoint {
|
||||
pub(super) fn from_window_position(
|
||||
bounds: Bounds<Pixels>,
|
||||
position: Point<Pixels>,
|
||||
scale_factor: f32,
|
||||
) -> Option<Self> {
|
||||
Some(Self {
|
||||
x: click_coordinate(position.x, bounds.origin.x, bounds.size.width)?,
|
||||
y: click_coordinate(position.y, bounds.origin.y, bounds.size.height)?,
|
||||
x: click_coordinate(position.x, bounds.origin.x, bounds.size.width, scale_factor)?,
|
||||
y: click_coordinate(position.y, bounds.origin.y, bounds.size.height, scale_factor)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -82,9 +83,9 @@ pub(super) struct WebSurfaceScrollDelta {
|
||||
}
|
||||
|
||||
impl WebSurfaceScrollDelta {
|
||||
pub(super) fn from_point(delta: Point<Pixels>) -> Option<Self> {
|
||||
let x = scroll_dimension(delta.x)?;
|
||||
let y = scroll_dimension(delta.y)?;
|
||||
pub(super) fn from_point(delta: Point<Pixels>, scale_factor: f32) -> Option<Self> {
|
||||
let x = scroll_dimension(delta.x, scale_factor)?;
|
||||
let y = scroll_dimension(delta.y, scale_factor)?;
|
||||
if x == 0 && y == 0 {
|
||||
return None;
|
||||
}
|
||||
@@ -105,8 +106,8 @@ impl WebSurfaceScrollDelta {
|
||||
}
|
||||
}
|
||||
|
||||
fn viewport_dimension(pixels: Pixels) -> Option<u32> {
|
||||
let value = f32::from(pixels.round());
|
||||
fn viewport_dimension(pixels: Pixels, scale_factor: f32) -> Option<u32> {
|
||||
let value = (f32::from(pixels) * positive_scale_or_one(scale_factor)).round();
|
||||
if !value.is_finite() || value < 1.0 || value > u32::MAX as f32 {
|
||||
return None;
|
||||
}
|
||||
@@ -114,8 +115,8 @@ fn viewport_dimension(pixels: Pixels) -> Option<u32> {
|
||||
Some(value as u32)
|
||||
}
|
||||
|
||||
fn scroll_dimension(pixels: Pixels) -> Option<i32> {
|
||||
let value = f32::from(pixels.round());
|
||||
fn scroll_dimension(pixels: Pixels, scale_factor: f32) -> Option<i32> {
|
||||
let value = (f32::from(pixels) * positive_scale_or_one(scale_factor)).round();
|
||||
if !value.is_finite() {
|
||||
return None;
|
||||
}
|
||||
@@ -129,14 +130,24 @@ fn scroll_dimension(pixels: Pixels) -> Option<i32> {
|
||||
Some(value as i32)
|
||||
}
|
||||
|
||||
fn click_coordinate(position: Pixels, origin: Pixels, size: Pixels) -> Option<u32> {
|
||||
fn click_coordinate(
|
||||
position: Pixels,
|
||||
origin: Pixels,
|
||||
size: Pixels,
|
||||
scale_factor: f32,
|
||||
) -> Option<u32> {
|
||||
let relative = f32::from(position) - f32::from(origin);
|
||||
let size = f32::from(size);
|
||||
if !relative.is_finite() || !size.is_finite() || relative < 0.0 || relative >= size {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(relative.floor() as u32)
|
||||
let scaled = (relative * positive_scale_or_one(scale_factor)).floor();
|
||||
if !scaled.is_finite() || scaled < 0.0 || scaled > u32::MAX as f32 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(scaled as u32)
|
||||
}
|
||||
|
||||
fn positive_scroll_component(current: i32, delta: i32) -> i32 {
|
||||
@@ -149,3 +160,12 @@ fn combined_scroll_delta(current: i32, next: i32) -> i32 {
|
||||
let value = i64::from(current) + i64::from(next);
|
||||
value.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
|
||||
}
|
||||
|
||||
/// Guard against a zero/negative/NaN scale factor reaching the
|
||||
/// arithmetic above. GPUI's `Window::scale_factor` returns the real
|
||||
/// device pixel ratio (1.0 on standard displays, 2.0 on Retina), so a
|
||||
/// non-positive value would mean the platform reported nonsense; we
|
||||
/// fall back to 1.0 rather than collapsing every coordinate to zero.
|
||||
fn positive_scale_or_one(scale_factor: f32) -> f32 {
|
||||
if scale_factor.is_finite() && scale_factor > 0.0 { scale_factor } else { 1.0 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user