1use std::any::Any;
2
3use iced_core::{widget::Operation, window};
4
5use crate::{
6 Element,
7 widget::{Id, Widget, container},
8};
9use iced::{
10 Event, Length, Point, Rectangle, Vector,
11 clipboard::dnd::{DndAction, DndEvent, SourceEvent},
12 event, mouse, overlay,
13};
14use iced_core::{
15 self, Clipboard, Shell, layout, renderer,
16 widget::{Tree, tree},
17};
18
19pub fn dnd_source<
20 'a,
21 Message: Clone + 'static,
22 D: iced::clipboard::mime::AsMimeTypes + Send + 'static,
23>(
24 child: impl Into<Element<'a, Message>>,
25) -> DndSource<'a, Message, D> {
26 DndSource::new(child)
27}
28
29pub struct DndSource<'a, Message, D> {
30 id: Id,
31 action: DndAction,
32 container: Element<'a, Message>,
33 window: Option<window::Id>,
34 drag_content: Option<Box<dyn Fn() -> D>>,
35 drag_icon: Option<Box<dyn Fn(Vector) -> (Element<'static, ()>, tree::State, Vector)>>,
36 on_start: Option<Message>,
37 on_cancelled: Option<Message>,
38 on_finish: Option<Message>,
39 drag_threshold: f32,
40}
41
42impl<
43 'a,
44 Message: Clone + 'static,
45 D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
46> DndSource<'a, Message, D>
47{
48 pub fn new(child: impl Into<Element<'a, Message>>) -> Self {
49 Self {
50 id: Id::unique(),
51 window: None,
52 action: DndAction::Copy | DndAction::Move,
53 container: container(child).into(),
54 drag_content: None,
55 drag_icon: None,
56 drag_threshold: 8.0,
57 on_start: None,
58 on_cancelled: None,
59 on_finish: None,
60 }
61 }
62
63 pub fn with_id(child: impl Into<Element<'a, Message>>, id: Id) -> Self {
64 Self {
65 id,
66 window: None,
67 action: DndAction::Copy | DndAction::Move,
68 container: container(child).into(),
69 drag_content: None,
70 drag_icon: None,
71 drag_threshold: 8.0,
72 on_start: None,
73 on_cancelled: None,
74 on_finish: None,
75 }
76 }
77
78 #[must_use]
79 pub fn action(mut self, action: DndAction) -> Self {
80 self.action = action;
81 self
82 }
83
84 #[must_use]
85 pub fn drag_content(mut self, f: impl Fn() -> D + 'static) -> Self {
86 self.drag_content = Some(Box::new(f));
87 self
88 }
89
90 #[must_use]
91 pub fn drag_icon(
92 mut self,
93 f: impl Fn(Vector) -> (Element<'static, ()>, tree::State, Vector) + 'static,
94 ) -> Self {
95 self.drag_icon = Some(Box::new(f));
96 self
97 }
98
99 #[must_use]
100 pub fn drag_threshold(mut self, threshold: f32) -> Self {
101 self.drag_threshold = threshold;
102 self
103 }
104
105 pub fn start_dnd(&self, clipboard: &mut dyn Clipboard, bounds: Rectangle, offset: Vector) {
106 let Some(content) = self.drag_content.as_ref().map(|f| f()) else {
107 return;
108 };
109
110 iced_core::clipboard::start_dnd(
111 clipboard,
112 false,
113 if let Some(window) = self.window.as_ref() {
114 Some(iced_core::clipboard::DndSource::Surface(*window))
115 } else {
116 Some(iced_core::clipboard::DndSource::Widget(self.id.clone()))
117 },
118 self.drag_icon.as_ref().map(|f| {
119 let (icon, state, offset) = f(offset);
120 iced_core::clipboard::IconSurface::new(
121 container(icon)
122 .width(Length::Fixed(bounds.width))
123 .height(Length::Fixed(bounds.height))
124 .into(),
125 state,
126 offset,
127 )
128 }),
129 Box::new(content),
130 self.action,
131 );
132 }
133
134 #[must_use]
135 pub fn on_start(mut self, on_start: Option<Message>) -> Self {
136 self.on_start = on_start;
137 self
138 }
139
140 #[must_use]
141 pub fn on_cancel(mut self, on_cancelled: Option<Message>) -> Self {
142 self.on_cancelled = on_cancelled;
143 self
144 }
145
146 #[must_use]
147 pub fn on_finish(mut self, on_finish: Option<Message>) -> Self {
148 self.on_finish = on_finish;
149 self
150 }
151
152 #[must_use]
153 pub fn window(mut self, window: window::Id) -> Self {
154 self.window = Some(window);
155 self
156 }
157}
158
159impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static>
160 Widget<Message, crate::Theme, crate::Renderer> for DndSource<'_, Message, D>
161{
162 fn children(&self) -> Vec<Tree> {
163 vec![Tree::new(&self.container)]
164 }
165
166 fn tag(&self) -> iced_core::widget::tree::Tag {
167 tree::Tag::of::<State>()
168 }
169
170 fn diff(&mut self, tree: &mut Tree) {
171 tree.diff_children(std::slice::from_mut(&mut self.container));
172 }
173
174 fn state(&self) -> iced_core::widget::tree::State {
175 tree::State::new(State::new())
176 }
177
178 fn size(&self) -> iced_core::Size<Length> {
179 self.container.as_widget().size()
180 }
181
182 fn layout(
183 &mut self,
184 tree: &mut Tree,
185 renderer: &crate::Renderer,
186 limits: &layout::Limits,
187 ) -> layout::Node {
188 let state = tree.state.downcast_mut::<State>();
189 let node = self
190 .container
191 .as_widget_mut()
192 .layout(&mut tree.children[0], renderer, limits);
193 state.cached_bounds = node.bounds();
194 node
195 }
196
197 fn operate(
198 &mut self,
199 tree: &mut Tree,
200 layout: layout::Layout<'_>,
201 renderer: &crate::Renderer,
202 operation: &mut dyn Operation,
203 ) {
204 operation.custom(
205 Some(&self.id),
206 layout.bounds(),
207 (&mut tree.state) as &mut dyn Any,
208 );
209
210 self.container
211 .as_widget_mut()
212 .operate(&mut tree.children[0], layout, renderer, operation);
213 }
214
215 fn update(
216 &mut self,
217 tree: &mut Tree,
218 event: &Event,
219 layout: layout::Layout<'_>,
220 cursor: mouse::Cursor,
221 renderer: &crate::Renderer,
222 clipboard: &mut dyn Clipboard,
223 shell: &mut Shell<'_, Message>,
224 viewport: &Rectangle,
225 ) {
226 self.container.as_widget_mut().update(
227 &mut tree.children[0],
228 event,
229 layout,
230 cursor,
231 renderer,
232 clipboard,
233 shell,
234 viewport,
235 );
236
237 let state = tree.state.downcast_mut::<State>();
238
239 match event {
240 Event::Mouse(mouse_event) => match mouse_event {
241 mouse::Event::ButtonPressed(mouse::Button::Left) => {
242 if let Some(position) = cursor.position() {
243 if !cursor.is_over(layout.bounds()) {
244 return;
245 }
246
247 state.left_pressed_position = Some(position);
248 shell.capture_event();
249 }
250 }
251 mouse::Event::ButtonReleased(mouse::Button::Left)
252 if state.left_pressed_position.is_some() =>
253 {
254 state.left_pressed_position = None;
255 shell.capture_event();
256 }
257 mouse::Event::CursorMoved { .. } => {
258 if let Some(position) = cursor.position() {
259 if self.drag_content.is_none() {
261 state.left_pressed_position = None;
262 return;
263 }
264 if let Some(left_pressed_position) = state.left_pressed_position
265 && position.distance(left_pressed_position) > self.drag_threshold
266 {
267 if let Some(on_start) = self.on_start.as_ref() {
268 shell.publish(on_start.clone());
269 }
270 let offset = Vector::new(
271 left_pressed_position.x - layout.bounds().x,
272 left_pressed_position.y - layout.bounds().y,
273 );
274 self.start_dnd(clipboard, state.cached_bounds, offset);
275 state.is_dragging = true;
276 state.left_pressed_position = None;
277 }
278 if !cursor.is_over(layout.bounds()) {
279 return;
280 }
281 shell.capture_event();
282 }
283 }
284 _ => (),
285 },
286 Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => {
287 if state.is_dragging {
288 if let Some(m) = self.on_cancelled.as_ref() {
289 shell.publish(m.clone());
290 }
291 state.is_dragging = false;
292 shell.capture_event();
293 }
294 }
295 Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => {
296 if state.is_dragging {
297 if let Some(m) = self.on_finish.as_ref() {
298 shell.publish(m.clone());
299 }
300 state.is_dragging = false;
301 shell.capture_event();
302 }
303 }
304 _ => (),
305 }
306 }
307
308 fn mouse_interaction(
309 &self,
310 tree: &Tree,
311 layout: layout::Layout<'_>,
312 cursor_position: mouse::Cursor,
313 viewport: &Rectangle,
314 renderer: &crate::Renderer,
315 ) -> mouse::Interaction {
316 let state = tree.state.downcast_ref::<State>();
317 if state.is_dragging {
318 return mouse::Interaction::Grabbing;
319 }
320 self.container.as_widget().mouse_interaction(
321 &tree.children[0],
322 layout,
323 cursor_position,
324 viewport,
325 renderer,
326 )
327 }
328
329 fn draw(
330 &self,
331 tree: &Tree,
332 renderer: &mut crate::Renderer,
333 theme: &crate::Theme,
334 renderer_style: &renderer::Style,
335 layout: layout::Layout<'_>,
336 cursor_position: mouse::Cursor,
337 viewport: &Rectangle,
338 ) {
339 self.container.as_widget().draw(
340 &tree.children[0],
341 renderer,
342 theme,
343 renderer_style,
344 layout,
345 cursor_position,
346 viewport,
347 );
348 }
349
350 fn overlay<'b>(
351 &'b mut self,
352 tree: &'b mut Tree,
353 layout: layout::Layout<'b>,
354 renderer: &crate::Renderer,
355 viewport: &Rectangle,
356 translation: Vector,
357 ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
358 self.container.as_widget_mut().overlay(
359 &mut tree.children[0],
360 layout,
361 renderer,
362 viewport,
363 translation,
364 )
365 }
366
367 fn drag_destinations(
368 &self,
369 state: &Tree,
370 layout: layout::Layout<'_>,
371 renderer: &crate::Renderer,
372 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
373 ) {
374 self.container.as_widget().drag_destinations(
375 &state.children[0],
376 layout,
377 renderer,
378 dnd_rectangles,
379 );
380 }
381
382 fn id(&self) -> Option<Id> {
383 Some(self.id.clone())
384 }
385
386 fn set_id(&mut self, id: Id) {
387 self.id = id;
388 }
389
390 #[cfg(feature = "a11y")]
391 fn a11y_nodes(
393 &self,
394 layout: iced_core::Layout<'_>,
395 state: &Tree,
396 p: mouse::Cursor,
397 ) -> iced_accessibility::A11yTree {
398 let c_state = &state.children[0];
399 self.container.as_widget().a11y_nodes(layout, c_state, p)
400 }
401}
402
403impl<
404 'a,
405 Message: Clone + 'static,
406 D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
407> From<DndSource<'a, Message, D>> for Element<'a, Message>
408{
409 fn from(e: DndSource<'a, Message, D>) -> Element<'a, Message> {
410 Element::new(e)
411 }
412}
413
414#[derive(Debug, Default)]
416struct State {
417 left_pressed_position: Option<Point>,
418 is_dragging: bool,
419 cached_bounds: Rectangle,
420}
421
422impl State {
423 fn new() -> Self {
424 Self::default()
425 }
426}