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