pixel_widgets/widget/
drag_drop.rs

1#![allow(clippy::vec_init_then_push)]
2
3use std::any::Any;
4use std::sync::Mutex;
5
6use smallvec::smallvec;
7
8use crate::draw::Primitive;
9use crate::event::{Event, Key};
10use crate::layout::{Rectangle, Size};
11use crate::node::{GenericNode, IntoNode, Node};
12use crate::style::{StyleState, Stylesheet};
13use crate::widget::{frame::Frame, Context, StateVec, Widget};
14
15/// Message type for communicating between `Drag` and `Drop` widgets
16pub trait DragDropId: 'static + Copy + Any + Send + Sync {}
17
18/// Context for `Drag` and `Drop` widgets. Only `Drag` and `Drop` widgets that share the same `DragDropContext` can
19/// interact with each other.
20//#[derive(Clone)]
21pub struct DragDropContext<T: DragDropId> {
22    data: Mutex<Option<(T, (f32, f32))>>,
23}
24
25/// A draggable item that can be dropped in `Drop` zones.
26pub struct Drag<'a, T: DragDropId, Message> {
27    context: Option<&'a DragDropContext<T>>,
28    data: Option<T>,
29    content: Option<Frame<'a, Message>>,
30}
31
32/// State for `Drag`
33pub struct DragState<T> {
34    dragging: Option<T>,
35    origin: (f32, f32),
36    offset: (f32, f32),
37}
38
39/// A drop zone where draggable `Drag` items may be dropped
40pub struct Drop<'a, T: DragDropId, Message, OnAccept, OnDrop> {
41    context: Option<&'a DragDropContext<T>>,
42    accept: OnAccept,
43    drop: OnDrop,
44    content: Option<Frame<'a, Message>>,
45}
46
47/// State for `Drop`
48pub struct DropState<T> {
49    hovering: Option<(T, (f32, f32))>,
50    mouse_over: bool,
51}
52
53impl<'a, T: DragDropId, Message: 'a> Drag<'a, T, Message> {
54    /// Construct a new `Drag` widget, with some data that is to be dragged through the context.
55    pub fn new(context: &'a DragDropContext<T>, data: T, content: impl IntoNode<'a, Message>) -> Self {
56        Self {
57            context: Some(context),
58            data: Some(data),
59            content: Some(Frame::new(content)),
60        }
61    }
62
63    /// Sets the drag and drop context that the drag widget will work with.
64    pub fn context(mut self, context: &'a DragDropContext<T>) -> Self {
65        self.context = Some(context);
66        self
67    }
68
69    /// Sets the draggable value
70    pub fn val(mut self, value: T) -> Self {
71        self.data = Some(value);
72        self
73    }
74
75    /// Sets the content widget from the first element of an iterator.
76    pub fn extend<I: IntoIterator<Item = N>, N: IntoNode<'a, Message>>(mut self, iter: I) -> Self {
77        if self.content.is_none() {
78            self.content = iter.into_iter().next().map(Frame::new);
79        }
80        self
81    }
82
83    fn content(&self) -> &Frame<'a, Message> {
84        self.content.as_ref().expect("content of `Drag` must be set")
85    }
86
87    fn content_mut(&mut self) -> &mut Frame<'a, Message> {
88        self.content.as_mut().expect("content of `Drag` must be set")
89    }
90}
91
92impl<'a, T: DragDropId, Message: 'a, OnAccept: 'a, OnDrop: 'a> Drop<'a, T, Message, OnAccept, OnDrop>
93where
94    OnAccept: Fn(T) -> bool,
95    OnDrop: Fn(T, (f32, f32)) -> Message,
96{
97    /// Construct a new `Drop` widget
98    pub fn new(
99        context: &'a DragDropContext<T>,
100        accept: OnAccept,
101        drop: OnDrop,
102        content: impl IntoNode<'a, Message>,
103    ) -> Self {
104        Self {
105            context: Some(context),
106            accept,
107            drop,
108            content: Some(Frame::new(content)),
109        }
110    }
111
112    /// Sets the drag and drop context that the drop widget will work with.
113    pub fn context(mut self, context: &'a DragDropContext<T>) -> Self {
114        self.context = Some(context);
115        self
116    }
117
118    /// Sets the on_accept delegate. If a draggable value is accepted by this drop target, the delegate should return true.
119    pub fn on_accept<N: Fn(T) -> bool>(self, on_accept: N) -> Drop<'a, T, Message, N, OnDrop> {
120        Drop {
121            context: self.context,
122            accept: on_accept,
123            drop: self.drop,
124            content: self.content,
125        }
126    }
127
128    /// Sets the on_drop delegate.
129    /// The delegate should return a message for it's parent component based on the dropped value.
130    /// The second argument contains the exact (x, y) coordinates where the value was dropped.
131    pub fn on_drop<N: Fn(T, (f32, f32)) -> Message>(self, on_drop: N) -> Drop<'a, T, Message, OnAccept, N> {
132        Drop {
133            context: self.context,
134            accept: self.accept,
135            drop: on_drop,
136            content: self.content,
137        }
138    }
139
140    /// Sets the content widget from the first element of an iterator.
141    pub fn extend<I: IntoIterator<Item = N>, N: IntoNode<'a, Message>>(mut self, iter: I) -> Self {
142        if self.content.is_none() {
143            self.content = iter.into_iter().next().map(Frame::new);
144        }
145        self
146    }
147
148    fn content(&self) -> &Frame<'a, Message> {
149        self.content.as_ref().expect("content of `Drop` must be set")
150    }
151
152    fn content_mut(&mut self) -> &mut Frame<'a, Message> {
153        self.content.as_mut().expect("content of `Drop` must be set")
154    }
155}
156
157impl<'a, T: DragDropId, Message> Default for Drag<'a, T, Message> {
158    fn default() -> Self {
159        Self {
160            context: None,
161            data: None,
162            content: None,
163        }
164    }
165}
166
167impl<'a, T: DragDropId + Send + Sync, Message: 'a> Widget<'a, Message> for Drag<'a, T, Message> {
168    type State = DragState<T>;
169
170    fn mount(&self) -> Self::State {
171        DragState::<T>::default()
172    }
173
174    fn widget(&self) -> &'static str {
175        "drag"
176    }
177
178    fn state(&self, state: &DragState<T>) -> StateVec {
179        if state.dragging.is_some() {
180            smallvec![StyleState::Drag]
181        } else {
182            smallvec![]
183        }
184    }
185
186    fn len(&self) -> usize {
187        self.content().len()
188    }
189
190    fn visit_children(&mut self, visitor: &mut dyn FnMut(&mut dyn GenericNode<'a, Message>)) {
191        self.content_mut().visit_children(visitor);
192    }
193
194    fn size(&self, _: &DragState<T>, style: &Stylesheet) -> (Size, Size) {
195        self.content().size(&(), style)
196    }
197
198    fn event(
199        &mut self,
200        state: &mut DragState<T>,
201        layout: Rectangle,
202        clip: Rectangle,
203        style: &Stylesheet,
204        event: Event,
205        context: &mut Context<Message>,
206    ) {
207        match event {
208            Event::Press(Key::LeftMouseButton) => {
209                let (x, y) = context.cursor();
210                if layout.point_inside(x, y) && clip.point_inside(x, y) {
211                    self.context
212                        .as_ref()
213                        .expect("context of `Drag` must be set")
214                        .data
215                        .lock()
216                        .unwrap()
217                        .replace((
218                            self.data.expect("data of `Drag` must be set"),
219                            (context.cursor.0 - layout.left, context.cursor.1 - layout.top),
220                        ));
221                    state.origin = context.cursor;
222                    state.offset = (0.0, 0.0);
223                    state.dragging = Some(self.data.expect("data of `Drag` must be set"));
224                    context.redraw();
225                }
226            }
227
228            Event::Cursor(x, y) if state.dragging.is_some() => {
229                state.offset = (x - state.origin.0, y - state.origin.1);
230                context.redraw();
231            }
232
233            Event::Release(Key::LeftMouseButton) if state.dragging.is_some() => {
234                state.dragging.take();
235                self.context
236                    .as_ref()
237                    .expect("context of `Drag` must be set")
238                    .data
239                    .lock()
240                    .unwrap()
241                    .take();
242                context.redraw();
243            }
244
245            _ => (),
246        }
247
248        self.content_mut().event(&mut (), layout, clip, style, event, context);
249    }
250
251    fn draw(
252        &mut self,
253        state: &mut DragState<T>,
254        layout: Rectangle,
255        clip: Rectangle,
256        style: &Stylesheet,
257    ) -> Vec<Primitive<'a>> {
258        if state.dragging.is_some() {
259            let (dx, dy) = state.offset;
260            let mut result = Vec::new();
261            result.push(Primitive::LayerUp);
262            result.extend(self.content_mut().draw(&mut (), layout.translate(dx, dy), clip, style));
263            result.push(Primitive::LayerDown);
264            result
265        } else {
266            self.content_mut().draw(&mut (), layout, clip, style)
267        }
268    }
269}
270
271impl<'a, T: DragDropId, Message> Default for Drop<'a, T, Message, fn(T) -> bool, fn(T, (f32, f32)) -> Message> {
272    fn default() -> Self {
273        Self {
274            context: None,
275            accept: |_| true,
276            drop: |_, _| panic!("on_drop of `Drop` must be set"),
277            content: None,
278        }
279    }
280}
281
282impl<'a, T, Message: 'a, OnAccept, OnDrop> Widget<'a, Message> for Drop<'a, T, Message, OnAccept, OnDrop>
283where
284    T: DragDropId + Send + Sync,
285    OnAccept: 'a + Send + Fn(T) -> bool,
286    OnDrop: 'a + Send + Fn(T, (f32, f32)) -> Message,
287{
288    type State = DropState<T>;
289
290    fn mount(&self) -> DropState<T> {
291        DropState::<T>::default()
292    }
293
294    fn widget(&self) -> &'static str {
295        "drop"
296    }
297
298    fn state(&self, state: &DropState<T>) -> StateVec {
299        if state.hovering.is_some() {
300            smallvec![StyleState::Drop]
301        } else if state.mouse_over {
302            smallvec![StyleState::DropDenied]
303        } else {
304            smallvec![]
305        }
306    }
307
308    fn len(&self) -> usize {
309        self.content().len()
310    }
311
312    fn visit_children(&mut self, visitor: &mut dyn FnMut(&mut dyn GenericNode<'a, Message>)) {
313        self.content_mut().visit_children(visitor);
314    }
315
316    fn size(&self, _: &DropState<T>, style: &Stylesheet) -> (Size, Size) {
317        self.content().size(&(), style)
318    }
319
320    fn event(
321        &mut self,
322        state: &mut DropState<T>,
323        layout: Rectangle,
324        clip: Rectangle,
325        style: &Stylesheet,
326        event: Event,
327        context: &mut Context<Message>,
328    ) {
329        match event {
330            Event::Cursor(x, y) => {
331                let inside = layout.point_inside(x, y) && clip.point_inside(x, y);
332                if inside && !state.mouse_over {
333                    if let Some(data) = *self
334                        .context
335                        .as_ref()
336                        .expect("context of `Drop` must be set")
337                        .data
338                        .lock()
339                        .unwrap()
340                    {
341                        if (self.accept)(data.0) {
342                            state.hovering = Some(data);
343                        }
344                    }
345                } else if !inside && state.mouse_over {
346                    state.hovering = None;
347                }
348                state.mouse_over = inside;
349            }
350
351            Event::Release(Key::LeftMouseButton) => {
352                if let Some(data) = state.hovering.take() {
353                    context.push((self.drop)(
354                        data.0,
355                        (
356                            context.cursor.0 - (data.1).0 - layout.left,
357                            context.cursor.1 - (data.1).1 - layout.top,
358                        ),
359                    ));
360                }
361            }
362
363            _ => (),
364        }
365
366        self.content_mut().event(&mut (), layout, clip, style, event, context)
367    }
368
369    fn draw(
370        &mut self,
371        _: &mut DropState<T>,
372        layout: Rectangle,
373        clip: Rectangle,
374        style: &Stylesheet,
375    ) -> Vec<Primitive<'a>> {
376        self.content_mut().draw(&mut (), layout, clip, style)
377    }
378}
379
380impl<'a, T: DragDropId + Send + Sync, Message: 'a> IntoNode<'a, Message> for Drag<'a, T, Message> {
381    fn into_node(self) -> Node<'a, Message> {
382        Node::from_widget(self)
383    }
384}
385
386impl<'a, T: DragDropId + Send + Sync, Message: 'a, OnAccept: 'a, OnDrop: 'a> IntoNode<'a, Message>
387    for Drop<'a, T, Message, OnAccept, OnDrop>
388where
389    OnAccept: Send + Fn(T) -> bool,
390    OnDrop: Send + Fn(T, (f32, f32)) -> Message,
391{
392    fn into_node(self) -> Node<'a, Message> {
393        Node::from_widget(self)
394    }
395}
396
397impl<T: 'static + Copy + Send + Sync> DragDropId for T {}
398
399impl<T: DragDropId> Default for DragDropContext<T> {
400    fn default() -> Self {
401        Self { data: Mutex::new(None) }
402    }
403}
404
405impl<T: DragDropId> Default for DragState<T> {
406    fn default() -> Self {
407        Self {
408            dragging: None,
409            origin: (0.0, 0.0),
410            offset: (0.0, 0.0),
411        }
412    }
413}
414
415impl<T: DragDropId> Default for DropState<T> {
416    fn default() -> Self {
417        Self {
418            hovering: None,
419            mouse_over: false,
420        }
421    }
422}