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:
1 parent
2c099ceffb
commit
ea99d30e3b
4 files changed
+73
-15
No files matched your search
@@ -373,3 +373,4 @@ Per-component lines live in `tasks/`. Tags there:
|
|||||||
| T31c | 2 | PASS | |
|
| T31c | 2 | PASS | |
|
||||||
| T31d | 1 | FAIL | a focused segment past the strip's fold stayed hidden; the tracker lost focus as Start and Stop swapped; Stop and Skip went untested; percent did not refuse content |
|
| T31d | 1 | FAIL | a focused segment past the strip's fold stayed hidden; the tracker lost focus as Start and Stop swapped; Stop and Skip went untested; percent did not refuse content |
|
||||||
| T31d | 2 | PASS | |
|
| T31d | 2 | PASS | |
|
||||||
|
| T32a | 1 | FAIL | the plain wheel panned against every scroll box; the handlers panned from the render's viewport, so events within a frame overwrote each other; the fit's cap at one went untested |
|
||||||
+19
-15
@@ -22,11 +22,11 @@ struct Pan {
|
|||||||
owner: EntityId,
|
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)]
|
#[derive(Default)]
|
||||||
struct Plane {
|
struct Plane {
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
grip: Option<Point<Pixels>>,
|
grip: Option<(Point<Pixels>, Viewport)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The view pixels of a canvas frame.
|
/// The view pixels of a canvas frame.
|
||||||
@@ -400,6 +400,7 @@ impl RenderOnce for InfiniteCanvas {
|
|||||||
let owner = plane.entity_id();
|
let owner = plane.entity_id();
|
||||||
let (wheel, pan, drop) = (set.clone(), set, plane.clone());
|
let (wheel, pan, drop) = (set.clone(), set, plane.clone());
|
||||||
let (scrolled, dragged, pressed) = (plane.clone(), plane.clone(), plane);
|
let (scrolled, dragged, pressed) = (plane.clone(), plane.clone(), plane);
|
||||||
|
let (live, held) = (local.clone(), local.clone());
|
||||||
div()
|
div()
|
||||||
.id(id)
|
.id(id)
|
||||||
.relative()
|
.relative()
|
||||||
@@ -416,34 +417,37 @@ impl RenderOnce for InfiniteCanvas {
|
|||||||
(lines.x * line, lines.y * line)
|
(lines.x * line, lines.y * line)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if delta == (0.0, 0.0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let pointer = event.position - bounds.origin;
|
let pointer = event.position - bounds.origin;
|
||||||
let about = (f32::from(pointer.x), f32::from(pointer.y));
|
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 {
|
let next = match event.modifiers.platform || event.modifiers.control {
|
||||||
true => now.zoomed((-delta.1 / 240.0).exp(), about),
|
true => now.zoomed((-delta.1 / 240.0).exp(), about),
|
||||||
false => now.panned((-delta.0, -delta.1)),
|
false => now.panned(delta),
|
||||||
};
|
};
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
wheel(next, window, cx);
|
wheel(next, window, cx);
|
||||||
})
|
})
|
||||||
.on_mouse_down(gpui::MouseButton::Left, move |event, _, 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(Pan { owner }, |_, _, _, cx| cx.new(|_| EmptyView))
|
||||||
.on_drag_move::<Pan>(move |event, window, cx| {
|
.on_drag_move::<Pan>(move |event, window, cx| {
|
||||||
if event.drag(cx).owner != owner {
|
if event.drag(cx).owner != owner {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let pointer = event.event.position;
|
let Some((press, start)) = dragged.read(cx).grip else {
|
||||||
let last = dragged.update(cx, |plane, _| plane.grip.replace(pointer));
|
return;
|
||||||
if let Some(last) = last {
|
};
|
||||||
let delta = pointer - last;
|
let way = event.event.position - press;
|
||||||
pan(
|
pan(
|
||||||
view.panned((f32::from(delta.x), f32::from(delta.y))),
|
start.panned((f32::from(way.x), f32::from(way.y))),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.on_drop(move |_: &Pan, _, cx| drop.update(cx, |plane, _| plane.grip = None))
|
.on_drop(move |_: &Pan, _, cx| drop.update(cx, |plane, _| plane.grip = None))
|
||||||
.child(painted)
|
.child(painted)
|
||||||
|
|||||||
@@ -155,3 +155,53 @@ fn a_press_on_the_map_centers_the_view_there(cx: &mut TestAppContext) {
|
|||||||
"the view centers on the point pressed: {middle:?}"
|
"the view centers on the point pressed: {middle:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wheel(at: (f32, f32), dy: f32, modifiers: Modifiers) -> ScrollWheelEvent {
|
||||||
|
ScrollWheelEvent {
|
||||||
|
position: point(px(at.0), px(at.1)),
|
||||||
|
delta: ScrollDelta::Pixels(point(px(0.0), px(dy))),
|
||||||
|
modifiers,
|
||||||
|
touch_phase: TouchPhase::Moved,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_plain_wheel_moves_the_content_as_a_scroll_box_does(cx: &mut TestAppContext) {
|
||||||
|
let (host, cx) = stage(plane, cx);
|
||||||
|
cx.simulate_event(wheel((100.0, 50.0), -50.0, Modifiers::none()));
|
||||||
|
settle(cx);
|
||||||
|
let view = *heard(&host, cx).last().expect("a scroll");
|
||||||
|
assert_eq!(
|
||||||
|
view.to_view((0.0, 0.0)),
|
||||||
|
(0.0, -50.0),
|
||||||
|
"the content went up with the wheel"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn wheels_and_moves_add_up(cx: &mut TestAppContext) {
|
||||||
|
let (host, cx) = stage(plane, cx);
|
||||||
|
for dy in [-10.0, -10.0, -10.0, 0.0] {
|
||||||
|
cx.simulate_event(wheel((100.0, 50.0), dy, Modifiers::none()));
|
||||||
|
}
|
||||||
|
settle(cx);
|
||||||
|
let view = *heard(&host, cx).last().expect("a scroll");
|
||||||
|
assert_eq!(
|
||||||
|
view.y, 30.0,
|
||||||
|
"three wheels add up, and an end that moves nothing leaves it"
|
||||||
|
);
|
||||||
|
let at = |x: f32, y: f32| point(px(x), px(y));
|
||||||
|
cx.simulate_mouse_move(at(200.0, 150.0), None, Modifiers::none());
|
||||||
|
cx.simulate_mouse_down(at(200.0, 150.0), MouseButton::Left, Modifiers::none());
|
||||||
|
for x in [190.0, 180.0, 170.0, 160.0] {
|
||||||
|
cx.simulate_mouse_move(at(x, 150.0), Some(MouseButton::Left), Modifiers::none());
|
||||||
|
}
|
||||||
|
cx.simulate_mouse_up(at(160.0, 150.0), MouseButton::Left, Modifiers::none());
|
||||||
|
settle(cx);
|
||||||
|
let view = *heard(&host, cx).last().expect("a pan");
|
||||||
|
assert_eq!(
|
||||||
|
(view.x, view.y),
|
||||||
|
(40.0, 30.0),
|
||||||
|
"the drag pans from the press by the pointer's whole way"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -159,6 +159,9 @@ mod tests {
|
|||||||
let view = Viewport::fitting(Frame::new(0.0, 0.0, 800.0, 400.0), (440.0, 440.0), 20.0);
|
let view = Viewport::fitting(Frame::new(0.0, 0.0, 800.0, 400.0), (440.0, 440.0), 20.0);
|
||||||
assert_eq!(view.zoom, 0.5);
|
assert_eq!(view.zoom, 0.5);
|
||||||
assert_eq!(view.to_view((400.0, 200.0)), (220.0, 220.0));
|
assert_eq!(view.to_view((400.0, 200.0)), (220.0, 220.0));
|
||||||
|
let small = Viewport::fitting(Frame::new(0.0, 0.0, 100.0, 50.0), (440.0, 440.0), 20.0);
|
||||||
|
assert_eq!(small.zoom, 1.0, "no nearer than a zoom of one");
|
||||||
|
assert_eq!(small.to_view((50.0, 25.0)), (220.0, 220.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user