1use std::{
2 borrow::Cow,
3 sync::atomic::{AtomicU64, Ordering},
4};
5
6use iced::Vector;
7
8use crate::{
9 Element,
10 iced::{
11 Event, Length, Rectangle,
12 clipboard::{
13 dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
14 mime::AllowedMimeTypes,
15 },
16 event,
17 id::Internal,
18 mouse, overlay,
19 },
20 iced_core::{
21 self, Clipboard, Shell, layout,
22 widget::{Tree, tree},
23 },
24 widget::{Id, Widget},
25};
26
27pub fn dnd_destination<'a, Message: 'static>(
28 child: impl Into<Element<'a, Message>>,
29 mimes: Vec<Cow<'static, str>>,
30) -> DndDestination<'a, Message> {
31 DndDestination::new(child, mimes)
32}
33
34pub fn dnd_destination_for_data<'a, T: AllowedMimeTypes, Message: 'static>(
35 child: impl Into<Element<'a, Message>>,
36 on_finish: impl Fn(Option<T>, DndAction) -> Message + 'static,
37) -> DndDestination<'a, Message> {
38 DndDestination::for_data(child, on_finish)
39}
40
41static DRAG_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub struct DragId(pub u128);
45
46impl DragId {
47 pub fn new() -> Self {
48 DragId(u128::from(u64::MAX) + u128::from(DRAG_ID_COUNTER.fetch_add(1, Ordering::Relaxed)))
49 }
50}
51
52#[allow(clippy::new_without_default)]
53impl Default for DragId {
54 fn default() -> Self {
55 DragId::new()
56 }
57}
58
59pub struct DndDestination<'a, Message> {
60 id: Id,
61 drag_id: Option<u64>,
62 preferred_action: DndAction,
63 action: DndAction,
64 container: Element<'a, Message>,
65 mime_types: Vec<Cow<'static, str>>,
66 forward_drag_as_cursor: bool,
67 on_hold: Option<Box<dyn Fn(f64, f64) -> Message>>,
68 on_drop: Option<Box<dyn Fn(f64, f64) -> Message>>,
69 on_enter: Option<Box<dyn Fn(f64, f64, Vec<String>) -> Message>>,
70 on_leave: Option<Box<dyn Fn() -> Message>>,
71 on_motion: Option<Box<dyn Fn(f64, f64) -> Message>>,
72 on_action_selected: Option<Box<dyn Fn(DndAction) -> Message>>,
73 on_data_received: Option<Box<dyn Fn(String, Vec<u8>) -> Message>>,
74 on_finish: Option<Box<dyn Fn(String, Vec<u8>, DndAction, f64, f64) -> Message>>,
75}
76
77impl<'a, Message: 'static> DndDestination<'a, Message> {
78 pub fn new(child: impl Into<Element<'a, Message>>, mimes: Vec<Cow<'static, str>>) -> Self {
79 Self {
80 id: Id::unique(),
81 drag_id: None,
82 mime_types: mimes,
83 preferred_action: DndAction::Move,
84 action: DndAction::Copy | DndAction::Move,
85 container: child.into(),
86 forward_drag_as_cursor: false,
87 on_hold: None,
88 on_drop: None,
89 on_enter: None,
90 on_leave: None,
91 on_motion: None,
92 on_action_selected: None,
93 on_data_received: None,
94 on_finish: None,
95 }
96 }
97
98 pub fn for_data<T: AllowedMimeTypes>(
99 child: impl Into<Element<'a, Message>>,
100 on_finish: impl Fn(Option<T>, DndAction) -> Message + 'static,
101 ) -> Self {
102 Self {
103 id: Id::unique(),
104 drag_id: None,
105 mime_types: T::allowed().iter().cloned().map(Cow::Owned).collect(),
106 preferred_action: DndAction::Move,
107 action: DndAction::Copy | DndAction::Move,
108 container: child.into(),
109 forward_drag_as_cursor: false,
110 on_hold: None,
111 on_drop: None,
112 on_enter: None,
113 on_leave: None,
114 on_motion: None,
115 on_action_selected: None,
116 on_data_received: None,
117 on_finish: Some(Box::new(move |mime, data, action, _, _| {
118 on_finish(T::try_from((data, mime)).ok(), action)
119 })),
120 }
121 }
122
123 #[must_use]
124 pub fn data_received_for<T: AllowedMimeTypes>(
125 mut self,
126 f: impl Fn(Option<T>) -> Message + 'static,
127 ) -> Self {
128 self.on_data_received = Some(Box::new(
129 move |mime, data| f(T::try_from((data, mime)).ok()),
130 ));
131 self
132 }
133
134 pub fn with_id(
135 child: impl Into<Element<'a, Message>>,
136 id: Id,
137 mimes: Vec<Cow<'static, str>>,
138 ) -> Self {
139 Self {
140 id,
141 drag_id: None,
142 mime_types: mimes,
143 preferred_action: DndAction::Move,
144 action: DndAction::Copy | DndAction::Move,
145 container: child.into(),
146 forward_drag_as_cursor: false,
147 on_hold: None,
148 on_drop: None,
149 on_enter: None,
150 on_leave: None,
151 on_motion: None,
152 on_action_selected: None,
153 on_data_received: None,
154 on_finish: None,
155 }
156 }
157
158 #[must_use]
159 pub fn drag_id(mut self, id: u64) -> Self {
160 self.drag_id = Some(id);
161 self
162 }
163
164 #[must_use]
165 pub fn action(mut self, action: DndAction) -> Self {
166 self.action = action;
167 self
168 }
169
170 #[must_use]
171 pub fn preferred_action(mut self, action: DndAction) -> Self {
172 self.preferred_action = action;
173 self
174 }
175
176 #[must_use]
177 pub fn forward_drag_as_cursor(mut self, forward: bool) -> Self {
178 self.forward_drag_as_cursor = forward;
179 self
180 }
181
182 #[must_use]
183 pub fn on_hold(mut self, f: impl Fn(f64, f64) -> Message + 'static) -> Self {
184 self.on_hold = Some(Box::new(f));
185 self
186 }
187
188 #[must_use]
189 pub fn on_drop(mut self, f: impl Fn(f64, f64) -> Message + 'static) -> Self {
190 self.on_drop = Some(Box::new(f));
191 self
192 }
193
194 #[must_use]
195 pub fn on_enter(mut self, f: impl Fn(f64, f64, Vec<String>) -> Message + 'static) -> Self {
196 self.on_enter = Some(Box::new(f));
197 self
198 }
199
200 #[must_use]
201 pub fn on_leave(mut self, m: impl Fn() -> Message + 'static) -> Self {
202 self.on_leave = Some(Box::new(m));
203 self
204 }
205
206 #[must_use]
207 pub fn on_finish(
208 mut self,
209 f: impl Fn(String, Vec<u8>, DndAction, f64, f64) -> Message + 'static,
210 ) -> Self {
211 self.on_finish = Some(Box::new(f));
212 self
213 }
214
215 #[must_use]
216 pub fn on_motion(mut self, f: impl Fn(f64, f64) -> Message + 'static) -> Self {
217 self.on_motion = Some(Box::new(f));
218 self
219 }
220
221 #[must_use]
222 pub fn on_action_selected(mut self, f: impl Fn(DndAction) -> Message + 'static) -> Self {
223 self.on_action_selected = Some(Box::new(f));
224 self
225 }
226
227 #[must_use]
228 pub fn on_data_received(mut self, f: impl Fn(String, Vec<u8>) -> Message + 'static) -> Self {
229 self.on_data_received = Some(Box::new(f));
230 self
231 }
232
233 #[must_use]
238 pub fn get_drag_id(&self) -> u128 {
239 u128::from(self.drag_id.unwrap_or_else(|| match &self.id.0 {
240 Internal::Unique(id) | Internal::Custom(id, _) => *id,
241 Internal::Set(_) => panic!("Invalid Id assigned to dnd destination."),
242 }))
243 }
244
245 pub fn id(mut self, id: Id) -> Self {
246 self.id = id;
247 self
248 }
249}
250
251impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
252 for DndDestination<'_, Message>
253{
254 fn children(&self) -> Vec<Tree> {
255 vec![Tree::new(&self.container)]
256 }
257
258 fn tag(&self) -> iced_core::widget::tree::Tag {
259 tree::Tag::of::<State<()>>()
260 }
261
262 fn diff(&mut self, tree: &mut Tree) {
263 tree.children[0].diff(self.container.as_widget_mut());
264 }
265
266 fn state(&self) -> iced_core::widget::tree::State {
267 tree::State::new(State::<()>::new())
268 }
269
270 fn size(&self) -> iced_core::Size<Length> {
271 self.container.as_widget().size()
272 }
273
274 fn layout(
275 &self,
276 tree: &mut Tree,
277 renderer: &crate::Renderer,
278 limits: &layout::Limits,
279 ) -> layout::Node {
280 self.container
281 .as_widget()
282 .layout(&mut tree.children[0], renderer, limits)
283 }
284
285 fn operate(
286 &self,
287 tree: &mut Tree,
288 layout: layout::Layout<'_>,
289 renderer: &crate::Renderer,
290 operation: &mut dyn iced_core::widget::Operation<()>,
291 ) {
292 self.container
293 .as_widget()
294 .operate(&mut tree.children[0], layout, renderer, operation);
295 }
296
297 #[allow(clippy::too_many_lines)]
298 fn on_event(
299 &mut self,
300 tree: &mut Tree,
301 event: Event,
302 layout: layout::Layout<'_>,
303 cursor: mouse::Cursor,
304 renderer: &crate::Renderer,
305 clipboard: &mut dyn Clipboard,
306 shell: &mut Shell<'_, Message>,
307 viewport: &Rectangle,
308 ) -> event::Status {
309 let s = self.container.as_widget_mut().on_event(
310 &mut tree.children[0],
311 event.clone(),
312 layout,
313 cursor,
314 renderer,
315 clipboard,
316 shell,
317 viewport,
318 );
319 if matches!(s, event::Status::Captured) {
320 return event::Status::Captured;
321 }
322
323 let state = tree.state.downcast_mut::<State<()>>();
324
325 let my_id = self.get_drag_id();
326
327 match event {
328 Event::Dnd(DndEvent::Offer(
329 id,
330 OfferEvent::Enter {
331 x, y, mime_types, ..
332 },
333 )) if id == Some(my_id) => {
334 if let Some(msg) = state.on_enter(
335 x,
336 y,
337 mime_types,
338 self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
339 (),
340 ) {
341 shell.publish(msg);
342 }
343 if self.forward_drag_as_cursor {
344 #[allow(clippy::cast_possible_truncation)]
345 let drag_cursor = mouse::Cursor::Available((x as f32, y as f32).into());
346 let event = Event::Mouse(mouse::Event::CursorMoved {
347 position: drag_cursor.position().unwrap(),
348 });
349 self.container.as_widget_mut().on_event(
350 &mut tree.children[0],
351 event,
352 layout,
353 drag_cursor,
354 renderer,
355 clipboard,
356 shell,
357 viewport,
358 );
359 }
360 return event::Status::Captured;
361 }
362 Event::Dnd(DndEvent::Offer(id, OfferEvent::Leave)) => {
363 if let Some(msg) =
364 state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref))
365 {
366 shell.publish(msg);
367 }
368
369 if self.forward_drag_as_cursor {
370 let drag_cursor = mouse::Cursor::Unavailable;
371 let event = Event::Mouse(mouse::Event::CursorLeft);
372 self.container.as_widget_mut().on_event(
373 &mut tree.children[0],
374 event,
375 layout,
376 drag_cursor,
377 renderer,
378 clipboard,
379 shell,
380 viewport,
381 );
382 }
383 return event::Status::Captured;
384 }
385 Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if id == Some(my_id) => {
386 if let Some(msg) = state.on_motion(
387 x,
388 y,
389 self.on_motion.as_ref().map(std::convert::AsRef::as_ref),
390 self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
391 (),
392 ) {
393 shell.publish(msg);
394 }
395
396 if self.forward_drag_as_cursor {
397 #[allow(clippy::cast_possible_truncation)]
398 let drag_cursor = mouse::Cursor::Available((x as f32, y as f32).into());
399 let event = Event::Mouse(mouse::Event::CursorMoved {
400 position: drag_cursor.position().unwrap(),
401 });
402 self.container.as_widget_mut().on_event(
403 &mut tree.children[0],
404 event,
405 layout,
406 drag_cursor,
407 renderer,
408 clipboard,
409 shell,
410 viewport,
411 );
412 }
413 return event::Status::Captured;
414 }
415 Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) => {
416 if let Some(msg) =
417 state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref))
418 {
419 shell.publish(msg);
420 }
421 return event::Status::Captured;
422 }
423 Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if id == Some(my_id) => {
424 if let Some(msg) =
425 state.on_drop(self.on_drop.as_ref().map(std::convert::AsRef::as_ref))
426 {
427 shell.publish(msg);
428 }
429 return event::Status::Captured;
430 }
431 Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action)))
432 if id == Some(my_id) =>
433 {
434 if let Some(msg) = state.on_action_selected(
435 action,
436 self.on_action_selected
437 .as_ref()
438 .map(std::convert::AsRef::as_ref),
439 ) {
440 shell.publish(msg);
441 }
442 return event::Status::Captured;
443 }
444 Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type }))
445 if id == Some(my_id) =>
446 {
447 if let (Some(msg), ret) = state.on_data_received(
448 mime_type,
449 data,
450 self.on_data_received
451 .as_ref()
452 .map(std::convert::AsRef::as_ref),
453 self.on_finish.as_ref().map(std::convert::AsRef::as_ref),
454 ) {
455 shell.publish(msg);
456 return ret;
457 }
458 return event::Status::Captured;
459 }
460 _ => {}
461 }
462 event::Status::Ignored
463 }
464
465 fn mouse_interaction(
466 &self,
467 tree: &Tree,
468 layout: layout::Layout<'_>,
469 cursor_position: mouse::Cursor,
470 viewport: &Rectangle,
471 renderer: &crate::Renderer,
472 ) -> mouse::Interaction {
473 self.container.as_widget().mouse_interaction(
474 &tree.children[0],
475 layout,
476 cursor_position,
477 viewport,
478 renderer,
479 )
480 }
481
482 fn draw(
483 &self,
484 tree: &Tree,
485 renderer: &mut crate::Renderer,
486 theme: &crate::Theme,
487 renderer_style: &iced_core::renderer::Style,
488 layout: layout::Layout<'_>,
489 cursor_position: mouse::Cursor,
490 viewport: &Rectangle,
491 ) {
492 self.container.as_widget().draw(
493 &tree.children[0],
494 renderer,
495 theme,
496 renderer_style,
497 layout,
498 cursor_position,
499 viewport,
500 );
501 }
502
503 fn overlay<'b>(
504 &'b mut self,
505 tree: &'b mut Tree,
506 layout: layout::Layout<'_>,
507 renderer: &crate::Renderer,
508 translation: Vector,
509 ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
510 self.container
511 .as_widget_mut()
512 .overlay(&mut tree.children[0], layout, renderer, translation)
513 }
514
515 fn drag_destinations(
516 &self,
517 state: &Tree,
518 layout: layout::Layout<'_>,
519 renderer: &crate::Renderer,
520 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
521 ) {
522 let bounds = layout.bounds();
523 let my_id = self.get_drag_id();
524 let my_dest = DndDestinationRectangle {
525 id: my_id,
526 rectangle: dnd::Rectangle {
527 x: f64::from(bounds.x),
528 y: f64::from(bounds.y),
529 width: f64::from(bounds.width),
530 height: f64::from(bounds.height),
531 },
532 mime_types: self.mime_types.clone(),
533 actions: self.action,
534 preferred: self.preferred_action,
535 };
536 dnd_rectangles.push(my_dest);
537
538 self.container.as_widget().drag_destinations(
539 &state.children[0],
540 layout,
541 renderer,
542 dnd_rectangles,
543 );
544 }
545
546 fn id(&self) -> Option<Id> {
547 Some(self.id.clone())
548 }
549
550 fn set_id(&mut self, id: Id) {
551 self.id = id;
552 }
553
554 #[cfg(feature = "a11y")]
555 fn a11y_nodes(
557 &self,
558 layout: iced_core::Layout<'_>,
559 state: &Tree,
560 p: mouse::Cursor,
561 ) -> iced_accessibility::A11yTree {
562 let c_state = &state.children[0];
563 self.container.as_widget().a11y_nodes(layout, c_state, p)
564 }
565}
566
567#[derive(Default)]
568pub struct State<T> {
569 pub drag_offer: Option<DragOffer<T>>,
570}
571
572pub struct DragOffer<T> {
573 pub x: f64,
574 pub y: f64,
575 pub dropped: bool,
576 pub selected_action: DndAction,
577 pub data: T,
578}
579
580impl<T> State<T> {
581 #[must_use]
582 pub fn new() -> Self {
583 Self { drag_offer: None }
584 }
585
586 pub fn on_enter<Message>(
587 &mut self,
588 x: f64,
589 y: f64,
590 mime_types: Vec<String>,
591 on_enter: Option<impl Fn(f64, f64, Vec<String>) -> Message>,
592 data: T,
593 ) -> Option<Message> {
594 self.drag_offer = Some(DragOffer {
595 x,
596 y,
597 dropped: false,
598 selected_action: DndAction::empty(),
599 data,
600 });
601 on_enter.map(|f| f(x, y, mime_types))
602 }
603
604 pub fn on_leave<Message>(&mut self, on_leave: Option<&dyn Fn() -> Message>) -> Option<Message> {
605 if self.drag_offer.as_ref().is_some_and(|d| !d.dropped) {
606 self.drag_offer = None;
607 on_leave.map(|f| f())
608 } else {
609 None
610 }
611 }
612
613 pub fn on_motion<Message>(
614 &mut self,
615 x: f64,
616 y: f64,
617 on_motion: Option<impl Fn(f64, f64) -> Message>,
618 on_enter: Option<impl Fn(f64, f64, Vec<String>) -> Message>,
619 data: T,
620 ) -> Option<Message> {
621 if let Some(s) = self.drag_offer.as_mut() {
622 s.x = x;
623 s.y = y;
624 } else {
625 self.drag_offer = Some(DragOffer {
626 x,
627 y,
628 dropped: false,
629 selected_action: DndAction::empty(),
630 data,
631 });
632 if let Some(f) = on_enter {
633 return Some(f(x, y, vec![]));
634 }
635 }
636 on_motion.map(|f| f(x, y))
637 }
638
639 pub fn on_drop<Message>(
640 &mut self,
641 on_drop: Option<impl Fn(f64, f64) -> Message>,
642 ) -> Option<Message> {
643 if let Some(offer) = self.drag_offer.as_mut() {
644 offer.dropped = true;
645 if let Some(f) = on_drop {
646 return Some(f(offer.x, offer.y));
647 }
648 }
649 None
650 }
651
652 pub fn on_action_selected<Message>(
653 &mut self,
654 action: DndAction,
655 on_action_selected: Option<impl Fn(DndAction) -> Message>,
656 ) -> Option<Message> {
657 if let Some(s) = self.drag_offer.as_mut() {
658 s.selected_action = action;
659 }
660 if let Some(f) = on_action_selected {
661 f(action).into()
662 } else {
663 None
664 }
665 }
666
667 pub fn on_data_received<Message>(
668 &mut self,
669 mime: String,
670 data: Vec<u8>,
671 on_data_received: Option<impl Fn(String, Vec<u8>) -> Message>,
672 on_finish: Option<impl Fn(String, Vec<u8>, DndAction, f64, f64) -> Message>,
673 ) -> (Option<Message>, event::Status) {
674 let Some(dnd) = self.drag_offer.as_ref() else {
675 self.drag_offer = None;
676 return (None, event::Status::Ignored);
677 };
678
679 if dnd.dropped {
680 let ret = (
681 on_finish.map(|f| f(mime, data, dnd.selected_action, dnd.x, dnd.y)),
682 event::Status::Captured,
683 );
684 self.drag_offer = None;
685 ret
686 } else if let Some(f) = on_data_received {
687 (Some(f(mime, data)), event::Status::Captured)
688 } else {
689 (None, event::Status::Ignored)
690 }
691 }
692}
693
694impl<'a, Message: 'static> From<DndDestination<'a, Message>> for Element<'a, Message> {
695 fn from(wrapper: DndDestination<'a, Message>) -> Self {
696 Element::new(wrapper)
697 }
698}