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 screen_position: LogicalPosition,
149 pub timestamp: CoreInstant,
151 pub button_state: u8,
153 pub event_id: u64,
155 pub pressure: f32,
157 pub tilt: (f32, f32),
160 pub touch_radius: (f32, f32),
163}
164
165impl_option!(
166 InputSample,
167 OptionInputSample,
168 copy = false,
169 [Debug, Clone, PartialEq]
170);
171
172#[derive(Debug, Clone, PartialEq)]
174pub struct InputSession {
175 pub samples: Vec<InputSample>,
177 pub ended: bool,
179 pub session_id: u64,
181 pub window_position_at_start: azul_core::window::WindowPosition,
184}
185
186impl InputSession {
187 fn new(session_id: u64, first_sample: InputSample, window_position: azul_core::window::WindowPosition) -> Self {
189 Self {
190 samples: vec![first_sample],
191 ended: false,
192 session_id,
193 window_position_at_start: window_position,
194 }
195 }
196
197 pub fn first_sample(&self) -> Option<&InputSample> {
199 self.samples.first()
200 }
201
202 pub fn last_sample(&self) -> Option<&InputSample> {
204 self.samples.last()
205 }
206
207 pub fn duration_ms(&self) -> Option<u64> {
209 let first = self.first_sample()?;
210 let last = self.last_sample()?;
211 let duration = last.timestamp.duration_since(&first.timestamp);
212 Some(duration_to_millis(duration))
213 }
214
215 pub fn total_distance(&self) -> f32 {
217 if self.samples.len() < 2 {
218 return 0.0;
219 }
220
221 let mut total = 0.0;
222 for i in 1..self.samples.len() {
223 let prev = &self.samples[i - 1];
224 let curr = &self.samples[i];
225 let dx = curr.position.x - prev.position.x;
226 let dy = curr.position.y - prev.position.y;
227 total += (dx * dx + dy * dy).sqrt();
228 }
229 total
230 }
231
232 pub fn direct_distance(&self) -> Option<f32> {
234 let first = self.first_sample()?;
235 let last = self.last_sample()?;
236 let dx = last.position.x - first.position.x;
237 let dy = last.position.y - first.position.y;
238 Some((dx * dx + dy * dy).sqrt())
239 }
240}
241
242#[derive(Debug, Clone, Copy, PartialEq)]
244pub struct DetectedDrag {
245 pub start_position: LogicalPosition,
247 pub current_position: LogicalPosition,
249 pub direct_distance: f32,
251 pub total_distance: f32,
253 pub duration_ms: u64,
255 pub sample_count: usize,
257 pub session_id: u64,
259}
260
261#[derive(Debug, Clone, Copy, PartialEq)]
263pub struct DetectedLongPress {
264 pub position: LogicalPosition,
266 pub duration_ms: u64,
268 pub callback_invoked: bool,
270 pub session_id: u64,
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum GestureDirection {
277 Up,
278 Down,
279 Left,
280 Right,
281}
282
283#[derive(Debug, Clone, Copy, PartialEq)]
285pub struct DetectedPinch {
286 pub scale: f32,
288 pub center: LogicalPosition,
290 pub initial_distance: f32,
292 pub current_distance: f32,
294 pub duration_ms: u64,
296}
297
298#[derive(Debug, Clone, Copy, PartialEq)]
300pub struct DetectedRotation {
301 pub angle_radians: f32,
303 pub center: LogicalPosition,
305 pub duration_ms: u64,
307}
308
309pub type NodeDragState = NodeDrag;
316
317pub type WindowDragState = WindowMoveDrag;
320
321pub type FileDropState = FileDropDrag;
324
325#[derive(Debug, Clone, Copy, PartialEq)]
327#[repr(C)]
328pub struct PenState {
329 pub position: LogicalPosition,
331 pub pressure: f32,
333 pub tilt: crate::callbacks::PenTilt,
335 pub in_contact: bool,
337 pub is_eraser: bool,
339 pub barrel_button_pressed: bool,
341 pub device_id: u64,
343}
344
345impl_option!(PenState, OptionPenState, [Debug, Clone, Copy, PartialEq]);
346
347impl Default for PenState {
348 fn default() -> Self {
349 Self {
350 position: LogicalPosition::zero(),
351 pressure: 0.0,
352 tilt: crate::callbacks::PenTilt {
353 x_tilt: 0.0,
354 y_tilt: 0.0,
355 },
356 in_contact: false,
357 is_eraser: false,
358 barrel_button_pressed: false,
359 device_id: 0,
360 }
361 }
362}
363
364#[derive(Debug, Clone, PartialEq)]
378pub struct GestureAndDragManager {
379 pub config: GestureDetectionConfig,
381 pub input_sessions: Vec<InputSession>,
383 pub active_drag: Option<DragContext>,
385 pub pen_state: Option<PenState>,
387 long_press_callbacks_invoked: Vec<u64>,
389 next_session_id: u64,
391}
392
393pub type GestureManager = GestureAndDragManager;
395
396impl Default for GestureAndDragManager {
397 fn default() -> Self {
398 Self::new()
399 }
400}
401
402impl GestureAndDragManager {
403 pub fn new() -> Self {
405 Self {
406 config: GestureDetectionConfig::default(),
407 input_sessions: Vec::new(),
408 next_session_id: 1,
409 active_drag: None,
410 pen_state: None,
411 long_press_callbacks_invoked: Vec::new(),
412 }
413 }
414
415 pub fn with_config(config: GestureDetectionConfig) -> Self {
417 Self {
418 config,
419 ..Self::new()
420 }
421 }
422
423 pub fn start_input_session(
435 &mut self,
436 position: LogicalPosition,
437 timestamp: CoreInstant,
438 button_state: u8,
439 window_position: azul_core::window::WindowPosition,
440 screen_position: LogicalPosition,
441 ) -> u64 {
442 self.start_input_session_with_pen(
443 position,
444 timestamp,
445 button_state,
446 allocate_event_id(),
447 0.5, (0.0, 0.0), (0.0, 0.0), window_position,
451 screen_position,
452 )
453 }
454
455 pub fn start_input_session_with_pen(
457 &mut self,
458 position: LogicalPosition,
459 timestamp: CoreInstant,
460 button_state: u8,
461 event_id: u64,
462 pressure: f32,
463 tilt: (f32, f32),
464 touch_radius: (f32, f32),
465 window_position: azul_core::window::WindowPosition,
466 screen_position: LogicalPosition,
467 ) -> u64 {
468 let last_ended_idx = self.input_sessions.iter().rposition(|s| s.ended);
472 let mut idx = 0usize;
473 self.input_sessions.retain(|session| {
474 let keep = !session.ended || Some(idx) == last_ended_idx;
475 idx += 1;
476 keep
477 });
478
479 let session_id = self.next_session_id;
480 self.next_session_id += 1;
481
482 let sample = InputSample {
483 position,
484 screen_position,
485 timestamp,
486 button_state,
487 event_id,
488 pressure,
489 tilt,
490 touch_radius,
491 };
492
493 let session = InputSession::new(session_id, sample, window_position);
494 self.input_sessions.push(session);
495
496 session_id
497 }
498
499 pub fn record_input_sample(
506 &mut self,
507 position: LogicalPosition,
508 timestamp: CoreInstant,
509 button_state: u8,
510 screen_position: LogicalPosition,
511 ) -> bool {
512 self.record_input_sample_with_pen(
513 position,
514 timestamp,
515 button_state,
516 allocate_event_id(),
517 0.5, (0.0, 0.0), (0.0, 0.0), screen_position,
521 )
522 }
523
524 pub fn record_input_sample_with_pen(
526 &mut self,
527 position: LogicalPosition,
528 timestamp: CoreInstant,
529 button_state: u8,
530 event_id: u64,
531 pressure: f32,
532 tilt: (f32, f32),
533 touch_radius: (f32, f32),
534 screen_position: LogicalPosition,
535 ) -> bool {
536 let session = match self.input_sessions.last_mut() {
537 Some(s) => s,
538 None => return false,
539 };
540
541 if session.ended {
542 return false;
543 }
544
545 if session.samples.len() >= MAX_SAMPLES_PER_SESSION {
547 let remove_count = session.samples.len() - MAX_SAMPLES_PER_SESSION + 100;
549 session.samples.drain(0..remove_count);
550 }
551
552 session.samples.push(InputSample {
553 position,
554 screen_position,
555 timestamp,
556 button_state,
557 event_id,
558 pressure,
559 tilt,
560 touch_radius,
561 });
562
563 true
564 }
565
566 pub fn end_current_session(&mut self) {
571 if let Some(session) = self.input_sessions.last_mut() {
572 session.ended = true;
573 }
574 }
575
576 pub fn clear_old_sessions(&mut self, current_time: CoreInstant) {
581 self.input_sessions.retain(|session| {
582 if let Some(last_sample) = session.last_sample() {
583 let duration = current_time.duration_since(&last_sample.timestamp);
584 let age_ms = duration_to_millis(duration);
585 age_ms < self.config.sample_cleanup_interval_ms
586 } else {
587 false
588 }
589 });
590
591 let valid_session_ids: Vec<u64> =
593 self.input_sessions.iter().map(|s| s.session_id).collect();
594
595 self.long_press_callbacks_invoked
596 .retain(|id| valid_session_ids.contains(id));
597 }
598
599 pub fn clear_all_sessions(&mut self) {
603 self.input_sessions.clear();
604 self.long_press_callbacks_invoked.clear();
605 }
606
607 pub fn update_pen_state(
611 &mut self,
612 position: LogicalPosition,
613 pressure: f32,
614 tilt: (f32, f32),
615 in_contact: bool,
616 is_eraser: bool,
617 barrel_button_pressed: bool,
618 device_id: u64,
619 ) {
620 self.pen_state = Some(PenState {
621 position,
622 pressure,
623 tilt: crate::callbacks::PenTilt {
624 x_tilt: tilt.0,
625 y_tilt: tilt.1,
626 },
627 in_contact,
628 is_eraser,
629 barrel_button_pressed,
630 device_id,
631 });
632 }
633
634 pub fn clear_pen_state(&mut self) {
636 self.pen_state = None;
637 }
638
639 pub fn get_pen_state(&self) -> Option<&PenState> {
641 self.pen_state.as_ref()
642 }
643
644 pub fn detect_drag(&self) -> Option<DetectedDrag> {
650 let session = self.get_current_session()?;
651
652 if session.samples.len() < self.config.min_samples_for_gesture {
653 return None;
654 }
655
656 let direct_distance = session.direct_distance()?;
657
658 if direct_distance >= self.config.drag_distance_threshold {
659 let first = session.first_sample()?;
660 let last = session.last_sample()?;
661
662 Some(DetectedDrag {
663 start_position: first.position,
664 current_position: last.position,
665 direct_distance,
666 total_distance: session.total_distance(),
667 duration_ms: session.duration_ms()?,
668 sample_count: session.samples.len(),
669 session_id: session.session_id,
670 })
671 } else {
672 None
673 }
674 }
675
676 pub fn detect_long_press(&self) -> Option<DetectedLongPress> {
681 let session = self.get_current_session()?;
682
683 if session.ended {
684 return None; }
686
687 let duration_ms = session.duration_ms()?;
688
689 if duration_ms < self.config.long_press_time_threshold_ms {
690 return None;
691 }
692
693 let distance = session.direct_distance()?;
694
695 if distance <= self.config.long_press_distance_threshold {
696 let first = session.first_sample()?;
697 let callback_invoked = self
698 .long_press_callbacks_invoked
699 .contains(&session.session_id);
700
701 Some(DetectedLongPress {
702 position: first.position,
703 duration_ms,
704 callback_invoked,
705 session_id: session.session_id,
706 })
707 } else {
708 None
709 }
710 }
711
712 pub fn mark_long_press_callback_invoked(&mut self, session_id: u64) {
717 if !self.long_press_callbacks_invoked.contains(&session_id) {
718 self.long_press_callbacks_invoked.push(session_id);
719 }
720 }
721
722 pub fn detect_double_click(&self) -> bool {
726 let sessions = &self.input_sessions;
727 if sessions.len() < 2 {
728 return false;
729 }
730
731 let prev_session = &sessions[sessions.len() - 2];
732 let last_session = &sessions[sessions.len() - 1];
733
734 if !prev_session.ended || !last_session.ended {
736 return false;
737 }
738
739 let prev_first = prev_session.first_sample();
740 let last_first = last_session.first_sample();
741 let (prev_first, last_first) = match (prev_first, last_first) {
742 (Some(p), Some(l)) => (p, l),
743 _ => return false,
744 };
745
746 let duration = last_first.timestamp.duration_since(&prev_first.timestamp);
747 let time_delta_ms = duration_to_millis(duration);
748 if time_delta_ms > self.config.double_click_time_threshold_ms {
749 return false;
750 }
751
752 let dx = last_first.position.x - prev_first.position.x;
753 let dy = last_first.position.y - prev_first.position.y;
754 let distance = (dx * dx + dy * dy).sqrt();
755
756 distance < self.config.double_click_distance_threshold
757 }
758
759 pub fn get_drag_direction(&self) -> Option<GestureDirection> {
761 let session = self.get_current_session()?;
762 let first = session.first_sample()?;
763 let last = session.last_sample()?;
764
765 let dx = last.position.x - first.position.x;
766 let dy = last.position.y - first.position.y;
767
768 let direction = if dx.abs() > dy.abs() {
769 if dx > 0.0 {
770 GestureDirection::Right
771 } else {
772 GestureDirection::Left
773 }
774 } else {
775 if dy > 0.0 {
776 GestureDirection::Down
777 } else {
778 GestureDirection::Up
779 }
780 };
781 Some(direction)
782 }
783
784 pub fn get_gesture_velocity(&self) -> Option<f32> {
786 let session = self.get_current_session()?;
787
788 if session.samples.len() < 2 {
789 return None;
790 }
791
792 let total_distance = session.total_distance();
793 let duration_ms = session.duration_ms()?;
794
795 if duration_ms == 0 {
796 return None;
797 }
798
799 let duration_secs = duration_ms as f32 / 1000.0;
800 Some(total_distance / duration_secs)
801 }
802
803 pub fn is_swipe(&self) -> bool {
805 self.get_gesture_velocity()
806 .map(|v| v >= self.config.swipe_velocity_threshold)
807 .unwrap_or(false)
808 }
809
810 pub fn detect_swipe_direction(&self) -> Option<GestureDirection> {
814 if !self.is_swipe() {
816 return None;
817 }
818
819 self.get_drag_direction()
821 }
822
823 pub fn detect_pinch(&self) -> Option<DetectedPinch> {
828 if self.input_sessions.len() < 2 {
830 return None;
831 }
832
833 let session1 = &self.input_sessions[self.input_sessions.len() - 2];
835 let session2 = &self.input_sessions[self.input_sessions.len() - 1];
836
837 let first1 = session1.first_sample()?;
839 let first2 = session2.first_sample()?;
840 let last1 = session1.last_sample()?;
841 let last2 = session2.last_sample()?;
842
843 let dx_initial = first2.position.x - first1.position.x;
845 let dy_initial = first2.position.y - first1.position.y;
846 let initial_distance = (dx_initial * dx_initial + dy_initial * dy_initial).sqrt();
847
848 let dx_current = last2.position.x - last1.position.x;
850 let dy_current = last2.position.y - last1.position.y;
851 let current_distance = (dx_current * dx_current + dy_current * dy_current).sqrt();
852
853 if initial_distance < 1.0 {
855 return None;
856 }
857
858 let scale = current_distance / initial_distance;
860
861 let scale_threshold = 1.0 + self.config.pinch_scale_threshold;
863 if scale > 1.0 / scale_threshold && scale < scale_threshold {
864 return None; }
866
867 let center = LogicalPosition {
869 x: (last1.position.x + last2.position.x) / 2.0,
870 y: (last1.position.y + last2.position.y) / 2.0,
871 };
872
873 let duration = last1.timestamp.duration_since(&first1.timestamp);
875 let duration_ms = duration_to_millis(duration);
876
877 Some(DetectedPinch {
878 scale,
879 center,
880 initial_distance,
881 current_distance,
882 duration_ms,
883 })
884 }
885
886 pub fn detect_rotation(&self) -> Option<DetectedRotation> {
891 if self.input_sessions.len() < 2 {
893 return None;
894 }
895
896 let session1 = &self.input_sessions[self.input_sessions.len() - 2];
898 let session2 = &self.input_sessions[self.input_sessions.len() - 1];
899
900 let first1 = session1.first_sample()?;
902 let first2 = session2.first_sample()?;
903 let last1 = session1.last_sample()?;
904 let last2 = session2.last_sample()?;
905
906 let center = LogicalPosition {
908 x: (last1.position.x + last2.position.x) / 2.0,
909 y: (last1.position.y + last2.position.y) / 2.0,
910 };
911
912 let dx_initial = first2.position.x - first1.position.x;
914 let dy_initial = first2.position.y - first1.position.y;
915 let initial_angle = dy_initial.atan2(dx_initial);
916
917 let dx_current = last2.position.x - last1.position.x;
919 let dy_current = last2.position.y - last1.position.y;
920 let current_angle = dy_current.atan2(dx_current);
921
922 let mut angle_diff = current_angle - initial_angle;
924
925 const PI: f32 = core::f32::consts::PI;
927 while angle_diff > PI {
928 angle_diff -= 2.0 * PI;
929 }
930 while angle_diff < -PI {
931 angle_diff += 2.0 * PI;
932 }
933
934 if angle_diff.abs() < self.config.rotation_angle_threshold {
936 return None;
937 }
938
939 let duration = last1.timestamp.duration_since(&first1.timestamp);
941 let duration_ms = duration_to_millis(duration);
942
943 Some(DetectedRotation {
944 angle_radians: angle_diff,
945 center,
946 duration_ms,
947 })
948 }
949
950 pub fn get_current_session(&self) -> Option<&InputSession> {
952 self.input_sessions.last()
953 }
954
955 pub fn get_current_mouse_position(&self) -> Option<LogicalPosition> {
957 self.get_current_session()
958 .and_then(|s| s.last_sample())
959 .map(|sample| sample.position)
960 }
961
962 pub fn get_drag_delta(&self) -> Option<(f32, f32)> {
967 let session = self.get_current_session()?;
968 let first = session.first_sample()?;
969 let last = session.last_sample()?;
970 Some((
971 last.position.x - first.position.x,
972 last.position.y - first.position.y,
973 ))
974 }
975
976 pub fn get_drag_delta_screen(&self) -> Option<(f32, f32)> {
988 let session = self.get_current_session()?;
989 let first = session.first_sample()?;
990 let last = session.last_sample()?;
991 Some((
992 last.screen_position.x - first.screen_position.x,
993 last.screen_position.y - first.screen_position.y,
994 ))
995 }
996
997 pub fn get_drag_delta_screen_incremental(&self) -> Option<(f32, f32)> {
1016 let session = self.get_current_session()?;
1017 let len = session.samples.len();
1018 if len < 2 {
1019 return None;
1020 }
1021 let prev = &session.samples[len - 2];
1022 let last = &session.samples[len - 1];
1023 Some((
1024 last.screen_position.x - prev.screen_position.x,
1025 last.screen_position.y - prev.screen_position.y,
1026 ))
1027 }
1028
1029 pub fn get_window_position_at_session_start(&self) -> Option<azul_core::window::WindowPosition> {
1033 let session = self.get_current_session()?;
1034 Some(session.window_position_at_start)
1035 }
1036
1037 pub fn get_drag_context(&self) -> Option<&DragContext> {
1043 self.active_drag.as_ref()
1044 }
1045
1046 pub fn get_drag_context_mut(&mut self) -> Option<&mut DragContext> {
1048 self.active_drag.as_mut()
1049 }
1050
1051 pub fn activate_text_selection_drag(
1053 &mut self,
1054 dom_id: DomId,
1055 anchor_ifc_node: NodeId,
1056 start_mouse_position: LogicalPosition,
1057 ) {
1058 let session_id = self.current_session_id().unwrap_or(0);
1059 self.active_drag = Some(DragContext::text_selection(
1060 dom_id,
1061 anchor_ifc_node,
1062 start_mouse_position,
1063 session_id,
1064 ));
1065 }
1066
1067 pub fn activate_scrollbar_drag(
1069 &mut self,
1070 scroll_container_node: NodeId,
1071 axis: ScrollbarAxis,
1072 start_mouse_position: LogicalPosition,
1073 start_scroll_offset: f32,
1074 track_length_px: f32,
1075 content_length_px: f32,
1076 viewport_length_px: f32,
1077 ) {
1078 let session_id = self.current_session_id().unwrap_or(0);
1079 self.active_drag = Some(DragContext::scrollbar_thumb(
1080 scroll_container_node,
1081 axis,
1082 start_mouse_position,
1083 start_scroll_offset,
1084 track_length_px,
1085 content_length_px,
1086 viewport_length_px,
1087 session_id,
1088 ));
1089 }
1090
1091 pub fn activate_node_drag(
1093 &mut self,
1094 dom_id: DomId,
1095 node_id: NodeId,
1096 drag_data: DragData,
1097 _start_hit_test: Option<HitTest>,
1098 ) {
1099 if let Some(detected) = self.detect_drag() {
1100 self.active_drag = Some(DragContext::node_drag(
1101 dom_id,
1102 node_id,
1103 detected.start_position,
1104 drag_data,
1105 detected.session_id,
1106 ));
1107 }
1108 }
1109
1110 pub fn activate_window_drag(
1112 &mut self,
1113 initial_window_position: WindowPosition,
1114 _start_hit_test: Option<HitTest>,
1115 ) {
1116 if let Some(detected) = self.detect_drag() {
1117 self.active_drag = Some(DragContext::window_move(
1118 detected.start_position,
1119 initial_window_position,
1120 detected.session_id,
1121 ));
1122 }
1123 }
1124
1125 pub fn start_file_drop(&mut self, files: Vec<AzString>, position: LogicalPosition) {
1127 let session_id = self.current_session_id().unwrap_or(0);
1128 self.active_drag = Some(DragContext::file_drop(files, position, session_id));
1129 }
1130
1131 pub fn update_active_drag_positions(&mut self, position: LogicalPosition) {
1133 if let Some(ref mut drag) = self.active_drag {
1134 drag.update_position(position);
1135 }
1136 }
1137
1138 pub fn update_drop_target(&mut self, target: Option<azul_core::dom::DomNodeId>) {
1140 if let Some(ref mut drag) = self.active_drag {
1141 match &mut drag.drag_type {
1142 ActiveDragType::Node(ref mut node_drag) => {
1143 node_drag.current_drop_target = target.into();
1144 }
1145 ActiveDragType::FileDrop(ref mut file_drop) => {
1146 file_drop.drop_target = target.into();
1147 }
1148 _ => {}
1149 }
1150 }
1151 }
1152
1153 pub fn update_auto_scroll_direction(&mut self, direction: AutoScrollDirection) {
1155 if let Some(ref mut drag) = self.active_drag {
1156 if let Some(text_drag) = drag.as_text_selection_mut() {
1157 text_drag.auto_scroll_direction = direction;
1158 }
1159 }
1160 }
1161
1162 pub fn end_drag(&mut self) -> Option<DragContext> {
1164 self.active_drag.take()
1165 }
1166
1167 pub fn cancel_drag(&mut self) {
1169 if let Some(ref mut drag) = self.active_drag {
1170 drag.cancelled = true;
1171 }
1172 self.active_drag = None;
1173 }
1174
1175 pub fn is_dragging(&self) -> bool {
1181 self.active_drag.is_some()
1182 }
1183
1184 pub fn is_text_selection_dragging(&self) -> bool {
1186 self.active_drag.as_ref().is_some_and(|d| d.is_text_selection())
1187 }
1188
1189 pub fn is_scrollbar_dragging(&self) -> bool {
1191 self.active_drag.as_ref().is_some_and(|d| d.is_scrollbar_thumb())
1192 }
1193
1194 pub fn is_node_dragging_any(&self) -> bool {
1196 self.active_drag.as_ref().is_some_and(|d| d.is_node_drag())
1197 }
1198
1199 pub fn is_node_drag_active(&self) -> bool {
1201 self.is_node_dragging_any()
1202 }
1203
1204 pub fn is_node_dragging(&self, dom_id: DomId, node_id: NodeId) -> bool {
1206 self.active_drag.as_ref().is_some_and(|d| {
1207 if let Some(node_drag) = d.as_node_drag() {
1208 node_drag.dom_id == dom_id && node_drag.node_id == node_id
1209 } else {
1210 false
1211 }
1212 })
1213 }
1214
1215 pub fn is_window_dragging(&self) -> bool {
1217 self.active_drag.as_ref().is_some_and(|d| d.is_window_move())
1218 }
1219
1220 pub fn is_file_dropping(&self) -> bool {
1222 self.active_drag.as_ref().is_some_and(|d| d.is_file_drop())
1223 }
1224
1225 pub fn session_count(&self) -> usize {
1227 self.input_sessions.len()
1228 }
1229
1230 pub fn current_session_id(&self) -> Option<u64> {
1232 self.get_current_session().map(|s| s.session_id)
1233 }
1234
1235 pub fn get_node_drag(&self) -> Option<&NodeDrag> {
1242 self.active_drag.as_ref().and_then(|d| d.as_node_drag())
1243 }
1244
1245 pub fn get_window_drag(&self) -> Option<&WindowMoveDrag> {
1248 self.active_drag.as_ref().and_then(|d| d.as_window_move())
1249 }
1250
1251 pub fn get_file_drop(&self) -> Option<&FileDropDrag> {
1254 self.active_drag.as_ref().and_then(|d| d.as_file_drop())
1255 }
1256
1257 pub fn end_node_drag(&mut self) -> Option<DragContext> {
1260 if self.active_drag.as_ref().is_some_and(|d| d.is_node_drag()) {
1261 self.end_drag()
1262 } else {
1263 None
1264 }
1265 }
1266
1267 pub fn end_window_drag(&mut self) -> Option<DragContext> {
1270 if self.active_drag.as_ref().is_some_and(|d| d.is_window_move()) {
1271 self.end_drag()
1272 } else {
1273 None
1274 }
1275 }
1276
1277 pub fn end_file_drop(&mut self) -> Option<DragContext> {
1280 if self.active_drag.as_ref().is_some_and(|d| d.is_file_drop()) {
1281 self.end_drag()
1282 } else {
1283 None
1284 }
1285 }
1286
1287 pub fn cancel_file_drop(&mut self) {
1290 if self.active_drag.as_ref().is_some_and(|d| d.is_file_drop()) {
1291 self.cancel_drag();
1292 }
1293 }
1294
1295 pub fn get_window_drag_delta(&self) -> Option<(i32, i32)> {
1304 let drag = self.active_drag.as_ref()?.as_window_move()?;
1305
1306 let delta_x = drag.current_position.x - drag.start_position.x;
1307 let delta_y = drag.current_position.y - drag.start_position.y;
1308
1309 match drag.initial_window_position {
1310 WindowPosition::Initialized(_initial_pos) => Some((delta_x as i32, delta_y as i32)),
1311 _ => None,
1312 }
1313 }
1314
1315 pub fn get_window_position_from_drag(&self) -> Option<WindowPosition> {
1319 let drag = self.active_drag.as_ref()?.as_window_move()?;
1320
1321 let delta_x = drag.current_position.x - drag.start_position.x;
1322 let delta_y = drag.current_position.y - drag.start_position.y;
1323
1324 match drag.initial_window_position {
1325 WindowPosition::Initialized(initial_pos) => {
1326 Some(WindowPosition::Initialized(PhysicalPositionI32::new(
1327 initial_pos.x + delta_x as i32,
1328 initial_pos.y + delta_y as i32,
1329 )))
1330 }
1331 _ => None,
1332 }
1333 }
1334
1335 pub fn get_scrollbar_scroll_offset(&self) -> Option<f32> {
1337 self.active_drag.as_ref()?.calculate_scrollbar_scroll_offset()
1338 }
1339
1340 pub fn remap_node_ids(
1345 &mut self,
1346 dom_id: azul_core::dom::DomId,
1347 node_id_map: &std::collections::BTreeMap<azul_core::id::NodeId, azul_core::id::NodeId>,
1348 ) {
1349 if let Some(ref mut drag) = self.active_drag {
1350 if !drag.remap_node_ids(dom_id, node_id_map) {
1351 drag.cancelled = true;
1353 self.active_drag = None;
1354 }
1355 }
1356 }
1357}