Dispatch source hit testing for native pointer clicks
Verify Tessera GUI / verify (push) Failing after 7m31s

This commit is contained in:
2026-10-21 15:37:26 +08:00
parent d19fab55b9
commit 7b11e32eb6
14 changed files with 560 additions and 30 deletions
+82 -5
View File
@@ -94,6 +94,13 @@ impl NativeCallable for Method {
}
let geometry = mount.geometry.clone();
let input = object(input)?;
if matches!(self.query, Query::HitTest) {
let x = number(cx, input, "x", true)?;
let y = number(cx, input, "y", true)?;
return Ok(NativeResult::Return(Value::Bool(
x >= 0.0 && y >= 0.0 && x < geometry.width && y < geometry.height,
)));
}
let node = block::snapshot(cx.store(), owner)?;
let result = match self.query {
Query::Measure => {
@@ -208,16 +215,68 @@ impl NativeCallable for Method {
vec![("commands", Value::Object(commands))],
)?)
}
Query::HitTest => {
let x = number(cx, input, "x", true)?;
let y = number(cx, input, "y", true)?;
Value::Bool(x >= 0.0 && y >= 0.0 && x < geometry.width && y < geometry.height)
}
Query::HitTest => unreachable!("handled above"),
};
Ok(NativeResult::Return(result))
}
}
#[derive(Debug)]
struct PointMethod {
expected: ObjectRef,
circle: bool,
}
impl NativeCallable for PointMethod {
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
let Some(Value::Object(point)) = args.first() else {
return Err(invalid("Point method requires a Point"));
};
if !cx.store().is_instance(*point, self.expected)? {
return Err(invalid("Point method requires a Point"));
}
let x = number(cx, *point, "x", true)?;
let y = number(cx, *point, "y", true)?;
let inside = if self.circle {
let [
_,
Value::Number(cx_value),
Value::Number(cy_value),
Value::Number(radius),
] = args
else {
return Err(invalid("inCircle requires center x/y and radius"));
};
let parse = |value: &endor_core::EndorNumber| -> Result<f64, VmError> {
let value = value
.to_string()
.parse::<f64>()
.map_err(|_| invalid("invalid circle number"))?;
if !value.is_finite() || value.abs() > MAX_DIMENSION {
return Err(invalid("circle number exceeds bounds"));
}
Ok(value)
};
let dx = x as f64 - parse(cx_value)?;
let dy = y as f64 - parse(cy_value)?;
let radius = parse(radius)?;
if radius < 0.0 {
return Err(invalid("circle radius must be nonnegative"));
}
dx * dx + dy * dy <= radius * radius
} else {
let [_, Value::Object(bounds)] = args else {
return Err(invalid("inside requires Bounds"));
};
let left = number(cx, *bounds, "x", true)?;
let top = number(cx, *bounds, "y", true)?;
let width = number(cx, *bounds, "width", false)?;
let height = number(cx, *bounds, "height", false)?;
x >= left && y >= top && x < left + width && y < top + height
};
Ok(NativeResult::Return(Value::Bool(inside)))
}
}
pub fn install(
vm: &mut Vm,
roots: &mut Vec<ObjectRef>,
@@ -240,5 +299,23 @@ pub fn install(
vm.attach_type(function, function_type)?;
vm.define_method(block_type, name, function)?;
}
let registry = object(&vm.get_attr(desktop, "types")?)?;
let point_type = object(
&vm.dict_get(registry, &Value::Text("Point".into()))?
.ok_or(invalid("missing Point type"))?,
)?;
for (name, circle) in [("inside", false), ("inCircle", true)] {
let function = vm.allocate(ContextNativeFunction::new(
name,
PointMethod {
expected: point_type,
circle,
},
vec![point_type],
))?;
roots.push(function);
vm.attach_type(function, function_type)?;
vm.define_method(point_type, name, function)?;
}
Ok(())
}