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