Skip to main content

vizia_core/modifiers/
drag.rs

1use crate::prelude::*;
2use std::any::TypeId;
3
4pub(crate) struct DragModel {
5    pub(crate) on_drag_start: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
6    pub(crate) on_drag_enter: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
7    pub(crate) on_drag_leave: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
8    pub(crate) on_drag_move: Option<Box<dyn Fn(&mut EventContext, f32, f32) + Send + Sync>>,
9    pub(crate) on_drop: Option<Box<dyn Fn(&mut EventContext, DropData) + Send + Sync>>,
10    pub(crate) dragging: Signal<bool>,
11}
12
13impl DragModel {
14    pub(crate) fn new() -> Self {
15        Self {
16            on_drag_start: None,
17            on_drag_enter: None,
18            on_drag_leave: None,
19            on_drag_move: None,
20            on_drop: None,
21            dragging: Signal::new(false),
22        }
23    }
24
25    fn try_start_drag(&mut self, cx: &mut EventContext) {
26        if cx.mouse.left.state == MouseButtonState::Pressed
27            && cx.mouse.left.pressed == cx.current()
28            && cx.is_draggable()
29            && reached_drag_distance_threshold(cx)
30            && !cx.has_drop_data()
31        {
32            if let Some(action) = &self.on_drag_start {
33                (action)(cx);
34            }
35
36            if cx.has_drop_data() {
37                cx.capture();
38                self.dragging.set(true);
39            }
40        }
41    }
42}
43
44pub(crate) enum DragEvent {
45    OnDragStart(Box<dyn Fn(&mut EventContext) + Send + Sync>),
46    OnDragEnter(Box<dyn Fn(&mut EventContext) + Send + Sync>),
47    OnDragLeave(Box<dyn Fn(&mut EventContext) + Send + Sync>),
48    OnDragMove(Box<dyn Fn(&mut EventContext, f32, f32) + Send + Sync>),
49    OnDrop(Box<dyn Fn(&mut EventContext, DropData) + Send + Sync>),
50}
51
52fn reached_drag_distance_threshold(cx: &EventContext) -> bool {
53    let (press_x, press_y) = cx.mouse.left.pos_down;
54    let delta_x = cx.mouse.cursor_x - press_x;
55    let delta_y = cx.mouse.cursor_y - press_y;
56    let drag_distance = cx.environment().drag_distance.get() as f32;
57
58    delta_x * delta_x + delta_y * delta_y >= drag_distance * drag_distance
59}
60
61impl Model for DragModel {
62    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
63        event.take(|drag_event, _| match drag_event {
64            DragEvent::OnDragStart(on_drag_start) => {
65                self.on_drag_start = Some(on_drag_start);
66            }
67
68            DragEvent::OnDragEnter(on_drag_enter) => {
69                self.on_drag_enter = Some(on_drag_enter);
70            }
71
72            DragEvent::OnDragLeave(on_drag_leave) => {
73                self.on_drag_leave = Some(on_drag_leave);
74            }
75
76            DragEvent::OnDragMove(on_drag_move) => {
77                self.on_drag_move = Some(on_drag_move);
78            }
79
80            DragEvent::OnDrop(on_drop) => {
81                self.on_drop = Some(on_drop);
82            }
83        });
84
85        event.map(|window_event, _meta| match window_event {
86            WindowEvent::Drop(drop_data) => {
87                if let Some(action) = &self.on_drop {
88                    (action)(cx, drop_data.clone());
89                }
90            }
91
92            WindowEvent::DragEnter => {
93                if let Some(action) = &self.on_drag_enter {
94                    (action)(cx);
95                }
96            }
97
98            WindowEvent::DragLeave => {
99                if let Some(action) = &self.on_drag_leave {
100                    (action)(cx);
101                }
102            }
103
104            WindowEvent::DragMove(x, y) => {
105                if let Some(action) = &self.on_drag_move {
106                    (action)(cx, *x, *y);
107                }
108            }
109
110            WindowEvent::MouseOut => {
111                self.try_start_drag(cx);
112            }
113
114            WindowEvent::MouseMove(_, _) => {
115                self.try_start_drag(cx);
116
117                if cx.mouse.left.state == MouseButtonState::Released {
118                    self.dragging.set(false);
119                }
120            }
121
122            WindowEvent::MouseUp(MouseButton::Left) => {
123                // Dragging is initiated via the left button, so only left-button
124                // release should end the drag and release capture.
125                cx.release();
126                self.dragging.set(false);
127            }
128
129            _ => {}
130        });
131    }
132}
133
134fn build_drag_model(cx: &mut Context, entity: Entity) {
135    if cx.models.get(&entity).and_then(|models| models.get(&TypeId::of::<DragModel>())).is_none() {
136        cx.with_current(entity, |cx| {
137            DragModel::new().build(cx);
138        });
139    }
140}
141
142/// Modifiers which add drag-and-drop callbacks to a view.
143pub trait DragModifiers<V> {
144    /// Adds a callback which is performed when the view begins to be dragged.
145    ///
146    /// The callback should call [`set_drop_data`](EventContext::set_drop_data) to supply the data
147    /// that will be delivered to the drop target.
148    fn on_drag<F>(self, action: F) -> Self
149    where
150        F: 'static + Fn(&mut EventContext) + Send + Sync;
151
152    /// Adds a callback which is performed when the cursor enters this view while carrying drag data.
153    fn on_drag_enter<F>(self, action: F) -> Self
154    where
155        F: 'static + Fn(&mut EventContext) + Send + Sync;
156
157    /// Adds a callback which is performed when the cursor leaves this view while carrying drag data.
158    fn on_drag_leave<F>(self, action: F) -> Self
159    where
160        F: 'static + Fn(&mut EventContext) + Send + Sync;
161
162    /// Adds a callback which is performed when the cursor moves over this view while carrying drag data.
163    fn on_drag_move<F>(self, action: F) -> Self
164    where
165        F: 'static + Fn(&mut EventContext, f32, f32) + Send + Sync;
166
167    /// Adds a callback which is performed when data is dropped on the view during a drag-and-drop operation.
168    fn on_drop<F>(self, action: F) -> Self
169    where
170        F: 'static + Fn(&mut EventContext, DropData) + Send + Sync;
171
172    /// Adds a view that is rendered under the mouse cursor while this view is being dragged.
173    fn on_drag_view<C: Fn(&mut Context) -> Handle<'_, T> + 'static, T: View>(
174        self,
175        content: C,
176    ) -> Self;
177}
178
179impl<V: View> DragModifiers<V> for Handle<'_, V> {
180    fn on_drag<F>(self, action: F) -> Self
181    where
182        F: 'static + Fn(&mut EventContext) + Send + Sync,
183    {
184        build_drag_model(self.cx, self.entity);
185
186        if let Some(abilities) = self.cx.style.abilities.get_mut(self.entity) {
187            abilities.set(Abilities::DRAGGABLE, true);
188        }
189
190        self.cx.emit_custom(
191            Event::new(DragEvent::OnDragStart(Box::new(action)))
192                .target(self.entity)
193                .origin(self.entity),
194        );
195
196        self
197    }
198
199    fn on_drag_enter<F>(self, action: F) -> Self
200    where
201        F: 'static + Fn(&mut EventContext) + Send + Sync,
202    {
203        build_drag_model(self.cx, self.entity);
204
205        self.cx.emit_custom(
206            Event::new(DragEvent::OnDragEnter(Box::new(action)))
207                .target(self.entity)
208                .origin(self.entity),
209        );
210
211        self
212    }
213
214    fn on_drag_leave<F>(self, action: F) -> Self
215    where
216        F: 'static + Fn(&mut EventContext) + Send + Sync,
217    {
218        build_drag_model(self.cx, self.entity);
219
220        self.cx.emit_custom(
221            Event::new(DragEvent::OnDragLeave(Box::new(action)))
222                .target(self.entity)
223                .origin(self.entity),
224        );
225
226        self
227    }
228
229    fn on_drag_move<F>(self, action: F) -> Self
230    where
231        F: 'static + Fn(&mut EventContext, f32, f32) + Send + Sync,
232    {
233        build_drag_model(self.cx, self.entity);
234
235        self.cx.emit_custom(
236            Event::new(DragEvent::OnDragMove(Box::new(action)))
237                .target(self.entity)
238                .origin(self.entity),
239        );
240
241        self
242    }
243
244    fn on_drop<F>(self, action: F) -> Self
245    where
246        F: 'static + Fn(&mut EventContext, DropData) + Send + Sync,
247    {
248        build_drag_model(self.cx, self.entity);
249
250        self.cx.emit_custom(
251            Event::new(DragEvent::OnDrop(Box::new(action))).target(self.entity).origin(self.entity),
252        );
253
254        self
255    }
256
257    fn on_drag_view<C: Fn(&mut Context) -> Handle<'_, T> + 'static, T: View>(
258        self,
259        content: C,
260    ) -> Self {
261        build_drag_model(self.cx, self.entity);
262
263        if let Some(abilities) = self.cx.style.abilities.get_mut(self.entity) {
264            abilities.set(Abilities::DRAGGABLE, true);
265        }
266
267        let source_entity = self.entity;
268
269        self.cx.with_current(source_entity, move |cx| {
270            let is_dragging = cx.data::<DragModel>().dragging;
271            let preview_entity: Signal<Option<Entity>> = Signal::new(None);
272            Binding::new(cx, is_dragging, move |cx| {
273                if is_dragging.get() {
274                    // Remove any leftover preview entity from a previous drag.
275                    if let Some(prev) = preview_entity.get() {
276                        cx.remove(prev);
277                    }
278
279                    let window_entity = cx.parent_window();
280                    let drag_entity = cx.with_current(window_entity, |cx| {
281                        (content)(cx)
282                            .position_type(PositionType::Absolute)
283                            .left(Pixels(0.0))
284                            .top(Pixels(0.0))
285                            .display(Display::None)
286                            .pointer_events(PointerEvents::None)
287                            .z_index(10_000)
288                            .entity()
289                    });
290
291                    preview_entity.set(Some(drag_entity));
292
293                    let mut ex = EventContext::new(cx);
294                    ex.set_active_drag_view(Some(drag_entity));
295                } else {
296                    // Drag ended — remove the preview entity and clear the active drag view.
297                    if let Some(entity) = preview_entity.get() {
298                        cx.remove(entity);
299                        preview_entity.set(None);
300                    }
301
302                    let mut ex = EventContext::new(cx);
303                    ex.set_active_drag_view(None);
304                }
305            });
306        });
307
308        self
309    }
310}