1use super::{
5 BuiltInMouseCursor, DragAction, DragActionArg, DropEvent, Item, ItemConsts, ItemRc,
6 PointerEventButton, RenderingResult,
7};
8use crate::Coord;
9use crate::cursor::MouseCursorInner;
10use crate::data_transfer::DataTransfer;
11use crate::graphics::Image;
12use crate::input::{
13 FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, InternalKeyEvent,
14 KeyEventResult, KeyboardModifiers, MouseEvent,
15};
16use crate::item_rendering::{CachedRenderingData, ItemRenderer};
17use crate::layout::{LayoutInfo, Orientation};
18use crate::lengths::{LogicalPoint, LogicalRect, LogicalSize};
19#[cfg(feature = "rtti")]
20use crate::rtti::*;
21use crate::window::WindowAdapter;
22use crate::{Callback, Property};
23use alloc::rc::Rc;
24use const_field_offset::FieldOffsets;
25use core::cell::Cell;
26use core::pin::Pin;
27use i_slint_core_macros::*;
28
29pub type DropEventArg = (DropEvent,);
30
31#[repr(C)]
33#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
34pub struct AllowedDragActions {
35 pub copy: bool,
36 pub move_: bool,
37 pub link: bool,
38}
39
40impl AllowedDragActions {
41 pub fn any(self) -> bool {
43 self.copy || self.move_ || self.link
44 }
45}
46
47pub(super) fn press_drag_filter(
52 pressed: &Cell<bool>,
53 pressed_position: &Cell<LogicalPoint>,
54 event: &MouseEvent,
55) -> InputEventFilterResult {
56 match event {
57 MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
58 pressed_position.set(*position);
59 pressed.set(true);
60 InputEventFilterResult::ForwardAndInterceptGrab
61 }
62 MouseEvent::Exit => {
63 pressed.set(false);
64 InputEventFilterResult::ForwardAndIgnore
65 }
66 MouseEvent::Released { button: PointerEventButton::Left, .. } => {
67 pressed.set(false);
68 InputEventFilterResult::ForwardAndIgnore
69 }
70 MouseEvent::Moved { position, .. } => {
71 if !pressed.get() {
72 InputEventFilterResult::ForwardEvent
73 } else if exceeds_drag_threshold(pressed_position.get(), *position) {
74 InputEventFilterResult::Intercept
75 } else {
76 InputEventFilterResult::ForwardAndInterceptGrab
77 }
78 }
79 MouseEvent::Wheel { .. } => InputEventFilterResult::ForwardAndIgnore,
80 MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
82 InputEventFilterResult::ForwardAndIgnore
83 }
84 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
85 InputEventFilterResult::ForwardAndIgnore
86 }
87 MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => {
88 InputEventFilterResult::ForwardAndIgnore
89 }
90 }
91}
92
93pub(super) fn exceeds_drag_threshold(
96 pressed_position: LogicalPoint,
97 position: LogicalPoint,
98) -> bool {
99 let dx = (position.x - pressed_position.x).abs();
100 let dy = (position.y - pressed_position.y).abs();
101 let threshold = super::flickable::DISTANCE_THRESHOLD.get();
102 dx > threshold || dy > threshold
103}
104
105#[repr(C)]
106#[derive(FieldOffsets, Default, SlintElement)]
107#[pin]
108pub struct DragArea {
110 pub enabled: Property<bool>,
111 pub data: Property<DataTransfer>,
112 pub drag_image: Property<Image>,
113 pub drag_image_offset_x: Property<i32>,
114 pub drag_image_offset_y: Property<i32>,
115 pub allow_copy: Property<bool>,
116 pub allow_move: Property<bool>,
117 pub allow_link: Property<bool>,
118 pub dragging: Property<bool>,
119 pub drag_finished: Callback<DragActionArg, ()>,
120 pressed: Cell<bool>,
121 pressed_position: Cell<LogicalPoint>,
122 pub cached_rendering_data: CachedRenderingData,
123}
124
125impl Item for DragArea {
126 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
127
128 fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
129
130 fn layout_info(
131 self: Pin<&Self>,
132 _: Orientation,
133 _cross_axis_constraint: Coord,
134 _window_adapter: &Rc<dyn WindowAdapter>,
135 _self_rc: &ItemRc,
136 ) -> LayoutInfo {
137 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
138 }
139
140 fn input_event_filter_before_children(
141 self: Pin<&Self>,
142 event: &MouseEvent,
143 _window_adapter: &Rc<dyn WindowAdapter>,
144 _self_rc: &ItemRc,
145 _: &mut MouseCursorInner,
146 ) -> InputEventFilterResult {
147 if !self.enabled() || !self.allowed_actions().any() || self.data().is_empty() {
148 self.cancel();
149 return InputEventFilterResult::ForwardAndIgnore;
150 }
151 press_drag_filter(&self.pressed, &self.pressed_position, event)
152 }
153
154 fn input_event(
155 self: Pin<&Self>,
156 event: &MouseEvent,
157 _window_adapter: &Rc<dyn WindowAdapter>,
158 _self_rc: &ItemRc,
159 _: &mut MouseCursorInner,
160 ) -> InputEventResult {
161 match event {
162 MouseEvent::Pressed { .. } => InputEventResult::EventAccepted,
163 MouseEvent::Exit => {
164 self.cancel();
165 InputEventResult::EventIgnored
166 }
167 MouseEvent::Released { .. } => {
168 self.cancel();
169 InputEventResult::EventIgnored
170 }
171 MouseEvent::Moved { position, .. } => {
172 if !self.pressed.get()
173 || !self.enabled()
174 || !self.allowed_actions().any()
175 || self.data().is_empty()
176 {
177 return InputEventResult::EventIgnored;
178 }
179 let start_drag = exceeds_drag_threshold(self.pressed_position.get(), *position);
180 if start_drag {
181 self.pressed.set(false);
182 InputEventResult::StartDrag
183 } else {
184 InputEventResult::EventAccepted
185 }
186 }
187 MouseEvent::Wheel { .. } => InputEventResult::EventIgnored,
188 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
189 InputEventResult::EventIgnored
190 }
191 MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => InputEventResult::EventIgnored,
192 }
193 }
194
195 fn capture_key_event(
196 self: Pin<&Self>,
197 _: &InternalKeyEvent,
198 _window_adapter: &Rc<dyn WindowAdapter>,
199 _self_rc: &ItemRc,
200 ) -> KeyEventResult {
201 KeyEventResult::EventIgnored
202 }
203
204 fn key_event(
205 self: Pin<&Self>,
206 _: &InternalKeyEvent,
207 _window_adapter: &Rc<dyn WindowAdapter>,
208 _self_rc: &ItemRc,
209 ) -> KeyEventResult {
210 KeyEventResult::EventIgnored
211 }
212
213 fn focus_event(
214 self: Pin<&Self>,
215 _: &FocusEvent,
216 _window_adapter: &Rc<dyn WindowAdapter>,
217 _self_rc: &ItemRc,
218 ) -> FocusEventResult {
219 FocusEventResult::FocusIgnored
220 }
221
222 fn render(
223 self: Pin<&Self>,
224 _: &mut &mut dyn ItemRenderer,
225 _self_rc: &ItemRc,
226 _size: LogicalSize,
227 ) -> RenderingResult {
228 RenderingResult::ContinueRenderingChildren
229 }
230
231 fn bounding_rect(
232 self: core::pin::Pin<&Self>,
233 _window_adapter: &Rc<dyn WindowAdapter>,
234 _self_rc: &ItemRc,
235 mut geometry: LogicalRect,
236 ) -> LogicalRect {
237 geometry.size = LogicalSize::zero();
238 geometry
239 }
240
241 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
242 false
243 }
244}
245
246impl ItemConsts for DragArea {
247 const cached_rendering_data_offset: const_field_offset::FieldOffset<
248 DragArea,
249 CachedRenderingData,
250 > = DragArea::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
251}
252
253impl DragArea {
254 fn cancel(self: Pin<&Self>) {
255 self.pressed.set(false)
256 }
257
258 pub(crate) fn allowed_actions(self: Pin<&Self>) -> AllowedDragActions {
259 AllowedDragActions {
260 copy: self.allow_copy(),
261 move_: self.allow_move(),
262 link: self.allow_link(),
263 }
264 }
265
266 pub(crate) fn initial_drop_event(self: Pin<&Self>) -> (DropEvent, AllowedDragActions) {
270 let allowed = self.allowed_actions();
271 let event = DropEvent {
272 data: self.data(),
273 position: Default::default(),
274 proposed_action: compute_proposed_action(KeyboardModifiers::default(), allowed),
275 };
276 (event, allowed)
277 }
278
279 pub(crate) fn finish_drag(self: Pin<&Self>, action: DragAction) {
282 self.dragging.set(false);
283 Self::FIELD_OFFSETS.drag_finished().apply_pin(self).call(&(action,));
284 }
285}
286
287#[repr(C)]
288#[derive(FieldOffsets, Default, SlintElement)]
289#[pin]
290pub struct DropArea {
292 pub enabled: Property<bool>,
293 pub has_drag: Property<bool>,
294 pub current_action: Property<DragAction>,
295 pub can_drop: Callback<DropEventArg, DragAction>,
296 pub dropped: Callback<DropEventArg, DragAction>,
297
298 pub cached_rendering_data: CachedRenderingData,
299}
300
301impl Item for DropArea {
302 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
303
304 fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
305
306 fn layout_info(
307 self: Pin<&Self>,
308 _: Orientation,
309 _cross_axis_constraint: Coord,
310 _window_adapter: &Rc<dyn WindowAdapter>,
311 _self_rc: &ItemRc,
312 ) -> LayoutInfo {
313 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
314 }
315
316 fn input_event_filter_before_children(
317 self: Pin<&Self>,
318 _: &MouseEvent,
319 _window_adapter: &Rc<dyn WindowAdapter>,
320 _self_rc: &ItemRc,
321 _: &mut MouseCursorInner,
322 ) -> InputEventFilterResult {
323 InputEventFilterResult::ForwardEvent
324 }
325
326 fn input_event(
327 self: Pin<&Self>,
328 event: &MouseEvent,
329 _: &Rc<dyn WindowAdapter>,
330 _self_rc: &ItemRc,
331 cursor: &mut MouseCursorInner,
332 ) -> InputEventResult {
333 if !self.enabled() {
334 return InputEventResult::EventIgnored;
335 }
336 match event {
337 MouseEvent::DragMove { event, allowed } => {
338 let raw = Self::FIELD_OFFSETS.can_drop().apply_pin(self).call(&(event.clone(),));
339 let chosen = clamp_action_to_allowed(raw, *allowed);
340 self.current_action.set(chosen);
341 if chosen != DragAction::None {
342 self.has_drag.set(true);
343 *cursor = MouseCursorInner::BuiltIn(cursor_for_action(chosen));
344 InputEventResult::EventAccepted
345 } else {
346 self.has_drag.set(false);
347 InputEventResult::EventIgnored
348 }
349 }
350 MouseEvent::Drop { event, allowed } => {
351 self.has_drag.set(false);
352 let returned =
353 Self::FIELD_OFFSETS.dropped().apply_pin(self).call(&(event.clone(),));
354 self.current_action.set(clamp_action_to_allowed(returned, *allowed));
358 InputEventResult::EventAccepted
359 }
360 MouseEvent::Exit => {
361 self.has_drag.set(false);
362 self.current_action.set(DragAction::None);
363 InputEventResult::EventIgnored
364 }
365 _ => InputEventResult::EventIgnored,
366 }
367 }
368
369 fn capture_key_event(
370 self: Pin<&Self>,
371 _: &InternalKeyEvent,
372 _window_adapter: &Rc<dyn WindowAdapter>,
373 _self_rc: &ItemRc,
374 ) -> KeyEventResult {
375 KeyEventResult::EventIgnored
376 }
377
378 fn key_event(
379 self: Pin<&Self>,
380 _: &InternalKeyEvent,
381 _window_adapter: &Rc<dyn WindowAdapter>,
382 _self_rc: &ItemRc,
383 ) -> KeyEventResult {
384 KeyEventResult::EventIgnored
385 }
386
387 fn focus_event(
388 self: Pin<&Self>,
389 _: &FocusEvent,
390 _window_adapter: &Rc<dyn WindowAdapter>,
391 _self_rc: &ItemRc,
392 ) -> FocusEventResult {
393 FocusEventResult::FocusIgnored
394 }
395
396 fn render(
397 self: Pin<&Self>,
398 _: &mut &mut dyn ItemRenderer,
399 _self_rc: &ItemRc,
400 _size: LogicalSize,
401 ) -> RenderingResult {
402 RenderingResult::ContinueRenderingChildren
403 }
404
405 fn bounding_rect(
406 self: core::pin::Pin<&Self>,
407 _window_adapter: &Rc<dyn WindowAdapter>,
408 _self_rc: &ItemRc,
409 mut geometry: LogicalRect,
410 ) -> LogicalRect {
411 geometry.size = LogicalSize::zero();
412 geometry
413 }
414
415 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
416 false
417 }
418}
419
420impl ItemConsts for DropArea {
421 const cached_rendering_data_offset: const_field_offset::FieldOffset<
422 DropArea,
423 CachedRenderingData,
424 > = DropArea::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
425}
426
427pub fn compute_proposed_action(
431 modifiers: KeyboardModifiers,
432 allowed_actions: AllowedDragActions,
433) -> DragAction {
434 let allowed = |a| match a {
435 DragAction::Copy => allowed_actions.copy,
436 DragAction::Move => allowed_actions.move_,
437 DragAction::Link => allowed_actions.link,
438 DragAction::None => false,
439 };
440 let modifier_request = match (modifiers.control, modifiers.shift) {
441 (true, true) => Some(DragAction::Link),
442 (true, false) => Some(DragAction::Copy),
443 (false, true) => Some(DragAction::Move),
444 (false, false) => None,
445 };
446 if let Some(req) = modifier_request
447 && allowed(req)
448 {
449 return req;
450 }
451 for fallback in [DragAction::Move, DragAction::Copy, DragAction::Link] {
452 if allowed(fallback) {
453 return fallback;
454 }
455 }
456 DragAction::None
457}
458
459pub(crate) fn clamp_action_to_allowed(
462 action: DragAction,
463 allowed: AllowedDragActions,
464) -> DragAction {
465 match action {
466 DragAction::None => DragAction::None,
467 DragAction::Copy if allowed.copy => DragAction::Copy,
468 DragAction::Move if allowed.move_ => DragAction::Move,
469 DragAction::Link if allowed.link => DragAction::Link,
470 _ => DragAction::None,
471 }
472}
473
474pub(crate) fn cursor_for_action(action: DragAction) -> BuiltInMouseCursor {
476 match action {
477 DragAction::Move => BuiltInMouseCursor::Default,
478 DragAction::Copy => BuiltInMouseCursor::Copy,
479 DragAction::Link => BuiltInMouseCursor::Alias,
480 DragAction::None => BuiltInMouseCursor::NoDrop,
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 fn modifiers(control: bool, shift: bool) -> KeyboardModifiers {
489 KeyboardModifiers { control, shift, alt: false, meta: false }
490 }
491
492 const ALL: AllowedDragActions = AllowedDragActions { copy: true, move_: true, link: true };
493 const COPY_ONLY: AllowedDragActions =
494 AllowedDragActions { copy: true, move_: false, link: false };
495 const MOVE_ONLY: AllowedDragActions =
496 AllowedDragActions { copy: false, move_: true, link: false };
497 const LINK_ONLY: AllowedDragActions =
498 AllowedDragActions { copy: false, move_: false, link: true };
499 const COPY_AND_MOVE: AllowedDragActions =
500 AllowedDragActions { copy: true, move_: true, link: false };
501
502 #[test]
503 fn compute_proposed_action_modifier_table() {
504 let a = |m| compute_proposed_action(m, ALL);
506 assert_eq!(a(modifiers(false, false)), DragAction::Move);
507 assert_eq!(a(modifiers(true, false)), DragAction::Copy);
508 assert_eq!(a(modifiers(false, true)), DragAction::Move);
509 assert_eq!(a(modifiers(true, true)), DragAction::Link);
510 }
511
512 #[test]
513 fn compute_proposed_action_falls_back_when_modifier_action_not_allowed() {
514 assert_eq!(compute_proposed_action(modifiers(true, false), MOVE_ONLY), DragAction::Move);
516 assert_eq!(compute_proposed_action(modifiers(true, true), COPY_ONLY), DragAction::Copy);
518 }
519
520 #[test]
521 fn compute_proposed_action_default_is_first_allowed() {
522 assert_eq!(
524 compute_proposed_action(modifiers(false, false), COPY_AND_MOVE),
525 DragAction::Move
526 );
527 assert_eq!(compute_proposed_action(modifiers(false, false), COPY_ONLY), DragAction::Copy);
528 assert_eq!(compute_proposed_action(modifiers(false, false), LINK_ONLY), DragAction::Link);
529 assert_eq!(
531 compute_proposed_action(modifiers(false, false), AllowedDragActions::default()),
532 DragAction::None
533 );
534 }
535}