Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions internal/core/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,10 @@ pub struct MouseInputState {
/// When this is Some, it means we are in the middle of a drag-drop operation and it contains the dragged data.
/// The `position` field has no signification
pub(crate) drag_data: Option<DropEvent>,
/// The DropArea that accepted the most recent DragMove, if any. On release we use
/// this to decide whether to deliver a Drop — matching OS DnD pipelines, where a
/// target that didn't previously accept never receives a drop.
pub(crate) drop_target: Option<ItemWeak>,
delayed: Option<(crate::timers::Timer, MouseEvent)>,
delayed_exit_items: Vec<ItemWeak>,
pub(crate) cursor: MouseCursor,
Expand Down Expand Up @@ -1169,6 +1173,7 @@ pub fn process_mouse_input(
) -> MouseInputState {
let mut result = MouseInputState {
drag_data: mouse_input_state.drag_data.clone(),
drop_target: mouse_input_state.drop_target.clone(),
cursor: mouse_input_state.cursor,
..Default::default()
};
Expand All @@ -1180,6 +1185,12 @@ pub fn process_mouse_input(
mouse_input_state.top_item().as_ref(),
false,
);
if matches!(mouse_event, MouseEvent::DragMove(_)) {
// Remember the accepting DropArea (or forget if none did) so the subsequent
// Release knows whether to deliver a Drop.
result.drop_target =
r.has_aborted().then(|| result.item_stack.last().map(|(w, _)| w.clone())).flatten();
}
if mouse_input_state.delayed.is_some()
&& (!r.has_aborted()
|| Option::zip(result.item_stack.last(), mouse_input_state.item_stack.last())
Expand Down
14 changes: 12 additions & 2 deletions internal/core/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,18 @@ impl WindowInner {
if let Some(mut drop_event) = mouse_input_state.drag_data.clone() {
match &event {
MouseEvent::Released { position, button: PointerEventButton::Left, .. } => {
drop_event.position = crate::lengths::logical_position_to_api(*position);
event = MouseEvent::Drop(drop_event);
mouse_input_state.drag_data = None;
if mouse_input_state.drop_target.take().is_some() {
drop_event.position = crate::lengths::logical_position_to_api(*position);
event = MouseEvent::Drop(drop_event);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that you can still end up deliverying the event to a different item if the position changed from one item to another.

} else {
// No DropArea accepted the most recent DragMove. Tear the drag
// down via Exit instead of converting to Drop so a non-accepting
// DropArea under the cursor doesn't fire `dropped`, and so the
// underlying Release doesn't reach hit-tested items as a
// spurious click.
event = MouseEvent::Exit;
}
}
MouseEvent::Moved { position, .. } => {
drop_event.position = crate::lengths::logical_position_to_api(*position);
Expand All @@ -638,6 +647,7 @@ impl WindowInner {
}
MouseEvent::Exit => {
mouse_input_state.drag_data = None;
mouse_input_state.drop_target = None;
}
_ => {}
}
Expand Down
19 changes: 19 additions & 0 deletions tests/cases/elements/dragarea_droparea.slint
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,25 @@ assert_eq!(instance.get_result(), "");
assert_eq!(instance.get_contains_drag(), false);
assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor()), MouseCursor::Default);

// When `can-drop` returns false, releasing over the DropArea must not invoke `dropped`.
instance.set_use_image(false);
instance.set_result("".into());
instance.on_can_drop(|_, _| false);

instance.window().dispatch_event(WindowEvent::PointerPressed { position: LogicalPosition::new(20.0, 25.0), button: PointerEventButton::Left });
slint_testing::mock_elapsed_time(20);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(21.0, 40.0) });
slint_testing::mock_elapsed_time(20);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(22.0, 120.0) });
slint_testing::mock_elapsed_time(20);
assert_eq!(instance.get_contains_drag(), false);
assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor()), MouseCursor::NoDrop);

instance.window().dispatch_event(WindowEvent::PointerReleased { position: LogicalPosition::new(22.0, 120.0), button: PointerEventButton::Left });
slint_testing::mock_elapsed_time(20);
assert_eq!(instance.get_result(), "");
assert_eq!(instance.get_contains_drag(), false);

```

*/
Loading