Canvas · the wheel scrolls as scroll boxes do; pans hold at the press

A wheel without a modifier now slides the content by its delta, as
gpui's scroll boxes do; it moved the other way. The wheel reads the
live viewport and skips an end with no delta, and a drag pans from the
press by the pointer's whole way with the viewport held at the press,
so events that land in one frame no longer overwrite each other. Tests:
a plain wheel moves the content up with it; three wheels and an empty
end add up, and four moves pan their sum; a small frame fits at a zoom
of one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-27 07:15:01 -04:00
1 parent 2c099ceffb
commit ea99d30e3b
4 files changed
+73 -15

No files matched your search

+19 -15
View File
@@ -22,11 +22,11 @@ struct Pan {
owner: EntityId,
}
/// The canvas's box and the pointer of a pan under way.
/// The canvas's box, and the pointer and viewport at the press of a pan under way.
#[derive(Default)]
struct Plane {
bounds: Bounds<Pixels>,
grip: Option<Point<Pixels>>,
grip: Option<(Point<Pixels>, Viewport)>,
}
/// The view pixels of a canvas frame.
@@ -400,6 +400,7 @@ impl RenderOnce for InfiniteCanvas {
let owner = plane.entity_id();
let (wheel, pan, drop) = (set.clone(), set, plane.clone());
let (scrolled, dragged, pressed) = (plane.clone(), plane.clone(), plane);
let (live, held) = (local.clone(), local.clone());
div()
.id(id)
.relative()
@@ -416,34 +417,37 @@ impl RenderOnce for InfiniteCanvas {
(lines.x * line, lines.y * line)
}
};
if delta == (0.0, 0.0) {
return;
}
let pointer = event.position - bounds.origin;
let about = (f32::from(pointer.x), f32::from(pointer.y));
let now = view;
let now = live.read(cx).value;
let next = match event.modifiers.platform || event.modifiers.control {
true => now.zoomed((-delta.1 / 240.0).exp(), about),
false => now.panned((-delta.0, -delta.1)),
false => now.panned(delta),
};
cx.stop_propagation();
wheel(next, window, cx);
})
.on_mouse_down(gpui::MouseButton::Left, move |event, _, cx| {
pressed.update(cx, |plane, _| plane.grip = Some(event.position))
let now = held.read(cx).value;
pressed.update(cx, |plane, _| plane.grip = Some((event.position, now)))
})
.on_drag(Pan { owner }, |_, _, _, cx| cx.new(|_| EmptyView))
.on_drag_move::<Pan>(move |event, window, cx| {
if event.drag(cx).owner != owner {
return;
}
let pointer = event.event.position;
let last = dragged.update(cx, |plane, _| plane.grip.replace(pointer));
if let Some(last) = last {
let delta = pointer - last;
pan(
view.panned((f32::from(delta.x), f32::from(delta.y))),
window,
cx,
);
}
let Some((press, start)) = dragged.read(cx).grip else {
return;
};
let way = event.event.position - press;
pan(
start.panned((f32::from(way.x), f32::from(way.y))),
window,
cx,
);
})
.on_drop(move |_: &Pan, _, cx| drop.update(cx, |plane, _| plane.grip = None))
.child(painted)