1use iced_core::layout::{Limits, Node};
3use iced_core::mouse::Cursor;
4use iced_core::renderer::Style;
5use iced_core::widget::tree::Tag;
6use iced_core::widget::{Id, Operation, Tree};
7use iced_core::{
8 Element, Event, Layout, Length, Pixels, Point, Rectangle, Size, Vector,
9 Widget, mouse, overlay, renderer, touch, window,
10};
11use std::fmt::Debug;
12use std::vec;
13
14pub struct Droppable<
16 'a,
17 Message,
18 Theme = iced_widget::Theme,
19 Renderer = iced_widget::Renderer,
20> where
21 Message: Clone,
22 Renderer: renderer::Renderer,
23{
24 content: Element<'a, Message, Theme, Renderer>,
25 id: Option<Id>,
26 drag_threshold: f32,
27 on_press: Option<Message>,
28 on_click: Option<Message>,
29 on_drop: Option<Box<dyn Fn(Point, Rectangle) -> Message + 'a>>,
30 on_drag: Option<Box<dyn Fn(Point, Rectangle) -> Message + 'a>>,
31 on_cancel: Option<Message>,
32 drag_mode: Option<(bool, bool)>,
33 drag_overlay: bool,
34 drag_hide: bool,
35 drag_center: bool,
36 drag_size: Option<Size>,
37 reset_delay: usize,
38 status: Option<Status>,
39}
40
41impl<'a, Message, Theme, Renderer> Droppable<'a, Message, Theme, Renderer>
42where
43 Message: Clone,
44 Renderer: renderer::Renderer,
45{
46 pub fn new(
48 content: impl Into<Element<'a, Message, Theme, Renderer>>,
49 ) -> Self {
50 Self {
51 content: content.into(),
52 id: None,
53 drag_threshold: 5.0,
54 on_press: None,
55 on_click: None,
56 on_drop: None,
57 on_drag: None,
58 on_cancel: None,
59 drag_mode: Some((true, true)),
60 drag_overlay: true,
61 drag_hide: false,
62 drag_center: false,
63 drag_size: None,
64 reset_delay: 0,
65 status: None,
66 }
67 }
68
69 pub fn id(mut self, id: Id) -> Self {
71 self.id = Some(id);
72 self
73 }
74
75 pub fn drag_threshold(mut self, drag_threshold: impl Into<Pixels>) -> Self {
82 self.drag_threshold = drag_threshold.into().0;
83 self
84 }
85
86 pub fn on_click(mut self, message: Message) -> Self {
90 self.on_click = Some(message);
91 self
92 }
93
94 pub fn on_press(mut self, message: Message) -> Self {
98 self.on_press = Some(message);
99 self
100 }
101
102 pub fn on_drop<F>(mut self, message: F) -> Self
106 where
107 F: Fn(Point, Rectangle) -> Message + 'a,
108 {
109 self.on_drop = Some(Box::new(message));
110 self
111 }
112
113 pub fn on_drag<F>(mut self, message: F) -> Self
115 where
116 F: Fn(Point, Rectangle) -> Message + 'a,
117 {
118 self.on_drag = Some(Box::new(message));
119 self
120 }
121
122 pub fn on_cancel(mut self, message: Message) -> Self {
124 self.on_cancel = Some(message);
125 self
126 }
127
128 pub fn drag_overlay(mut self, drag_overlay: bool) -> Self {
130 self.drag_overlay = drag_overlay;
131 self
132 }
133
134 pub fn drag_hide(mut self, drag_hide: bool) -> Self {
136 self.drag_hide = drag_hide;
137 self
138 }
139
140 pub fn drag_center(mut self, drag_center: bool) -> Self {
142 self.drag_center = drag_center;
143 self
144 }
145
146 pub fn drag_mode(mut self, drag_x: bool, drag_y: bool) -> Self {
148 self.drag_mode = Some((drag_x, drag_y));
149 self
150 }
151
152 pub fn drag_size(mut self, hide_size: Size) -> Self {
154 self.drag_size = Some(hide_size);
155 self
156 }
157
158 pub fn reset_delay(mut self, reset_delay: usize) -> Self {
168 self.reset_delay = reset_delay;
169 self
170 }
171}
172
173impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
174 for Droppable<'a, Message, Theme, Renderer>
175where
176 Message: Clone,
177 Renderer: renderer::Renderer,
178{
179 fn size(&self) -> Size<Length> {
180 self.content.as_widget().size()
181 }
182
183 fn layout(
184 &mut self,
185 tree: &mut Tree,
186 renderer: &Renderer,
187 limits: &Limits,
188 ) -> Node {
189 let state: &mut State = tree.state.downcast_mut::<State>();
190 let content_node = self.content.as_widget_mut().layout(
191 &mut tree.children[0],
192 renderer,
193 limits,
194 );
195
196 if let Some(new_size) = self.drag_size {
198 match state.action {
199 Action::Drag(_, _) => {
200 return Node::with_children(
201 new_size,
202 content_node.children().to_vec(),
203 );
204 }
205 Action::Wait(reveal_index) => {
206 if reveal_index <= 1 {
207 state.action = Action::None;
208 } else {
209 state.action = Action::Wait(reveal_index - 1);
210 }
211
212 return Node::with_children(
213 new_size,
214 content_node.children().to_vec(),
215 );
216 }
217 _ => (),
218 }
219 }
220
221 content_node
222 }
223
224 fn draw(
225 &self,
226 tree: &Tree,
227 renderer: &mut Renderer,
228 theme: &Theme,
229 style: &Style,
230 layout: Layout<'_>,
231 cursor: Cursor,
232 viewport: &Rectangle,
233 ) {
234 let state: &State = tree.state.downcast_ref::<State>();
235 if let Action::Drag(_, _) = state.action
236 && self.drag_hide
237 {
238 return;
239 }
240
241 self.content.as_widget().draw(
242 &tree.children[0],
243 renderer,
244 theme,
245 style,
246 layout,
247 cursor,
248 viewport,
249 );
250 }
251
252 fn tag(&self) -> Tag {
253 Tag::of::<State>()
254 }
255
256 fn state(&self) -> iced_core::widget::tree::State {
257 iced_core::widget::tree::State::new(State::default())
258 }
259
260 fn children(&self) -> Vec<Tree> {
261 vec![Tree::new(&self.content)]
262 }
263
264 fn diff(&self, tree: &mut Tree) {
265 tree.diff_children(std::slice::from_ref(&self.content))
266 }
267
268 fn operate(
269 &mut self,
270 tree: &mut Tree,
271 layout: Layout<'_>,
272 renderer: &Renderer,
273 operation: &mut dyn Operation,
274 ) {
275 let state = tree.state.downcast_mut::<State>();
276 operation.custom(self.id.as_ref(), layout.bounds(), state);
277 operation.container(self.id.as_ref(), layout.bounds());
278 operation.traverse(&mut |operation| {
279 self.content.as_widget_mut().operate(
280 &mut tree.children[0],
281 layout,
282 renderer,
283 operation,
284 );
285 });
286 }
287
288 fn update(
289 &mut self,
290 tree: &mut Tree,
291 event: &Event,
292 layout: Layout<'_>,
293 cursor: Cursor,
294 _renderer: &Renderer,
295 _clipboard: &mut dyn iced_core::Clipboard,
296 shell: &mut iced_core::Shell<'_, Message>,
297 _viewport: &Rectangle,
298 ) {
299 let state = tree.state.downcast_mut::<State>();
300
301 if !matches!(state.action, Action::Drag(_, _)) {
302 self.content.as_widget_mut().update(
304 &mut tree.children[0],
305 event,
306 layout,
307 cursor,
308 _renderer,
309 _clipboard,
310 shell,
311 _viewport,
312 );
313 if shell.is_event_captured() {
316 return;
317 }
318 }
319
320 if let Some(on_drop) = self.on_drop.as_deref() {
321 match event {
322 Event::Mouse(mouse::Event::ButtonPressed(
323 mouse::Button::Left,
324 ))
325 | Event::Touch(touch::Event::FingerPressed { .. }) => {
326 let bounds = layout.bounds();
327 if cursor.is_over(bounds) {
328 state.action =
330 Action::Select(cursor.position().unwrap());
331 state.widget_pos = bounds.position();
332 state.overlay_bounds.width = bounds.width;
333 state.overlay_bounds.height = bounds.height;
334
335 if let Some(on_click) = self.on_click.clone() {
336 shell.publish(on_click);
337 }
338
339 shell.capture_event();
340 }
341 }
342 Event::Mouse(mouse::Event::ButtonPressed(
343 mouse::Button::Right,
344 ))
345 | Event::Touch(touch::Event::FingerLost { .. }) => {
346 if let Action::Drag(_, _) = state.action {
347 state.action = Action::None;
348 if let Some(on_cancel) = self.on_cancel.clone() {
349 shell.publish(on_cancel);
350 }
351
352 shell.invalidate_layout();
353 shell.request_redraw();
354 }
355 }
356 Event::Mouse(mouse::Event::ButtonReleased(
357 mouse::Button::Left,
358 ))
359 | Event::Touch(touch::Event::FingerLifted { .. }) => {
360 match state.action {
361 Action::Select(_) => {
362 if let Some(on_press) = self.on_press.clone() {
363 shell.publish(on_press);
364 }
365
366 state.action = Action::None;
367 }
368 Action::Drag(_, current) => {
369 let message =
371 (on_drop)(current, state.overlay_bounds);
372 shell.publish(message);
373
374 if self.reset_delay == 0 {
375 state.action = Action::None;
376 } else {
377 state.action = Action::Wait(self.reset_delay);
378 }
379 }
380 _ => (),
381 }
382 }
383 Event::Mouse(mouse::Event::CursorMoved { position })
384 | Event::Touch(touch::Event::FingerMoved {
385 position,
386 ..
387 }) => {
388 let mut position = *position;
389
390 let should_drag = match state.action {
391 Action::Select(start) => {
392 let distance = ((position.x - start.x).powi(2)
395 + (position.y - start.y).powi(2))
396 .sqrt();
397
398 if distance >= self.drag_threshold {
399 state.action = Action::Drag(start, position);
400 true
401 } else {
402 false
403 }
404 }
405 Action::Drag(start, _) => {
406 state.action = Action::Drag(start, position);
407 true
408 }
409 _ => false,
410 };
411
412 if should_drag && let Action::Drag(start, _) = state.action
413 {
414 if let Some((drag_x, drag_y)) = self.drag_mode {
416 position = Point {
417 x: if drag_x { position.x } else { start.x },
418 y: if drag_y { position.y } else { start.y },
419 };
420 }
421
422 if self.drag_center {
424 state.overlay_bounds.x =
425 position.x - state.overlay_bounds.width / 2.0;
426 state.overlay_bounds.y =
427 position.y - state.overlay_bounds.height / 2.0;
428 } else {
429 state.overlay_bounds.x =
430 state.widget_pos.x + position.x - start.x;
431 state.overlay_bounds.y =
432 state.widget_pos.y + position.y - start.y;
433 }
434
435 if let Some(on_drag) = self.on_drag.as_deref() {
437 let message =
438 (on_drag)(position, state.overlay_bounds);
439 shell.publish(message);
440 }
441
442 shell.request_redraw();
443 }
444 }
445 _ => {}
446 }
447 }
448
449 let current_status = if self.on_drop.is_none() {
450 Status::Disabled
451 } else if cursor.is_over(layout.bounds()) {
452 if let Action::Drag(_, _) = state.action {
453 Status::Dragged
454 } else {
455 Status::Hovered
456 }
457 } else {
458 Status::Active
459 };
460
461 if let Event::Window(window::Event::RedrawRequested(_now)) = event {
462 self.status = Some(current_status);
463 } else if self.status.is_some_and(|status| status != current_status) {
464 shell.request_redraw();
465 }
466 }
467
468 fn mouse_interaction(
469 &self,
470 tree: &Tree,
471 layout: Layout<'_>,
472 cursor: Cursor,
473 _viewport: &Rectangle,
474 _renderer: &Renderer,
475 ) -> mouse::Interaction {
476 let state = tree.state.downcast_ref::<State>();
477
478 if let Action::Drag(_, _) = state.action {
479 return mouse::Interaction::Grabbing;
480 }
481
482 let child_interact = self.content.as_widget().mouse_interaction(
483 &tree.children[0],
484 layout,
485 cursor,
486 _viewport,
487 _renderer,
488 );
489
490 if child_interact != mouse::Interaction::default() {
491 return child_interact;
492 }
493
494 if cursor.is_over(layout.bounds()) {
495 if self.on_drop.is_some() {
496 if self.on_press.is_some() {
497 mouse::Interaction::Pointer
498 } else {
499 mouse::Interaction::Grab
500 }
501 } else {
502 mouse::Interaction::NotAllowed
503 }
504 } else {
505 mouse::Interaction::default()
506 }
507 }
508
509 fn overlay<'b>(
510 &'b mut self,
511 tree: &'b mut Tree,
512 layout: Layout<'b>,
513 renderer: &Renderer,
514 _viewport: &Rectangle,
515 _translation: Vector,
516 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
517 let state: &mut State = tree.state.downcast_mut::<State>();
518 if self.drag_overlay
519 && let Action::Drag(_, _) = state.action
520 {
521 return Some(overlay::Element::new(Box::new(Overlay {
522 content: &mut self.content,
523 tree: &mut tree.children[0],
524 overlay_bounds: state.overlay_bounds,
525 })));
526 }
527 self.content.as_widget_mut().overlay(
528 &mut tree.children[0],
529 layout,
530 renderer,
531 _viewport,
532 _translation,
533 )
534 }
535}
536
537impl<'a, Message, Theme, Renderer> From<Droppable<'a, Message, Theme, Renderer>>
538 for Element<'a, Message, Theme, Renderer>
539where
540 Message: 'a + Clone,
541 Theme: 'a,
542 Renderer: 'a + renderer::Renderer,
543{
544 fn from(
545 droppable: Droppable<'a, Message, Theme, Renderer>,
546 ) -> Element<'a, Message, Theme, Renderer> {
547 Element::new(droppable)
548 }
549}
550
551#[derive(Default, Clone, Copy, PartialEq, Debug)]
552pub struct State {
553 widget_pos: Point,
554 overlay_bounds: Rectangle,
555 action: Action,
556}
557
558#[derive(Default, Clone, Copy, PartialEq, Debug)]
559pub enum Status {
560 #[default]
561 Active,
562 Hovered,
563 Dragged,
564 Disabled,
565}
566
567#[derive(Default, Clone, Copy, PartialEq, Debug)]
568pub enum Action {
569 #[default]
570 None,
571 Select(Point),
573 Drag(Point, Point),
575 Wait(usize),
577}
578
579struct Overlay<'a, 'b, Message, Theme, Renderer>
580where
581 Renderer: renderer::Renderer,
582{
583 content: &'b mut Element<'a, Message, Theme, Renderer>,
584 tree: &'b mut Tree,
585 overlay_bounds: Rectangle,
586}
587
588impl<'a, 'b, Message, Theme, Renderer>
589 overlay::Overlay<Message, Theme, Renderer>
590 for Overlay<'a, 'b, Message, Theme, Renderer>
591where
592 Renderer: renderer::Renderer,
593{
594 fn layout(&mut self, renderer: &Renderer, _bounds: Size) -> Node {
595 Widget::<Message, Theme, Renderer>::layout(
596 self.content.as_widget_mut(),
597 self.tree,
598 renderer,
599 &Limits::new(Size::ZERO, self.overlay_bounds.size()),
600 )
601 .move_to(self.overlay_bounds.position())
602 }
603
604 fn draw(
605 &self,
606 renderer: &mut Renderer,
607 theme: &Theme,
608 inherited_style: &Style,
609 layout: Layout<'_>,
610 cursor_position: Cursor,
611 ) {
612 Widget::<Message, Theme, Renderer>::draw(
613 self.content.as_widget(),
614 self.tree,
615 renderer,
616 theme,
617 inherited_style,
618 layout,
619 cursor_position,
620 &Rectangle::with_size(Size::INFINITE),
621 );
622 }
623}