feat(iced): add transform handle hit-testing

This commit is contained in:
2026-07-15 03:09:03 +03:00
parent a8c1772807
commit 56bbcdf356
@@ -66,6 +66,8 @@ struct OverlayState {
cursor_pos: Option<Point>,
/// Whether the mouse is over the overlay.
is_hovered: bool,
/// Current handle being hovered (for cursor changes).
hovered_handle: TransformHandle,
}
impl OverlayProgram {
@@ -84,6 +86,69 @@ impl OverlayProgram {
(ox, oy, display_w, display_h)
}
/// Test which transform handle is at the given viewport position.
pub fn hit_test_handle(
&self,
viewport_x: f32,
viewport_y: f32,
) -> TransformHandle {
let Some(ref tr) = self.selection_transform else {
return TransformHandle::None;
};
if tr.is_empty() {
return TransformHandle::None;
}
let (origin_x, origin_y, _, _) = self.canvas_origin(iced::Size::new(0.0, 0.0));
// Convert viewport to canvas coordinates
let canvas_x = (viewport_x - origin_x) / self.zoom;
let canvas_y = (viewport_y - origin_y) / self.zoom;
let handle_size = TransformHandle::HANDLE_SIZE / self.zoom;
let half = handle_size / 2.0;
let x = tr.pos.x;
let y = tr.pos.y;
let w = tr.size.x;
let h = tr.size.y;
// Check rotation handle first (it's outside the box)
let rotate_y = y - TransformHandle::ROTATE_OFFSET / self.zoom;
let rotate_x = x + w / 2.0;
let rotate_radius = 4.0 / self.zoom;
let dx = canvas_x - rotate_x;
let dy = canvas_y - rotate_y;
if dx * dx + dy * dy <= rotate_radius * rotate_radius {
return TransformHandle::Rotate;
}
// Check corner and edge handles
let handles = [
(TransformHandle::TopLeft, x, y),
(TransformHandle::TopRight, x + w, y),
(TransformHandle::BottomLeft, x, y + h),
(TransformHandle::BottomRight, x + w, y + h),
(TransformHandle::Top, x + w / 2.0, y),
(TransformHandle::Bottom, x + w / 2.0, y + h),
(TransformHandle::Left, x, y + h / 2.0),
(TransformHandle::Right, x + w, y + h / 2.0),
];
for (handle, hx, hy) in &handles {
if (canvas_x - hx).abs() <= half && (canvas_y - hy).abs() <= half {
return *handle;
}
}
// Check if inside the selection box (for move)
if canvas_x >= x && canvas_x <= x + w && canvas_y >= y && canvas_y <= y + h {
return TransformHandle::Move;
}
TransformHandle::None
}
/// Draw transform handles for selection.
fn draw_transform_handles(
&self,
@@ -291,11 +356,19 @@ impl canvas::Program<Message> for OverlayProgram {
);
state.cursor_pos = Some(local_pos);
state.is_hovered = bounds.contains(position);
// Update hovered handle
if self.selection_transform.is_some() {
state.hovered_handle = self.hit_test_handle(position.x, position.y);
} else {
state.hovered_handle = TransformHandle::None;
}
}
_ => {
if state.is_hovered && !cursor.is_over(bounds) {
state.is_hovered = false;
state.cursor_pos = None;
state.hovered_handle = TransformHandle::None;
}
}
}