1use alloc::{collections::btree_map::BTreeMap, vec::Vec};
17#[cfg(feature = "std")]
18use std::sync::atomic::{AtomicU64, Ordering};
19
20use azul_core::{
21 dom::{DomId, NodeId, OptionDomNodeId},
22 drag::{
23 ActiveDragType, AutoScrollDirection, DragContext, DragData, DragEffect, DropEffect,
24 FileDropDrag, NodeDrag, ScrollbarAxis, ScrollbarThumbDrag, TextSelectionDrag,
25 WindowMoveDrag, WindowResizeDrag, WindowResizeEdge,
26 },
27 geom::{LogicalPosition, PhysicalPositionI32},
28 hit_test::HitTest,
29 selection::TextCursor,
30 task::{Duration as CoreDuration, Instant as CoreInstant},
31 window::WindowPosition,
32};
33use azul_css::AzString;
34use azul_css::{impl_option, impl_option_inner, StringVec};
35
36pub use azul_core::drag::{
38 ActiveDragType as DragType, AutoScrollDirection as AutoScroll, DragContext as UnifiedDragContext,
39 DragData as UnifiedDragData, DragEffect as UnifiedDragEffect, DropEffect as UnifiedDropEffect,
40 ScrollbarAxis as ScrollAxis, ScrollbarThumbDrag as ScrollbarDrag,
41};
42
43#[cfg(feature = "std")]
44static NEXT_EVENT_ID: AtomicU64 = AtomicU64::new(1);
45
46#[cfg(feature = "std")]
48pub fn allocate_event_id() -> u64 {
49 NEXT_EVENT_ID.fetch_add(1, Ordering::Relaxed)
50}
51
52#[cfg(not(feature = "std"))]
54pub fn allocate_event_id() -> u64 {
55 0
56}
57
58fn duration_to_millis(duration: CoreDuration) -> u64 {
63 match duration {
64 #[cfg(feature = "std")]
65 CoreDuration::System(system_diff) => {
66 let std_duration: std::time::Duration = system_diff.into();
67 std_duration.as_millis() as u64
68 }
69 #[cfg(not(feature = "std"))]
70 CoreDuration::System(system_diff) => {
71 system_diff.secs * 1000 + (system_diff.nanos / 1_000_000) as u64
73 }
74 CoreDuration::Tick(tick_diff) => {
75 tick_diff.tick_diff
77 }
78 }
79}
80
81pub const MAX_SAMPLES_PER_SESSION: usize = 1000;
86
87pub const DEFAULT_SAMPLE_TIMEOUT_MS: u64 = 2000;
92
93#[derive(Debug, Clone, Copy, PartialEq)]
95pub struct GestureDetectionConfig {
96 pub drag_distance_threshold: f32,
98 pub double_click_time_threshold_ms: u64,
100 pub double_click_distance_threshold: f32,
102 pub long_press_time_threshold_ms: u64,
104 pub long_press_distance_threshold: f32,
106 pub min_samples_for_gesture: usize,
108 pub swipe_velocity_threshold: f32,
110 pub pinch_scale_threshold: f32,
112 pub rotation_angle_threshold: f32,
114 pub sample_cleanup_interval_ms: u64,
116}
117
118impl Default for GestureDetectionConfig {
119 fn default() -> Self {
120 Self {
121 drag_distance_threshold: 5.0,
122 double_click_time_threshold_ms: 500,
123 double_click_distance_threshold: 5.0,
124 long_press_time_threshold_ms: 500,
125 long_press_distance_threshold: 10.0,
126 min_samples_for_gesture: 2,
127 swipe_velocity_threshold: 500.0, pinch_scale_threshold: 0.1, rotation_angle_threshold: 0.1, sample_cleanup_interval_ms: DEFAULT_SAMPLE_TIMEOUT_MS,
131 }
132 }
133}
134
135#[derive(Debug, Clone, PartialEq)]
137pub struct InputSample {
138 pub position: LogicalPosition,
140 pub timestamp: CoreInstant,
142 pub button_state: u8,
144 pub event_id: u64,
146 pub pressure: f32,
148 pub tilt: (f32, f32),
151 pub touch_radius: (f32, f32),
154}
155
156impl_option!(
157 InputSample,
158 OptionInputSample,
159 copy = false,
160 [Debug, Clone, PartialEq]
161);
162
163#[derive(Debug, Clone, PartialEq)]
165pub struct InputSession {
166 pub samples: Vec<InputSample>,
168 pub ended: bool,
170 pub session_id: u64,
172}
173
174impl InputSession {
175 fn new(session_id: u64, first_sample: InputSample) -> Self {
177 Self {
178 samples: vec![first_sample],
179 ended: false,
180 session_id,
181 }
182 }
183
184 pub fn first_sample(&self) -> Option<&InputSample> {
186 self.samples.first()
187 }
188
189 pub fn last_sample(&self) -> Option<&InputSample> {
191 self.samples.last()
192 }
193
194 pub fn duration_ms(&self) -> Option<u64> {
196 let first = self.first_sample()?;
197 let last = self.last_sample()?;
198 let duration = last.timestamp.duration_since(&first.timestamp);
199 Some(duration_to_millis(duration))
200 }
201
202 pub fn total_distance(&self) -> f32 {
204 if self.samples.len() < 2 {
205 return 0.0;
206 }
207
208 let mut total = 0.0;
209 for i in 1..self.samples.len() {
210 let prev = &self.samples[i - 1];
211 let curr = &self.samples[i];
212 let dx = curr.position.x - prev.position.x;
213 let dy = curr.position.y - prev.position.y;
214 total += (dx * dx + dy * dy).sqrt();
215 }
216 total
217 }
218
219 pub fn direct_distance(&self) -> Option<f32> {
221 let first = self.first_sample()?;
222 let last = self.last_sample()?;
223 let dx = last.position.x - first.position.x;
224 let dy = last.position.y - first.position.y;
225 Some((dx * dx + dy * dy).sqrt())
226 }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq)]
231pub struct DetectedDrag {
232 pub start_position: LogicalPosition,
234 pub current_position: LogicalPosition,
236 pub direct_distance: f32,
238 pub total_distance: f32,
240 pub duration_ms: u64,
242 pub sample_count: usize,
244 pub session_id: u64,
246}
247
248#[derive(Debug, Clone, Copy, PartialEq)]
250pub struct DetectedLongPress {
251 pub position: LogicalPosition,
253 pub duration_ms: u64,
255 pub callback_invoked: bool,
257 pub session_id: u64,
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum GestureDirection {
264 Up,
265 Down,
266 Left,
267 Right,
268}
269
270#[derive(Debug, Clone, Copy, PartialEq)]
272pub struct DetectedPinch {
273 pub scale: f32,
275 pub center: LogicalPosition,
277 pub initial_distance: f32,
279 pub current_distance: f32,
281 pub duration_ms: u64,
283}
284
285#[derive(Debug, Clone, Copy, PartialEq)]
287pub struct DetectedRotation {
288 pub angle_radians: f32,
290 pub center: LogicalPosition,
292 pub duration_ms: u64,
294}
295
296pub type NodeDragState = NodeDrag;
303
304pub type WindowDragState = WindowMoveDrag;
307
308pub type FileDropState = FileDropDrag;
311
312#[derive(Debug, Clone, Copy, PartialEq)]
314#[repr(C)]
315pub struct PenState {
316 pub position: LogicalPosition,
318 pub pressure: f32,
320 pub tilt: crate::callbacks::PenTilt,
322 pub in_contact: bool,
324 pub is_eraser: bool,
326 pub barrel_button_pressed: bool,
328 pub device_id: u64,
330}
331
332impl_option!(PenState, OptionPenState, [Debug, Clone, Copy, PartialEq]);
333
334impl Default for PenState {
335 fn default() -> Self {
336 Self {
337 position: LogicalPosition::zero(),
338 pressure: 0.0,
339 tilt: crate::callbacks::PenTilt {
340 x_tilt: 0.0,
341 y_tilt: 0.0,
342 },
343 in_contact: false,
344 is_eraser: false,
345 barrel_button_pressed: false,
346 device_id: 0,
347 }
348 }
349}
350
351#[derive(Debug, Clone, PartialEq)]
365pub struct GestureAndDragManager {
366 pub config: GestureDetectionConfig,
368 pub input_sessions: Vec<InputSession>,
370 pub active_drag: Option<DragContext>,
372 pub pen_state: Option<PenState>,
374 long_press_callbacks_invoked: Vec<u64>,
376 next_session_id: u64,
378}
379
380pub type GestureManager = GestureAndDragManager;
382
383impl Default for GestureAndDragManager {
384 fn default() -> Self {
385 Self::new()
386 }
387}
388
389impl GestureAndDragManager {
390 pub fn new() -> Self {
392 Self {
393 config: GestureDetectionConfig::default(),
394 input_sessions: Vec::new(),
395 next_session_id: 1,
396 active_drag: None,
397 pen_state: None,
398 long_press_callbacks_invoked: Vec::new(),
399 }
400 }
401
402 pub fn with_config(config: GestureDetectionConfig) -> Self {
404 Self {
405 config,
406 ..Self::new()
407 }
408 }
409
410 pub fn start_input_session(
419 &mut self,
420 position: LogicalPosition,
421 timestamp: CoreInstant,
422 button_state: u8,
423 ) -> u64 {
424 self.start_input_session_with_pen(
425 position,
426 timestamp,
427 button_state,
428 allocate_event_id(),
429 0.5, (0.0, 0.0), (0.0, 0.0), )
433 }
434
435 pub fn start_input_session_with_pen(
437 &mut self,
438 position: LogicalPosition,
439 timestamp: CoreInstant,
440 button_state: u8,
441 event_id: u64,
442 pressure: f32,
443 tilt: (f32, f32),
444 touch_radius: (f32, f32),
445 ) -> u64 {
446 self.input_sessions.retain(|session| !session.ended);
451
452 let session_id = self.next_session_id;
453 self.next_session_id += 1;
454
455 let sample = InputSample {
456 position,
457 timestamp,
458 button_state,
459 event_id,
460 pressure,
461 tilt,
462 touch_radius,
463 };
464
465 let session = InputSession::new(session_id, sample);
466 self.input_sessions.push(session);
467
468 session_id
469 }
470
471 pub fn record_input_sample(
478 &mut self,
479 position: LogicalPosition,
480 timestamp: CoreInstant,
481 button_state: u8,
482 ) -> bool {
483 self.record_input_sample_with_pen(
484 position,
485 timestamp,
486 button_state,
487 allocate_event_id(),
488 0.5, (0.0, 0.0), (0.0, 0.0), )
492 }
493
494 pub fn record_input_sample_with_pen(
496 &mut self,
497 position: LogicalPosition,
498 timestamp: CoreInstant,
499 button_state: u8,
500 event_id: u64,
501 pressure: f32,
502 tilt: (f32, f32),
503 touch_radius: (f32, f32),
504 ) -> bool {
505 let session = match self.input_sessions.last_mut() {
506 Some(s) => s,
507 None => return false,
508 };
509
510 if session.ended {
511 return false;
512 }
513
514 if session.samples.len() >= MAX_SAMPLES_PER_SESSION {
516 let remove_count = session.samples.len() - MAX_SAMPLES_PER_SESSION + 100;
518 session.samples.drain(0..remove_count);
519 }
520
521 session.samples.push(InputSample {
522 position,
523 timestamp,
524 button_state,
525 event_id,
526 pressure,
527 tilt,
528 touch_radius,
529 });
530
531 true
532 }
533
534 pub fn end_current_session(&mut self) {
539 if let Some(session) = self.input_sessions.last_mut() {
540 session.ended = true;
541 }
542 }
543
544 pub fn clear_old_sessions(&mut self, current_time: CoreInstant) {
549 self.input_sessions.retain(|session| {
550 if let Some(last_sample) = session.last_sample() {
551 let duration = current_time.duration_since(&last_sample.timestamp);
552 let age_ms = duration_to_millis(duration);
553 age_ms < self.config.sample_cleanup_interval_ms
554 } else {
555 false
556 }
557 });
558
559 let valid_session_ids: Vec<u64> =
561 self.input_sessions.iter().map(|s| s.session_id).collect();
562
563 self.long_press_callbacks_invoked
564 .retain(|id| valid_session_ids.contains(id));
565 }
566
567 pub fn clear_all_sessions(&mut self) {
571 self.input_sessions.clear();
572 self.long_press_callbacks_invoked.clear();
573 }
574
575 pub fn update_pen_state(
579 &mut self,
580 position: LogicalPosition,
581 pressure: f32,
582 tilt: (f32, f32),
583 in_contact: bool,
584 is_eraser: bool,
585 barrel_button_pressed: bool,
586 device_id: u64,
587 ) {
588 self.pen_state = Some(PenState {
589 position,
590 pressure,
591 tilt: crate::callbacks::PenTilt {
592 x_tilt: tilt.0,
593 y_tilt: tilt.1,
594 },
595 in_contact,
596 is_eraser,
597 barrel_button_pressed,
598 device_id,
599 });
600 }
601
602 pub fn clear_pen_state(&mut self) {
604 self.pen_state = None;
605 }
606
607 pub fn get_pen_state(&self) -> Option<&PenState> {
609 self.pen_state.as_ref()
610 }
611
612 pub fn detect_drag(&self) -> Option<DetectedDrag> {
618 let session = self.get_current_session()?;
619
620 if session.samples.len() < self.config.min_samples_for_gesture {
621 return None;
622 }
623
624 let direct_distance = session.direct_distance()?;
625
626 if direct_distance >= self.config.drag_distance_threshold {
627 let first = session.first_sample()?;
628 let last = session.last_sample()?;
629
630 Some(DetectedDrag {
631 start_position: first.position,
632 current_position: last.position,
633 direct_distance,
634 total_distance: session.total_distance(),
635 duration_ms: session.duration_ms()?,
636 sample_count: session.samples.len(),
637 session_id: session.session_id,
638 })
639 } else {
640 None
641 }
642 }
643
644 pub fn detect_long_press(&self) -> Option<DetectedLongPress> {
649 let session = self.get_current_session()?;
650
651 if session.ended {
652 return None; }
654
655 let duration_ms = session.duration_ms()?;
656
657 if duration_ms < self.config.long_press_time_threshold_ms {
658 return None;
659 }
660
661 let distance = session.direct_distance()?;
662
663 if distance <= self.config.long_press_distance_threshold {
664 let first = session.first_sample()?;
665 let callback_invoked = self
666 .long_press_callbacks_invoked
667 .contains(&session.session_id);
668
669 Some(DetectedLongPress {
670 position: first.position,
671 duration_ms,
672 callback_invoked,
673 session_id: session.session_id,
674 })
675 } else {
676 None
677 }
678 }
679
680 pub fn mark_long_press_callback_invoked(&mut self, session_id: u64) {
685 if !self.long_press_callbacks_invoked.contains(&session_id) {
686 self.long_press_callbacks_invoked.push(session_id);
687 }
688 }
689
690 pub fn detect_double_click(&self) -> bool {
694 let sessions = &self.input_sessions;
695 if sessions.len() < 2 {
696 return false;
697 }
698
699 let prev_session = &sessions[sessions.len() - 2];
700 let last_session = &sessions[sessions.len() - 1];
701
702 if !prev_session.ended || !last_session.ended {
704 return false;
705 }
706
707 let prev_first = prev_session.first_sample();
708 let last_first = last_session.first_sample();
709 let (prev_first, last_first) = match (prev_first, last_first) {
710 (Some(p), Some(l)) => (p, l),
711 _ => return false,
712 };
713
714 let duration = last_first.timestamp.duration_since(&prev_first.timestamp);
715 let time_delta_ms = duration_to_millis(duration);
716 if time_delta_ms > self.config.double_click_time_threshold_ms {
717 return false;
718 }
719
720 let dx = last_first.position.x - prev_first.position.x;
721 let dy = last_first.position.y - prev_first.position.y;
722 let distance = (dx * dx + dy * dy).sqrt();
723
724 distance < self.config.double_click_distance_threshold
725 }
726
727 pub fn get_drag_direction(&self) -> Option<GestureDirection> {
729 let session = self.get_current_session()?;
730 let first = session.first_sample()?;
731 let last = session.last_sample()?;
732
733 let dx = last.position.x - first.position.x;
734 let dy = last.position.y - first.position.y;
735
736 let direction = if dx.abs() > dy.abs() {
737 if dx > 0.0 {
738 GestureDirection::Right
739 } else {
740 GestureDirection::Left
741 }
742 } else {
743 if dy > 0.0 {
744 GestureDirection::Down
745 } else {
746 GestureDirection::Up
747 }
748 };
749 Some(direction)
750 }
751
752 pub fn get_gesture_velocity(&self) -> Option<f32> {
754 let session = self.get_current_session()?;
755
756 if session.samples.len() < 2 {
757 return None;
758 }
759
760 let total_distance = session.total_distance();
761 let duration_ms = session.duration_ms()?;
762
763 if duration_ms == 0 {
764 return None;
765 }
766
767 let duration_secs = duration_ms as f32 / 1000.0;
768 Some(total_distance / duration_secs)
769 }
770
771 pub fn is_swipe(&self) -> bool {
773 self.get_gesture_velocity()
774 .map(|v| v >= self.config.swipe_velocity_threshold)
775 .unwrap_or(false)
776 }
777
778 pub fn detect_swipe_direction(&self) -> Option<GestureDirection> {
782 if !self.is_swipe() {
784 return None;
785 }
786
787 self.get_drag_direction()
789 }
790
791 pub fn detect_pinch(&self) -> Option<DetectedPinch> {
796 if self.input_sessions.len() < 2 {
798 return None;
799 }
800
801 let session1 = &self.input_sessions[self.input_sessions.len() - 2];
803 let session2 = &self.input_sessions[self.input_sessions.len() - 1];
804
805 let first1 = session1.first_sample()?;
807 let first2 = session2.first_sample()?;
808 let last1 = session1.last_sample()?;
809 let last2 = session2.last_sample()?;
810
811 let dx_initial = first2.position.x - first1.position.x;
813 let dy_initial = first2.position.y - first1.position.y;
814 let initial_distance = (dx_initial * dx_initial + dy_initial * dy_initial).sqrt();
815
816 let dx_current = last2.position.x - last1.position.x;
818 let dy_current = last2.position.y - last1.position.y;
819 let current_distance = (dx_current * dx_current + dy_current * dy_current).sqrt();
820
821 if initial_distance < 1.0 {
823 return None;
824 }
825
826 let scale = current_distance / initial_distance;
828
829 let scale_threshold = 1.0 + self.config.pinch_scale_threshold;
831 if scale > 1.0 / scale_threshold && scale < scale_threshold {
832 return None; }
834
835 let center = LogicalPosition {
837 x: (last1.position.x + last2.position.x) / 2.0,
838 y: (last1.position.y + last2.position.y) / 2.0,
839 };
840
841 let duration = last1.timestamp.duration_since(&first1.timestamp);
843 let duration_ms = duration_to_millis(duration);
844
845 Some(DetectedPinch {
846 scale,
847 center,
848 initial_distance,
849 current_distance,
850 duration_ms,
851 })
852 }
853
854 pub fn detect_rotation(&self) -> Option<DetectedRotation> {
859 if self.input_sessions.len() < 2 {
861 return None;
862 }
863
864 let session1 = &self.input_sessions[self.input_sessions.len() - 2];
866 let session2 = &self.input_sessions[self.input_sessions.len() - 1];
867
868 let first1 = session1.first_sample()?;
870 let first2 = session2.first_sample()?;
871 let last1 = session1.last_sample()?;
872 let last2 = session2.last_sample()?;
873
874 let center = LogicalPosition {
876 x: (last1.position.x + last2.position.x) / 2.0,
877 y: (last1.position.y + last2.position.y) / 2.0,
878 };
879
880 let dx_initial = first2.position.x - first1.position.x;
882 let dy_initial = first2.position.y - first1.position.y;
883 let initial_angle = dy_initial.atan2(dx_initial);
884
885 let dx_current = last2.position.x - last1.position.x;
887 let dy_current = last2.position.y - last1.position.y;
888 let current_angle = dy_current.atan2(dx_current);
889
890 let mut angle_diff = current_angle - initial_angle;
892
893 const PI: f32 = core::f32::consts::PI;
895 while angle_diff > PI {
896 angle_diff -= 2.0 * PI;
897 }
898 while angle_diff < -PI {
899 angle_diff += 2.0 * PI;
900 }
901
902 if angle_diff.abs() < self.config.rotation_angle_threshold {
904 return None;
905 }
906
907 let duration = last1.timestamp.duration_since(&first1.timestamp);
909 let duration_ms = duration_to_millis(duration);
910
911 Some(DetectedRotation {
912 angle_radians: angle_diff,
913 center,
914 duration_ms,
915 })
916 }
917
918 pub fn get_current_session(&self) -> Option<&InputSession> {
920 self.input_sessions.last()
921 }
922
923 pub fn get_current_mouse_position(&self) -> Option<LogicalPosition> {
925 self.get_current_session()
926 .and_then(|s| s.last_sample())
927 .map(|sample| sample.position)
928 }
929
930 pub fn get_drag_context(&self) -> Option<&DragContext> {
936 self.active_drag.as_ref()
937 }
938
939 pub fn get_drag_context_mut(&mut self) -> Option<&mut DragContext> {
941 self.active_drag.as_mut()
942 }
943
944 pub fn activate_text_selection_drag(
946 &mut self,
947 dom_id: DomId,
948 anchor_ifc_node: NodeId,
949 start_mouse_position: LogicalPosition,
950 ) {
951 let session_id = self.current_session_id().unwrap_or(0);
952 self.active_drag = Some(DragContext::text_selection(
953 dom_id,
954 anchor_ifc_node,
955 start_mouse_position,
956 session_id,
957 ));
958 }
959
960 pub fn activate_scrollbar_drag(
962 &mut self,
963 scroll_container_node: NodeId,
964 axis: ScrollbarAxis,
965 start_mouse_position: LogicalPosition,
966 start_scroll_offset: f32,
967 track_length_px: f32,
968 content_length_px: f32,
969 viewport_length_px: f32,
970 ) {
971 let session_id = self.current_session_id().unwrap_or(0);
972 self.active_drag = Some(DragContext::scrollbar_thumb(
973 scroll_container_node,
974 axis,
975 start_mouse_position,
976 start_scroll_offset,
977 track_length_px,
978 content_length_px,
979 viewport_length_px,
980 session_id,
981 ));
982 }
983
984 pub fn activate_node_drag(
986 &mut self,
987 dom_id: DomId,
988 node_id: NodeId,
989 drag_data: DragData,
990 _start_hit_test: Option<HitTest>,
991 ) {
992 if let Some(detected) = self.detect_drag() {
993 self.active_drag = Some(DragContext::node_drag(
994 dom_id,
995 node_id,
996 detected.start_position,
997 drag_data,
998 detected.session_id,
999 ));
1000 }
1001 }
1002
1003 pub fn activate_window_drag(
1005 &mut self,
1006 initial_window_position: WindowPosition,
1007 _start_hit_test: Option<HitTest>,
1008 ) {
1009 if let Some(detected) = self.detect_drag() {
1010 self.active_drag = Some(DragContext::window_move(
1011 detected.start_position,
1012 initial_window_position,
1013 detected.session_id,
1014 ));
1015 }
1016 }
1017
1018 pub fn start_file_drop(&mut self, files: Vec<AzString>, position: LogicalPosition) {
1020 let session_id = self.current_session_id().unwrap_or(0);
1021 self.active_drag = Some(DragContext::file_drop(files, position, session_id));
1022 }
1023
1024 pub fn update_active_drag_positions(&mut self, position: LogicalPosition) {
1026 if let Some(ref mut drag) = self.active_drag {
1027 drag.update_position(position);
1028 }
1029 }
1030
1031 pub fn update_drop_target(&mut self, target: Option<azul_core::dom::DomNodeId>) {
1033 if let Some(ref mut drag) = self.active_drag {
1034 match &mut drag.drag_type {
1035 ActiveDragType::Node(ref mut node_drag) => {
1036 node_drag.current_drop_target = target.into();
1037 }
1038 ActiveDragType::FileDrop(ref mut file_drop) => {
1039 file_drop.drop_target = target.into();
1040 }
1041 _ => {}
1042 }
1043 }
1044 }
1045
1046 pub fn update_auto_scroll_direction(&mut self, direction: AutoScrollDirection) {
1048 if let Some(ref mut drag) = self.active_drag {
1049 if let Some(text_drag) = drag.as_text_selection_mut() {
1050 text_drag.auto_scroll_direction = direction;
1051 }
1052 }
1053 }
1054
1055 pub fn end_drag(&mut self) -> Option<DragContext> {
1057 self.active_drag.take()
1058 }
1059
1060 pub fn cancel_drag(&mut self) {
1062 if let Some(ref mut drag) = self.active_drag {
1063 drag.cancelled = true;
1064 }
1065 self.active_drag = None;
1066 }
1067
1068 pub fn is_dragging(&self) -> bool {
1074 self.active_drag.is_some()
1075 }
1076
1077 pub fn is_text_selection_dragging(&self) -> bool {
1079 self.active_drag.as_ref().is_some_and(|d| d.is_text_selection())
1080 }
1081
1082 pub fn is_scrollbar_dragging(&self) -> bool {
1084 self.active_drag.as_ref().is_some_and(|d| d.is_scrollbar_thumb())
1085 }
1086
1087 pub fn is_node_dragging_any(&self) -> bool {
1089 self.active_drag.as_ref().is_some_and(|d| d.is_node_drag())
1090 }
1091
1092 pub fn is_node_dragging(&self, dom_id: DomId, node_id: NodeId) -> bool {
1094 self.active_drag.as_ref().is_some_and(|d| {
1095 if let Some(node_drag) = d.as_node_drag() {
1096 node_drag.dom_id == dom_id && node_drag.node_id == node_id
1097 } else {
1098 false
1099 }
1100 })
1101 }
1102
1103 pub fn is_window_dragging(&self) -> bool {
1105 self.active_drag.as_ref().is_some_and(|d| d.is_window_move())
1106 }
1107
1108 pub fn is_file_dropping(&self) -> bool {
1110 self.active_drag.as_ref().is_some_and(|d| d.is_file_drop())
1111 }
1112
1113 pub fn session_count(&self) -> usize {
1115 self.input_sessions.len()
1116 }
1117
1118 pub fn current_session_id(&self) -> Option<u64> {
1120 self.get_current_session().map(|s| s.session_id)
1121 }
1122
1123 pub fn get_node_drag(&self) -> Option<&NodeDrag> {
1130 self.active_drag.as_ref().and_then(|d| d.as_node_drag())
1131 }
1132
1133 pub fn get_window_drag(&self) -> Option<&WindowMoveDrag> {
1136 self.active_drag.as_ref().and_then(|d| d.as_window_move())
1137 }
1138
1139 pub fn get_file_drop(&self) -> Option<&FileDropDrag> {
1142 self.active_drag.as_ref().and_then(|d| d.as_file_drop())
1143 }
1144
1145 pub fn end_node_drag(&mut self) -> Option<DragContext> {
1148 if self.active_drag.as_ref().is_some_and(|d| d.is_node_drag()) {
1149 self.end_drag()
1150 } else {
1151 None
1152 }
1153 }
1154
1155 pub fn end_window_drag(&mut self) -> Option<DragContext> {
1158 if self.active_drag.as_ref().is_some_and(|d| d.is_window_move()) {
1159 self.end_drag()
1160 } else {
1161 None
1162 }
1163 }
1164
1165 pub fn end_file_drop(&mut self) -> Option<DragContext> {
1168 if self.active_drag.as_ref().is_some_and(|d| d.is_file_drop()) {
1169 self.end_drag()
1170 } else {
1171 None
1172 }
1173 }
1174
1175 pub fn cancel_file_drop(&mut self) {
1178 if self.active_drag.as_ref().is_some_and(|d| d.is_file_drop()) {
1179 self.cancel_drag();
1180 }
1181 }
1182
1183 pub fn get_window_drag_delta(&self) -> Option<(i32, i32)> {
1192 let drag = self.active_drag.as_ref()?.as_window_move()?;
1193
1194 let delta_x = drag.current_position.x - drag.start_position.x;
1195 let delta_y = drag.current_position.y - drag.start_position.y;
1196
1197 match drag.initial_window_position {
1198 WindowPosition::Initialized(_initial_pos) => Some((delta_x as i32, delta_y as i32)),
1199 _ => None,
1200 }
1201 }
1202
1203 pub fn get_window_position_from_drag(&self) -> Option<WindowPosition> {
1207 let drag = self.active_drag.as_ref()?.as_window_move()?;
1208
1209 let delta_x = drag.current_position.x - drag.start_position.x;
1210 let delta_y = drag.current_position.y - drag.start_position.y;
1211
1212 match drag.initial_window_position {
1213 WindowPosition::Initialized(initial_pos) => {
1214 Some(WindowPosition::Initialized(PhysicalPositionI32::new(
1215 initial_pos.x + delta_x as i32,
1216 initial_pos.y + delta_y as i32,
1217 )))
1218 }
1219 _ => None,
1220 }
1221 }
1222
1223 pub fn get_scrollbar_scroll_offset(&self) -> Option<f32> {
1225 self.active_drag.as_ref()?.calculate_scrollbar_scroll_offset()
1226 }
1227}