Skip to main content

freya_components/
draggable_canvas.rs

1use freya_core::prelude::*;
2use torin::{
3    prelude::{
4        Area,
5        CursorPoint,
6        Position,
7        Size2D,
8    },
9    size::Size,
10};
11
12#[derive(Clone)]
13struct DraggableCanvasRegistry(State<Vec<usize>>);
14
15/// A canvas container that allows draggable elements within it.
16///
17/// # Example
18///
19/// ```rust
20/// # use freya::prelude::*;
21/// fn app() -> impl IntoElement {
22///     DraggableCanvas::new().child(Draggable::new().child("Draggable item"))
23/// }
24/// ```
25#[derive(PartialEq)]
26pub struct DraggableCanvas {
27    children: Vec<Element>,
28    layout: LayoutData,
29    key: DiffKey,
30}
31
32impl KeyExt for DraggableCanvas {
33    fn write_key(&mut self) -> &mut DiffKey {
34        &mut self.key
35    }
36}
37
38impl Default for DraggableCanvas {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl DraggableCanvas {
45    pub fn new() -> Self {
46        Self {
47            children: vec![],
48            layout: LayoutData::default(),
49            key: DiffKey::None,
50        }
51    }
52}
53
54impl LayoutExt for DraggableCanvas {
55    fn get_layout(&mut self) -> &mut LayoutData {
56        &mut self.layout
57    }
58}
59
60impl ContainerExt for DraggableCanvas {}
61
62impl ChildrenExt for DraggableCanvas {
63    fn get_children(&mut self) -> &mut Vec<Element> {
64        &mut self.children
65    }
66}
67
68impl Component for DraggableCanvas {
69    fn render(&self) -> impl IntoElement {
70        let mut layout = use_state(Area::default);
71        use_provide_context(|| DraggableCanvasRegistry(State::create(Vec::new())));
72        let a11y_id = use_a11y();
73        let mut offset = use_state(CursorPoint::zero);
74        let mut dragging_position = use_state::<Option<CursorPoint>>(|| None);
75
76        let on_mouse_move = move |e: Event<MouseEventData>| {
77            if let Some(dragging_position) = dragging_position() {
78                offset.set((e.element_location - dragging_position).to_point());
79                e.stop_propagation();
80            }
81        };
82
83        let on_pointer_down = move |e: Event<PointerEventData>| {
84            if !e.data().is_primary() {
85                return;
86            }
87            dragging_position.set(Some((offset() - e.element_location()).to_point()));
88            e.stop_propagation();
89        };
90
91        let on_global_pointer_press = move |e: Event<PointerEventData>| {
92            if dragging_position.read().is_some() {
93                e.stop_propagation();
94                e.prevent_default();
95                dragging_position.set(None);
96            }
97        };
98
99        let on_wheel = move |e: Event<WheelEventData>| {
100            let mut current_offset = offset.write();
101            current_offset.x += e.delta_x;
102            current_offset.y += e.delta_y;
103        };
104
105        let (offset_x, offset_y) = offset().to_tuple();
106
107        rect()
108            .layout(self.layout.clone())
109            .on_sized(move |e: Event<SizedEventData>| layout.set(e.visible_area))
110            .on_mouse_move(on_mouse_move)
111            .on_pointer_down(on_pointer_down)
112            .on_global_pointer_press(on_global_pointer_press)
113            .on_wheel(on_wheel)
114            .offset_x(offset_x as f32)
115            .offset_y(offset_y as f32)
116            .a11y_id(a11y_id)
117            .a11y_role(AccessibilityRole::ScrollView)
118            .a11y_builder(move |node| {
119                node.set_scroll_x(offset_x);
120                node.set_scroll_y(offset_y)
121            })
122            .scrollable(true)
123            .overflow(Overflow::Clip)
124            .children(self.children.clone())
125    }
126    fn render_key(&self) -> DiffKey {
127        self.key.clone().or(self.default_key())
128    }
129}
130
131#[derive(PartialEq)]
132pub struct Draggable {
133    initial_position: CursorPoint,
134    children: Vec<Element>,
135    key: DiffKey,
136}
137
138impl Default for Draggable {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144impl Draggable {
145    pub fn new() -> Self {
146        Self {
147            initial_position: CursorPoint::zero(),
148            children: vec![],
149            key: DiffKey::None,
150        }
151    }
152
153    pub fn initial_position(mut self, initial_position: impl Into<CursorPoint>) -> Self {
154        self.initial_position = initial_position.into();
155        self
156    }
157}
158
159impl KeyExt for Draggable {
160    fn write_key(&mut self) -> &mut DiffKey {
161        &mut self.key
162    }
163}
164
165impl ChildrenExt for Draggable {
166    fn get_children(&mut self) -> &mut Vec<Element> {
167        &mut self.children
168    }
169}
170
171impl Component for Draggable {
172    fn render(&self) -> impl IntoElement {
173        let mut position = use_state(|| self.initial_position);
174        let mut dragging_position = use_state::<Option<CursorPoint>>(|| None);
175        let DraggableCanvasRegistry(mut registry) = use_consume::<DraggableCanvasRegistry>();
176        let id = use_id::<DraggableCanvas>();
177
178        use_hook(move || {
179            registry.write().push(id);
180        });
181
182        use_drop(move || {
183            registry.write().retain(|i| *i != id);
184        });
185
186        let on_global_pointer_move = move |e: Event<PointerEventData>| {
187            if let Some(dragging_position) = dragging_position() {
188                position.set((e.global_location() - dragging_position).to_point());
189                e.stop_propagation();
190            }
191        };
192
193        let on_pointer_down = move |e: Event<PointerEventData>| {
194            if !e.data().is_primary() {
195                return;
196            }
197            dragging_position.set(Some((e.global_location() - position()).to_point()));
198            e.stop_propagation();
199            let mut registry = registry.write();
200            registry.retain(|i| *i != id);
201            registry.insert(0, id);
202        };
203
204        let on_capture_global_pointer_press = move |e: Event<PointerEventData>| {
205            if dragging_position.read().is_some() {
206                e.stop_propagation();
207                e.prevent_default();
208                dragging_position.set(None);
209            }
210        };
211
212        let (left, top) = position().to_f32().to_tuple();
213
214        let layer = registry
215            .read()
216            .iter()
217            .rev()
218            .position(|i| *i == id)
219            .map(|layer| layer * 1024)
220            .unwrap_or_default();
221
222        rect()
223            .on_global_pointer_move(on_global_pointer_move)
224            .on_pointer_down(on_pointer_down)
225            .on_capture_global_pointer_press(on_capture_global_pointer_press)
226            .position(Position::new_absolute().left(left).top(top))
227            .layer(layer as i16)
228            .children(self.children.clone())
229    }
230
231    fn render_key(&self) -> DiffKey {
232        self.key.clone().or(self.default_key())
233    }
234}
235
236/// Which edge/corner is currently being resized.
237#[derive(Clone, Copy, PartialEq, Debug)]
238enum ResizeEdge {
239    Right,
240    Bottom,
241    BottomRight,
242}
243
244#[derive(PartialEq)]
245pub struct ResizableDraggable {
246    initial_position: CursorPoint,
247    initial_size: Size2D,
248    handle_size: f32,
249    corner_size: f32,
250    children: Vec<Element>,
251    key: DiffKey,
252}
253
254impl ResizableDraggable {
255    pub fn new(initial_size: impl Into<Size2D>) -> Self {
256        Self {
257            initial_position: CursorPoint::zero(),
258            initial_size: initial_size.into(),
259            handle_size: 4.,
260            corner_size: 12.,
261            children: vec![],
262            key: DiffKey::None,
263        }
264    }
265
266    pub fn initial_position(mut self, initial_position: impl Into<CursorPoint>) -> Self {
267        self.initial_position = initial_position.into();
268        self
269    }
270}
271
272impl KeyExt for ResizableDraggable {
273    fn write_key(&mut self) -> &mut DiffKey {
274        &mut self.key
275    }
276}
277
278impl ChildrenExt for ResizableDraggable {
279    fn get_children(&mut self) -> &mut Vec<Element> {
280        &mut self.children
281    }
282}
283
284impl Component for ResizableDraggable {
285    fn render(&self) -> impl IntoElement {
286        let mut position = use_state(|| self.initial_position);
287        let mut size = use_state(|| self.initial_size);
288        let mut dragging_position = use_state::<Option<CursorPoint>>(|| None);
289        let mut resizing = use_state::<Option<(ResizeEdge, CursorPoint)>>(|| None);
290        let DraggableCanvasRegistry(mut registry) = use_consume::<DraggableCanvasRegistry>();
291        let id = use_id::<DraggableCanvas>();
292
293        use_hook(move || {
294            registry.write().push(id);
295        });
296
297        use_drop(move || {
298            registry.write().retain(|i| *i != id);
299        });
300
301        let on_global_pointer_move = move |e: Event<PointerEventData>| {
302            if let Some(dragging_position) = dragging_position() {
303                position.set((e.global_location() - dragging_position).to_point());
304                e.stop_propagation();
305            }
306            if let Some((edge, start_point)) = resizing() {
307                // For now hardcoded, but I could probably just make it customizable
308                const MIN: f32 = 20.;
309
310                let delta = (e.global_location() - start_point).to_f32().to_point();
311                let (current_width, current_height) = size().to_tuple();
312                let new_width = if matches!(edge, ResizeEdge::Right | ResizeEdge::BottomRight) {
313                    (current_width + delta.x).max(MIN)
314                } else {
315                    current_width
316                };
317                let new_height = if matches!(edge, ResizeEdge::Bottom | ResizeEdge::BottomRight) {
318                    (current_height + delta.y).max(MIN)
319                } else {
320                    current_height
321                };
322                size.set((new_width, new_height).into());
323                resizing.set(Some((edge, e.global_location())));
324                e.stop_propagation();
325            }
326        };
327
328        let on_pointer_down = move |e: Event<PointerEventData>| {
329            if !e.data().is_primary() {
330                return;
331            }
332            // Don't start dragging if a resize was just initiated on a handle
333            if resizing.read().is_some() {
334                return;
335            }
336            dragging_position.set(Some((e.global_location() - position()).to_point()));
337            e.stop_propagation();
338            let mut registry_write = registry.write();
339            registry_write.retain(|i| *i != id);
340            registry_write.insert(0, id);
341        };
342
343        let on_capture_global_pointer_press = move |e: Event<PointerEventData>| {
344            if dragging_position.read().is_some() {
345                e.stop_propagation();
346                e.prevent_default();
347                dragging_position.set(None);
348            }
349            if resizing.read().is_some() {
350                e.stop_propagation();
351                e.prevent_default();
352                resizing.set(None);
353                Cursor::set(CursorIcon::default());
354            }
355        };
356
357        let (left, top) = position().to_f32().to_tuple();
358        let (width, height) = size().to_tuple();
359
360        let layer = registry
361            .read()
362            .iter()
363            .rev()
364            .position(|i| *i == id)
365            .map(|layer| layer * 1024)
366            .unwrap_or_default();
367
368        let handle = self.handle_size;
369        let corner = self.corner_size;
370
371        let right_handle = rect()
372            .width(Size::px(handle))
373            .height(Size::px(height - corner))
374            .position(Position::new_absolute().right(-handle).top(0.))
375            .background(Color::WHITE)
376            .opacity(0.)
377            .on_pointer_enter(move |_: Event<PointerEventData>| {
378                Cursor::set(CursorIcon::ColResize);
379            })
380            .on_pointer_leave(move |_: Event<PointerEventData>| {
381                if resizing().is_none() {
382                    Cursor::set(CursorIcon::default());
383                }
384            })
385            .on_pointer_down(move |e: Event<PointerEventData>| {
386                if !e.data().is_primary() {
387                    return;
388                }
389                e.stop_propagation();
390                resizing.set(Some((ResizeEdge::Right, e.global_location())));
391                let mut registry = registry.write();
392                registry.retain(|i| *i != id);
393                registry.insert(0, id);
394            });
395
396        let bottom_handle = rect()
397            .width(Size::px(width - corner))
398            .height(Size::px(handle))
399            .position(Position::new_absolute().left(0.).bottom(-handle))
400            .background(Color::WHITE)
401            .opacity(0.)
402            .on_pointer_enter(move |_: Event<PointerEventData>| {
403                Cursor::set(CursorIcon::RowResize);
404            })
405            .on_pointer_leave(move |_: Event<PointerEventData>| {
406                if resizing().is_none() {
407                    Cursor::set(CursorIcon::default());
408                }
409            })
410            .on_pointer_down(move |e: Event<PointerEventData>| {
411                if !e.data().is_primary() {
412                    return;
413                }
414                e.stop_propagation();
415                resizing.set(Some((ResizeEdge::Bottom, e.global_location())));
416                let mut registry = registry.write();
417                registry.retain(|i| *i != id);
418                registry.insert(0, id);
419            });
420
421        let corner_handle = rect()
422            .width(Size::px(corner))
423            .height(Size::px(corner))
424            .position(Position::new_absolute().right(-handle).bottom(-handle))
425            .background(Color::WHITE)
426            .opacity(0.)
427            .on_pointer_enter(move |_: Event<PointerEventData>| {
428                Cursor::set(CursorIcon::SeResize);
429            })
430            .on_pointer_leave(move |_: Event<PointerEventData>| {
431                if resizing().is_none() {
432                    Cursor::set(CursorIcon::default());
433                }
434            })
435            .on_pointer_down(move |e: Event<PointerEventData>| {
436                if !e.data().is_primary() {
437                    return;
438                }
439                e.stop_propagation();
440                resizing.set(Some((ResizeEdge::BottomRight, e.global_location())));
441                let mut registry = registry.write();
442                registry.retain(|i| *i != id);
443                registry.insert(0, id);
444            });
445
446        rect()
447            .on_global_pointer_move(on_global_pointer_move)
448            .on_pointer_down(on_pointer_down)
449            .on_capture_global_pointer_press(on_capture_global_pointer_press)
450            .position(Position::new_absolute().left(left).top(top))
451            .width(Size::px(width))
452            .height(Size::px(height))
453            .layer(layer as i16)
454            .child(
455                rect()
456                    .overflow(Overflow::Clip)
457                    .children(self.children.clone()),
458            )
459            .child(right_handle)
460            .child(bottom_handle)
461            .child(corner_handle)
462    }
463
464    fn render_key(&self) -> DiffKey {
465        self.key.clone().or(self.default_key())
466    }
467}