freya_components/
drag_drop.rs1use freya_core::{
2 prelude::*,
3 scope_id::ScopeId,
4};
5use torin::prelude::*;
6
7#[derive(Clone, Copy)]
8enum DragPhase {
9 Idle,
10 Pressing {
11 press_point: CursorPoint,
12 offset: CursorPoint,
13 },
14 Dragging {
15 position: CursorPoint,
16 offset: CursorPoint,
17 },
18}
19
20pub fn use_drag<T: 'static>() -> State<Option<T>> {
26 match try_consume_root_context() {
27 Some(s) => s,
28 None => {
29 let state = State::<Option<T>>::create_in_scope(None, ScopeId::ROOT);
30 provide_context_for_scope_id(state, ScopeId::ROOT);
31 state
32 }
33 }
34}
35
36#[derive(Clone, PartialEq)]
47pub struct DragZone<T: Clone + 'static + PartialEq> {
48 children: Vec<Element>,
49 drag_element: Option<Element>,
50 data: T,
51 show_while_dragging: bool,
52 drag_threshold: f64,
53 enabled: bool,
54 layout: LayoutData,
55 key: DiffKey,
56}
57
58impl<T: Clone + PartialEq + 'static> ChildrenExt for DragZone<T> {
59 fn get_children(&mut self) -> &mut Vec<Element> {
60 &mut self.children
61 }
62}
63
64impl<T: Clone + PartialEq + 'static> KeyExt for DragZone<T> {
65 fn write_key(&mut self) -> &mut DiffKey {
66 &mut self.key
67 }
68}
69
70impl<T: Clone + PartialEq + 'static> LayoutExt for DragZone<T> {
71 fn get_layout(&mut self) -> &mut LayoutData {
72 &mut self.layout
73 }
74}
75
76impl<T: Clone + PartialEq + 'static> ContainerExt for DragZone<T> {}
77
78impl<T: Clone + PartialEq + 'static> DragZone<T> {
79 pub fn new(data: T) -> Self {
81 Self {
82 data,
83 children: Vec::new(),
84 drag_element: None,
85 show_while_dragging: true,
86 drag_threshold: 4.0,
87 enabled: true,
88 layout: LayoutData::default(),
89 key: DiffKey::default(),
90 }
91 }
92
93 pub fn show_while_dragging(mut self, show_while_dragging: bool) -> Self {
95 self.show_while_dragging = show_while_dragging;
96 self
97 }
98
99 pub fn drag_element(mut self, drag_element: impl Into<Element>) -> Self {
101 self.drag_element = Some(drag_element.into());
102 self
103 }
104
105 pub fn drag_threshold(mut self, drag_threshold: f64) -> Self {
107 self.drag_threshold = drag_threshold;
108 self
109 }
110
111 pub fn enabled(mut self, enabled: bool) -> Self {
113 self.enabled = enabled;
114 self
115 }
116}
117
118impl<T: Clone + PartialEq> Component for DragZone<T> {
119 fn render(&self) -> impl IntoElement {
120 let mut drags = use_drag::<T>();
121 let mut phase = use_state(|| DragPhase::Idle);
122 let mut drag_element_size = use_state(|| None::<Size2D>);
123 let data = self.data.clone();
124 let drag_threshold = self.drag_threshold;
125
126 let on_global_pointer_move = move |e: Event<PointerEventData>| match phase() {
127 DragPhase::Dragging { offset, .. } => {
128 phase.set(DragPhase::Dragging {
129 position: e.global_location(),
130 offset,
131 });
132 }
133 DragPhase::Pressing {
134 press_point,
135 offset,
136 } => {
137 let current = e.global_location();
138 let dx = current.x - press_point.x;
139 let dy = current.y - press_point.y;
140
141 if (dx * dx + dy * dy).sqrt() >= drag_threshold {
142 phase.set(DragPhase::Dragging {
143 position: current,
144 offset,
145 });
146 *drags.write() = Some(data.clone());
147 }
148 }
149 DragPhase::Idle => {}
150 };
151
152 let on_pointer_down = move |e: Event<PointerEventData>| {
153 if e.data().button() != Some(MouseButton::Left) {
154 return;
155 }
156 phase.set(DragPhase::Pressing {
157 press_point: e.global_location(),
158 offset: e.element_location(),
159 });
160 };
161
162 let on_global_pointer_press = move |_: Event<PointerEventData>| {
163 if !matches!(phase(), DragPhase::Idle) {
164 phase.set(DragPhase::Idle);
165 *drags.write() = None;
166 }
167 };
168
169 let dragging = match phase() {
170 DragPhase::Dragging { position, offset } => Some((position, offset)),
171 _ => None,
172 };
173
174 rect()
175 .layout(self.layout.clone())
176 .on_global_pointer_press(on_global_pointer_press)
177 .on_global_pointer_move(on_global_pointer_move)
178 .maybe(self.enabled, |el| el.on_pointer_down(on_pointer_down))
179 .maybe_child((dragging.zip(self.drag_element.clone())).map(
180 |((position, offset), drag_element)| {
181 let size = *drag_element_size.read();
182 let anchor = size.map_or(offset, |size| {
183 offset.min(CursorPoint::new(size.width as f64, size.height as f64))
184 });
185 let (x, y) = (position - anchor).to_f32().to_tuple();
186 rect()
187 .position(Position::new_global())
188 .layer(Layer::Overlay)
189 .interactive(false)
190 .opacity(if size.is_some() { 1. } else { 0. })
191 .offset_x(x + 1.)
193 .offset_y(y + 1.)
194 .on_sized(move |e: Event<SizedEventData>| {
195 drag_element_size.set_if_modified(Some(e.area.size))
196 })
197 .child(drag_element)
198 },
199 ))
200 .maybe(self.show_while_dragging || dragging.is_none(), |el| {
201 el.children(self.children.clone())
202 })
203 }
204
205 fn render_key(&self) -> DiffKey {
206 self.key.clone().or(self.default_key())
207 }
208}
209
210#[derive(PartialEq, Clone)]
221pub struct DropZone<T: 'static + PartialEq + Clone> {
222 children: Vec<Element>,
223 on_drop: EventHandler<T>,
224 on_drag_over: Option<EventHandler<bool>>,
225 layout: LayoutData,
226 key: DiffKey,
227}
228
229impl<T: Clone + PartialEq + 'static> ChildrenExt for DropZone<T> {
230 fn get_children(&mut self) -> &mut Vec<Element> {
231 &mut self.children
232 }
233}
234
235impl<T: Clone + PartialEq + 'static> KeyExt for DropZone<T> {
236 fn write_key(&mut self) -> &mut DiffKey {
237 &mut self.key
238 }
239}
240
241impl<T: Clone + PartialEq + 'static> LayoutExt for DropZone<T> {
242 fn get_layout(&mut self) -> &mut LayoutData {
243 &mut self.layout
244 }
245}
246
247impl<T: Clone + PartialEq + 'static> ContainerExt for DropZone<T> {}
248
249impl<T: PartialEq + Clone + 'static> DropZone<T> {
250 pub fn new(on_drop: impl Into<EventHandler<T>>) -> Self {
252 Self {
253 children: Vec::new(),
254 on_drop: on_drop.into(),
255 on_drag_over: None,
256 layout: LayoutData::default(),
257 key: DiffKey::default(),
258 }
259 }
260
261 pub fn on_drag_over(mut self, on_drag_over: impl Into<EventHandler<bool>>) -> Self {
264 self.on_drag_over = Some(on_drag_over.into());
265 self
266 }
267}
268
269impl<T: Clone + PartialEq + 'static> Component for DropZone<T> {
270 fn render(&self) -> impl IntoElement {
271 let mut drags = use_drag::<T>();
272 let on_drop = self.on_drop.clone();
273 let on_drag_over = self.on_drag_over.clone();
274
275 let on_mouse_up = {
276 let on_drag_over = on_drag_over.clone();
277 move |e: Event<MouseEventData>| {
278 e.stop_propagation();
279 let payload = (*drags.read()).clone();
280 if let Some(payload) = payload {
281 on_drop.call(payload);
282 *drags.write() = None;
283 if let Some(on_drag_over) = &on_drag_over {
284 on_drag_over.call(false);
285 }
286 }
287 }
288 };
289
290 rect()
291 .layout(self.layout.clone())
292 .on_mouse_up(on_mouse_up)
293 .map(on_drag_over, move |el, on_drag_over| {
294 el.on_pointer_enter({
295 let on_drag_over = on_drag_over.clone();
296 move |_| {
297 if drags.read().is_some() {
298 on_drag_over.call(true);
299 }
300 }
301 })
302 .on_pointer_leave(move |_| {
303 if drags.read().is_some() {
304 on_drag_over.call(false);
305 }
306 })
307 })
308 .children(self.children.clone())
309 }
310
311 fn render_key(&self) -> DiffKey {
312 self.key.clone().or(self.default_key())
313 }
314}