app: stop slider drags from leaking into other sliders
CI / Build & test (Linux) (push) Successful in 18m42s
CI / Build & test (Windows) (push) Successful in 44m58s

gpui delivers DragMoveEvent to every drop target with a matching
payload type under the cursor, and every slider shared the SliderDrag
payload type — dragging one slider (e.g. an OFX panel parameter) also
moved every other slider the cursor passed over (timeline zoom, track
height). Tag the payload with the starting control and ignore drag
moves that are not ours.

Also finish the gesture on mouse-up (inside or outside the track)
instead of on_drop: gpui only delivers drop events to the hovered
target, so releasing outside the track used to leave the drag
unfinished and the final value/undo edit unemitted.
This commit is contained in:
2026-08-25 19:10:25 +08:00
parent a56f6ddf87
commit 3bf914a2b8
+36 -7
View File
@@ -82,6 +82,12 @@ const HORIZONTAL_DRAG_RANGE_PX: f32 = 240.0;
/// by 1/10 step, Home/End jump to the range ends.
/// The in-flight drag gesture payload shared with the drag events.
struct SliderDrag {
/// The control that STARTED the drag. gpui delivers drag-move events
/// to every drop target with a matching payload type under the
/// cursor — without this gate, dragging one slider also moved every
/// other slider the cursor passed over (the OFX-panel drag that
/// moved the timeline zoom/track-height sliders).
control: usize,
/// The cursor x of the first drag-move (the drag origin).
first_x: f32,
/// The model fraction when the drag started.
@@ -161,6 +167,16 @@ impl Slider {
cx.notify();
}
/// Complete an in-flight drag (one undoable edit per gesture): emits
/// the final value and the DragFinished notice. No-op without a drag.
fn finish_drag(&mut self, cx: &mut Context<Self>) {
if self.dragging {
self.dragging = false;
self.emit_changed(cx);
cx.emit(SliderEvent::DragFinished { control: self.control });
}
}
/// Open the numeric editor with the current value as its text.
fn begin_edit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let text = self.display();
@@ -227,6 +243,7 @@ impl Render for Slider {
let control = self.control;
let handle_x = (fraction * 100.0).clamp(0.0, 100.0) as f32;
let drag_payload = std::sync::Arc::new(std::sync::RwLock::new(SliderDrag {
control,
first_x: 0.0,
origin_fraction: 0.0,
first: true,
@@ -248,6 +265,10 @@ impl Render for Slider {
cx.listener(|this, event: &gpui::DragMoveEvent<std::sync::Arc<std::sync::RwLock<SliderDrag>>>, _window, cx| {
{
let mut drag = event.drag(cx).write().unwrap();
if drag.control != this.control {
// Another slider's drag passing over us: not ours.
return;
}
if drag.first {
drag.first = false;
drag.first_x = f32::from(event.event.position.x);
@@ -275,13 +296,21 @@ impl Render for Slider {
cx.notify();
}),
)
.on_drop(
cx.listener(|this, _drag: &std::sync::Arc<std::sync::RwLock<SliderDrag>>, _window, cx| {
if this.dragging {
this.dragging = false;
this.emit_changed(cx);
cx.emit(SliderEvent::DragFinished { control: this.control });
}
// Finish the gesture on mouse up — wherever it lands (gpui only
// delivers drop events to the hovered target, so finishing via
// `on_drop` lost the gesture when the cursor ended outside the
// track). Both the inside and the outside variants gate on
// `dragging`, so only a gesture this slider started completes.
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _event: &MouseUpEvent, _window, cx| {
this.finish_drag(cx);
}),
)
.on_mouse_up_out(
MouseButton::Left,
cx.listener(|this, _event: &MouseUpEvent, _window, cx| {
this.finish_drag(cx);
}),
)
.on_click(cx.listener(|this, event: &gpui::ClickEvent, window, cx| {