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