1use std::any::{Any, TypeId};
2use std::fmt;
3use std::rc::Rc;
4
5use sequoia_core::{Point, Rect, Vec2};
6use sequoia_input::Modifiers;
7use sequoia_layout::Axis;
8
9use crate::any::AnyView;
10use crate::overlay::OverlayId;
11use crate::runtime::ElementId;
12
13pub const DRAG_THRESHOLD: f32 = 4.0;
14
15#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
16pub struct DragId(pub u64);
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct DragEnd {
20 pub dropped: bool,
21
22 pub cancelled: bool,
23}
24
25#[derive(Clone)]
26pub struct DragPayload {
27 type_id: TypeId,
28 value: Rc<dyn Any>,
29 type_name: &'static str,
30}
31
32impl DragPayload {
33 pub fn new<T: 'static>(value: T) -> Self {
34 Self {
35 type_id: TypeId::of::<T>(),
36 value: Rc::new(value),
37 type_name: std::any::type_name::<T>(),
38 }
39 }
40
41 pub fn get<T: 'static>(&self) -> Option<&T> {
42 self.value.downcast_ref::<T>()
43 }
44
45 pub fn is<T: 'static>(&self) -> bool {
46 self.type_id == TypeId::of::<T>()
47 }
48
49 pub fn type_id(&self) -> TypeId {
50 self.type_id
51 }
52
53 pub fn type_name(&self) -> &'static str {
54 self.type_name
55 }
56}
57
58impl fmt::Debug for DragPayload {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 f.debug_struct("DragPayload")
61 .field("type", &self.type_name)
62 .finish()
63 }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq)]
67pub struct DropPosition {
68 pub global: Point,
69
70 pub target_rect: Rect,
71
72 pub local: Point,
73
74 pub fraction: Vec2,
75
76 pub modifiers: Modifiers,
77}
78
79impl DropPosition {
80 pub fn in_rect(global: Point, target_rect: Rect, modifiers: Modifiers) -> Self {
81 let local = Point::new(global.x - target_rect.left(), global.y - target_rect.top());
82 let fraction = Vec2::new(
83 if target_rect.size.width > 0.0 {
84 (local.x / target_rect.size.width).clamp(0.0, 1.0)
85 } else {
86 0.0
87 },
88 if target_rect.size.height > 0.0 {
89 (local.y / target_rect.size.height).clamp(0.0, 1.0)
90 } else {
91 0.0
92 },
93 );
94 Self {
95 global,
96 target_rect,
97 local: Point::new(local.x, local.y),
98 fraction,
99 modifiers,
100 }
101 }
102
103 pub fn insertion_index(&self, count: usize, axis: Axis) -> usize {
104 insertion_index(self.global, self.target_rect, count, axis)
105 }
106
107 pub fn before_midpoint(&self, axis: Axis) -> bool {
108 match axis {
109 Axis::Horizontal => self.fraction.x < 0.5,
110 Axis::Vertical => self.fraction.y < 0.5,
111 }
112 }
113}
114
115#[derive(Clone, Debug)]
116pub struct DragSession {
117 pub id: DragId,
118
119 pub payload: DragPayload,
120
121 pub source: Option<ElementId>,
122
123 pub origin: Point,
124
125 pub grab_offset: Vec2,
126
127 pub current: Point,
128
129 pub target: Option<ElementId>,
130
131 pub modifiers: Modifiers,
132
133 pub preview: Option<OverlayId>,
134}
135
136impl DragSession {
137 pub fn position_in(&self, rect: Rect) -> DropPosition {
138 DropPosition::in_rect(self.current, rect, self.modifiers)
139 }
140}
141
142pub struct DragOptions {
143 pub(crate) threshold: f32,
144 pub(crate) preview: Option<AnyView>,
145}
146
147impl Default for DragOptions {
148 fn default() -> Self {
149 Self {
150 threshold: DRAG_THRESHOLD,
151 preview: None,
152 }
153 }
154}
155
156impl DragOptions {
157 pub fn new() -> Self {
158 Self::default()
159 }
160
161 pub fn threshold(mut self, px: f32) -> Self {
162 self.threshold = px.max(0.0);
163 self
164 }
165
166 pub fn preview(mut self, view: impl crate::view::View + 'static) -> Self {
167 self.preview = Some(AnyView::new(view));
168 self
169 }
170}
171
172pub(crate) struct PendingDrag {
173 pub(crate) source: ElementId,
174
175 pub(crate) origin: Point,
176
177 pub(crate) source_rect: Rect,
178 pub(crate) payload: DragPayload,
179 pub(crate) preview: Option<AnyView>,
180 pub(crate) threshold: f32,
181}
182
183pub(crate) struct ActiveDrag {
184 pub(crate) id: DragId,
185 pub(crate) payload: DragPayload,
186
187 pub(crate) source: Option<ElementId>,
188 pub(crate) origin: Point,
189 pub(crate) grab_offset: Vec2,
190 pub(crate) current: Point,
191 pub(crate) modifiers: Modifiers,
192 pub(crate) target: Option<ElementId>,
193 pub(crate) preview: Option<OverlayId>,
194}
195
196impl ActiveDrag {
197 pub(crate) fn snapshot(&self) -> DragSession {
198 DragSession {
199 id: self.id,
200 payload: self.payload.clone(),
201 source: self.source,
202 origin: self.origin,
203 grab_offset: self.grab_offset,
204 current: self.current,
205 target: self.target,
206 modifiers: self.modifiers,
207 preview: self.preview,
208 }
209 }
210}
211
212pub fn exceeded_threshold(origin: Point, current: Point, threshold: f32) -> bool {
213 let d = current - origin;
214 if !d.x.is_finite() || !d.y.is_finite() || !threshold.is_finite() {
215 return false;
216 }
217 d.length() >= threshold.max(0.0)
218}
219
220pub fn grab_offset(origin: Point, source_rect: Rect) -> Vec2 {
221 origin - source_rect.origin
222}
223
224pub fn preview_origin(current: Point, grab: Vec2) -> Point {
225 current - grab
226}
227
228pub fn insertion_index(pos: Point, rect: Rect, count: usize, axis: Axis) -> usize {
229 if count == 0 {
230 return 0;
231 }
232 let (start, extent, p) = match axis {
233 Axis::Horizontal => (rect.left(), rect.size.width, pos.x),
234 Axis::Vertical => (rect.top(), rect.size.height, pos.y),
235 };
236 if !(extent.is_finite() && extent > 0.0) || !p.is_finite() {
237 return 0;
238 }
239 let t = ((p - start) / extent * count as f32).floor() as i64;
240 t.clamp(0, count as i64) as usize
241}
242
243pub fn move_item<T>(items: &mut Vec<T>, from: usize, slot: usize) {
244 if from >= items.len() {
245 return;
246 }
247 let item = items.remove(from);
248
249 let mut to = if slot > from { slot - 1 } else { slot };
250 to = to.min(items.len());
251 items.insert(to, item);
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 fn rect(x: f32, y: f32, w: f32, h: f32) -> Rect {
259 Rect::from_xywh(x, y, w, h)
260 }
261
262 #[test]
263 fn threshold_is_euclidean() {
264 let o = Point::ZERO;
265 assert!(exceeded_threshold(o, Point::new(3.0, 4.0), 5.0));
266 assert!(!exceeded_threshold(o, Point::new(3.0, 4.0), 5.1));
267 assert!(exceeded_threshold(o, Point::new(-4.0, 0.0), 4.0));
268 assert!(!exceeded_threshold(o, Point::new(1.0, 1.0), 4.0));
269 assert!(!exceeded_threshold(o, Point::new(f32::NAN, 0.0), 0.0));
270 }
271
272 #[test]
273 fn grab_keeps_preview_under_cursor() {
274 let src = rect(100.0, 50.0, 60.0, 20.0);
275 let origin = Point::new(115.0, 58.0);
276 let grab = grab_offset(origin, src);
277 assert_eq!(grab, Vec2::new(15.0, 8.0));
278
279 let cur = Point::new(200.0, 100.0);
280 let pv = preview_origin(cur, grab);
281 assert_eq!(pv + grab, cur);
282 }
283
284 #[test]
285 fn drop_position_local_and_fraction() {
286 let r = rect(10.0, 20.0, 100.0, 40.0);
287 let p = DropPosition::in_rect(Point::new(35.0, 30.0), r, Modifiers::NONE);
288 assert_eq!(p.local, Point::new(25.0, 10.0));
289 assert!((p.fraction.x - 0.25).abs() < 1e-6);
290 assert!((p.fraction.y - 0.25).abs() < 1e-6);
291
292 let outside = DropPosition::in_rect(Point::new(500.0, -50.0), r, Modifiers::NONE);
293 assert_eq!(outside.fraction.x, 1.0);
294 assert_eq!(outside.fraction.y, 0.0);
295
296 let zero = DropPosition::in_rect(Point::ZERO, Rect::ZERO, Modifiers::NONE);
297 assert_eq!(zero.fraction, Vec2::ZERO);
298 }
299
300 #[test]
301 fn insertion_slots() {
302 let r = rect(0.0, 0.0, 100.0, 400.0);
303 assert_eq!(
304 insertion_index(Point::new(50.0, -10.0), r, 4, Axis::Vertical),
305 0
306 );
307 assert_eq!(
308 insertion_index(Point::new(50.0, 50.0), r, 4, Axis::Vertical),
309 0
310 );
311 assert_eq!(
312 insertion_index(Point::new(50.0, 150.0), r, 4, Axis::Vertical),
313 1
314 );
315 assert_eq!(
316 insertion_index(Point::new(50.0, 350.0), r, 4, Axis::Vertical),
317 3
318 );
319 assert_eq!(
320 insertion_index(Point::new(50.0, 500.0), r, 4, Axis::Vertical),
321 4
322 );
323 assert_eq!(insertion_index(Point::ZERO, r, 0, Axis::Vertical), 0);
324 let h = rect(0.0, 0.0, 400.0, 20.0);
325 assert_eq!(
326 insertion_index(Point::new(250.0, 10.0), h, 4, Axis::Horizontal),
327 2
328 );
329 }
330
331 #[test]
332 fn move_item_semantics() {
333 let mut v = vec!['A', 'B', 'C', 'D'];
334 move_item(&mut v, 0, 3);
335 assert_eq!(v, vec!['B', 'C', 'A', 'D']);
336 let mut v = vec!['A', 'B', 'C', 'D'];
337 move_item(&mut v, 3, 1);
338 assert_eq!(v, vec!['A', 'D', 'B', 'C']);
339 let mut v = vec!['A', 'B', 'C', 'D'];
340 move_item(&mut v, 1, 1);
341 assert_eq!(v, vec!['A', 'B', 'C', 'D']);
342 let mut v = vec!['A', 'B', 'C', 'D'];
343 move_item(&mut v, 0, 4);
344 assert_eq!(v, vec!['B', 'C', 'D', 'A']);
345 let mut v = vec!['A'];
346 move_item(&mut v, 5, 0);
347 assert_eq!(v, vec!['A']);
348 }
349
350 #[test]
351 fn payload_typed_access() {
352 let p = DragPayload::new(42u32);
353 assert!(p.is::<u32>());
354 assert!(!p.is::<String>());
355 assert_eq!(p.get::<u32>(), Some(&42u32));
356 assert_eq!(p.get::<String>(), None);
357 }
358}