1use alloc::vec::Vec;
14
15use crate::dom::{DomId, DomNodeId, NodeId, OptionDomNodeId};
16use crate::geom::LogicalPosition;
17use crate::selection::TextCursor;
18use crate::window::WindowPosition;
19
20use azul_css::{AzString, StringVec, U8Vec};
21
22#[derive(Debug, Clone, PartialEq)]
27#[repr(C, u8)]
28pub enum ActiveDragType {
29 TextSelection(TextSelectionDrag),
31 ScrollbarThumb(ScrollbarThumbDrag),
33 Node(NodeDrag),
35 WindowMove(WindowMoveDrag),
37 WindowResize(WindowResizeDrag),
39 FileDrop(FileDropDrag),
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[repr(C)]
48pub struct TextSelectionDrag {
49 pub dom_id: DomId,
51 pub anchor_ifc_node: NodeId,
53 pub anchor_cursor: Option<TextCursor>,
55 pub start_mouse_position: LogicalPosition,
57 pub current_mouse_position: LogicalPosition,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq)]
65#[repr(C)]
66pub struct ScrollbarThumbDrag {
67 pub dom_id: DomId,
71 pub scroll_container_node: NodeId,
73 pub axis: ScrollbarAxis,
75 pub start_mouse_position: LogicalPosition,
77 pub start_scroll_offset: f32,
79 pub current_mouse_position: LogicalPosition,
81 pub track_length_px: f32,
83 pub content_length_px: f32,
85 pub viewport_length_px: f32,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91#[repr(C)]
92pub enum ScrollbarAxis {
93 Vertical,
94 Horizontal,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
101#[repr(C)]
102pub struct NodeDrag {
103 pub dom_id: DomId,
105 pub node_id: NodeId,
107 pub start_position: LogicalPosition,
109 pub current_position: LogicalPosition,
111 pub drag_offset: LogicalPosition,
113 pub current_drop_target: OptionDomNodeId,
115 pub previous_drop_target: OptionDomNodeId,
117 pub drag_data: DragData,
119 pub drop_accepted: bool,
121 pub drop_effect: DropEffect,
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129#[repr(C)]
130pub struct WindowMoveDrag {
131 pub start_position: LogicalPosition,
133 pub current_position: LogicalPosition,
135 pub initial_window_position: WindowPosition,
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[repr(C)]
144pub struct WindowResizeDrag {
145 pub edge: WindowResizeEdge,
147 pub start_position: LogicalPosition,
149 pub current_position: LogicalPosition,
151 pub initial_width: u32,
153 pub initial_height: u32,
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159#[repr(C)]
160pub enum WindowResizeEdge {
161 Top,
162 Bottom,
163 Left,
164 Right,
165 TopLeft,
166 TopRight,
167 BottomLeft,
168 BottomRight,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq)]
175#[repr(C)]
176pub struct FileDropDrag {
177 pub files: StringVec,
179 pub position: LogicalPosition,
181 pub drop_target: OptionDomNodeId,
183 pub drop_effect: DropEffect,
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
194#[repr(C)]
195pub enum DropEffect {
196 #[default]
198 None,
199 Copy,
201 Link,
203 Move,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
215#[repr(C)]
216pub enum DragEffect {
217 #[default]
220 Uninitialized,
221 None,
223 Copy,
225 CopyLink,
227 CopyMove,
229 Link,
231 LinkMove,
233 Move,
235 All,
237}
238
239#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
242#[repr(C)]
243pub struct MimeTypeData {
244 pub mime_type: AzString,
245 pub data: U8Vec,
246}
247
248impl_option!(
249 MimeTypeData,
250 OptionMimeTypeData,
251 copy = false,
252 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
253);
254
255impl_vec!(
256 MimeTypeData,
257 MimeTypeDataVec,
258 MimeTypeDataVecDestructor,
259 MimeTypeDataVecDestructorType,
260 MimeTypeDataVecSlice,
261 OptionMimeTypeData
262);
263impl_vec_mut!(MimeTypeData, MimeTypeDataVec);
264impl_vec_debug!(MimeTypeData, MimeTypeDataVec);
265impl_vec_partialord!(MimeTypeData, MimeTypeDataVec);
266impl_vec_ord!(MimeTypeData, MimeTypeDataVec);
267impl_vec_clone!(MimeTypeData, MimeTypeDataVec, MimeTypeDataVecDestructor);
268impl_vec_partialeq!(MimeTypeData, MimeTypeDataVec);
269impl_vec_eq!(MimeTypeData, MimeTypeDataVec);
270impl_vec_hash!(MimeTypeData, MimeTypeDataVec);
271
272#[derive(Debug, Default, Clone, PartialEq, Eq)]
277#[repr(C)]
278pub struct DragData {
279 pub data: MimeTypeDataVec,
283 pub effect_allowed: DragEffect,
285}
286
287impl DragData {
288 #[must_use]
290 pub const fn new() -> Self {
291 Self {
292 data: MimeTypeDataVec::new(),
293 effect_allowed: DragEffect::Uninitialized,
294 }
295 }
296
297 pub fn set_data(&mut self, mime_type: impl Into<AzString>, data: Vec<u8>) {
300 let mime_type = mime_type.into();
301 let value: U8Vec = data.into();
302 if let Some(entry) = self
303 .data
304 .as_mut()
305 .iter_mut()
306 .find(|e| e.mime_type == mime_type)
307 {
308 entry.data = value;
309 } else {
310 self.data.push(MimeTypeData {
311 mime_type,
312 data: value,
313 });
314 }
315 }
316
317 #[must_use]
319 pub fn get_data(&self, mime_type: &str) -> Option<&[u8]> {
320 self.data
321 .as_ref()
322 .iter()
323 .find(|e| e.mime_type.as_str() == mime_type)
324 .map(|e| e.data.as_ref())
325 }
326
327 pub fn set_text(&mut self, text: impl Into<AzString>) {
329 let text_str = text.into();
330 self.set_data("text/plain", text_str.as_str().as_bytes().to_vec());
331 }
332
333 #[must_use]
335 pub fn get_text(&self) -> Option<AzString> {
336 self.get_data("text/plain")
337 .map(|bytes| AzString::from(core::str::from_utf8(bytes).unwrap_or("")))
338 }
339}
340
341#[derive(Debug, Clone, PartialEq)]
348pub struct DragContext {
349 pub drag_type: ActiveDragType,
351 pub session_id: u64,
353 pub cancelled: bool,
355}
356
357impl DragContext {
358 #[must_use]
360 pub const fn new(drag_type: ActiveDragType, session_id: u64) -> Self {
361 Self {
362 drag_type,
363 session_id,
364 cancelled: false,
365 }
366 }
367
368 #[must_use]
370 pub const fn text_selection(
371 dom_id: DomId,
372 anchor_ifc_node: NodeId,
373 start_mouse_position: LogicalPosition,
374 session_id: u64,
375 ) -> Self {
376 Self::new(
377 ActiveDragType::TextSelection(TextSelectionDrag {
378 dom_id,
379 anchor_ifc_node,
380 anchor_cursor: None,
381 start_mouse_position,
382 current_mouse_position: start_mouse_position,
383 }),
384 session_id,
385 )
386 }
387
388 #[must_use]
390 pub const fn scrollbar_thumb(
391 dom_id: DomId,
392 scroll_container_node: NodeId,
393 axis: ScrollbarAxis,
394 start_mouse_position: LogicalPosition,
395 start_scroll_offset: f32,
396 track_length_px: f32,
397 content_length_px: f32,
398 viewport_length_px: f32,
399 session_id: u64,
400 ) -> Self {
401 Self::new(
402 ActiveDragType::ScrollbarThumb(ScrollbarThumbDrag {
403 dom_id,
404 scroll_container_node,
405 axis,
406 start_mouse_position,
407 start_scroll_offset,
408 current_mouse_position: start_mouse_position,
409 track_length_px,
410 content_length_px,
411 viewport_length_px,
412 }),
413 session_id,
414 )
415 }
416
417 #[must_use]
419 pub const fn node_drag(
420 dom_id: DomId,
421 node_id: NodeId,
422 start_position: LogicalPosition,
423 drag_data: DragData,
424 session_id: u64,
425 ) -> Self {
426 Self::new(
427 ActiveDragType::Node(NodeDrag {
428 dom_id,
429 node_id,
430 start_position,
431 current_position: start_position,
432 drag_offset: LogicalPosition::zero(),
433 current_drop_target: OptionDomNodeId::None,
434 previous_drop_target: OptionDomNodeId::None,
435 drag_data,
436 drop_accepted: false,
437 drop_effect: DropEffect::None,
438 }),
439 session_id,
440 )
441 }
442
443 #[must_use]
445 pub const fn window_move(
446 start_position: LogicalPosition,
447 initial_window_position: WindowPosition,
448 session_id: u64,
449 ) -> Self {
450 Self::new(
451 ActiveDragType::WindowMove(WindowMoveDrag {
452 start_position,
453 current_position: start_position,
454 initial_window_position,
455 }),
456 session_id,
457 )
458 }
459
460 #[must_use]
462 pub fn file_drop(files: Vec<AzString>, position: LogicalPosition, session_id: u64) -> Self {
463 Self::new(
464 ActiveDragType::FileDrop(FileDropDrag {
465 files: files.into(),
466 position,
467 drop_target: OptionDomNodeId::None,
468 drop_effect: DropEffect::Copy,
469 }),
470 session_id,
471 )
472 }
473
474 pub const fn update_position(&mut self, position: LogicalPosition) {
476 match &mut self.drag_type {
477 ActiveDragType::TextSelection(ref mut drag) => {
478 drag.current_mouse_position = position;
479 }
480 ActiveDragType::ScrollbarThumb(ref mut drag) => {
481 drag.current_mouse_position = position;
482 }
483 ActiveDragType::Node(ref mut drag) => {
484 drag.current_position = position;
485 }
486 ActiveDragType::WindowMove(ref mut drag) => {
487 drag.current_position = position;
488 }
489 ActiveDragType::WindowResize(ref mut drag) => {
490 drag.current_position = position;
491 }
492 ActiveDragType::FileDrop(ref mut drag) => {
493 drag.position = position;
494 }
495 }
496 }
497
498 #[must_use]
500 pub const fn current_position(&self) -> LogicalPosition {
501 match &self.drag_type {
502 ActiveDragType::TextSelection(drag) => drag.current_mouse_position,
503 ActiveDragType::ScrollbarThumb(drag) => drag.current_mouse_position,
504 ActiveDragType::Node(drag) => drag.current_position,
505 ActiveDragType::WindowMove(drag) => drag.current_position,
506 ActiveDragType::WindowResize(drag) => drag.current_position,
507 ActiveDragType::FileDrop(drag) => drag.position,
508 }
509 }
510
511 #[must_use]
513 pub const fn start_position(&self) -> LogicalPosition {
514 match &self.drag_type {
515 ActiveDragType::TextSelection(drag) => drag.start_mouse_position,
516 ActiveDragType::ScrollbarThumb(drag) => drag.start_mouse_position,
517 ActiveDragType::Node(drag) => drag.start_position,
518 ActiveDragType::WindowMove(drag) => drag.start_position,
519 ActiveDragType::WindowResize(drag) => drag.start_position,
520 ActiveDragType::FileDrop(drag) => drag.position, }
522 }
523
524 #[must_use]
526 pub const fn is_text_selection(&self) -> bool {
527 matches!(self.drag_type, ActiveDragType::TextSelection(_))
528 }
529
530 #[must_use]
532 pub const fn is_scrollbar_thumb(&self) -> bool {
533 matches!(self.drag_type, ActiveDragType::ScrollbarThumb(_))
534 }
535
536 #[must_use]
538 pub const fn is_node_drag(&self) -> bool {
539 matches!(self.drag_type, ActiveDragType::Node(_))
540 }
541
542 #[must_use]
544 pub const fn is_window_move(&self) -> bool {
545 matches!(self.drag_type, ActiveDragType::WindowMove(_))
546 }
547
548 #[must_use]
550 pub const fn is_file_drop(&self) -> bool {
551 matches!(self.drag_type, ActiveDragType::FileDrop(_))
552 }
553
554 #[must_use]
556 pub const fn as_text_selection(&self) -> Option<&TextSelectionDrag> {
557 match &self.drag_type {
558 ActiveDragType::TextSelection(drag) => Some(drag),
559 _ => None,
560 }
561 }
562
563 pub const fn as_text_selection_mut(&mut self) -> Option<&mut TextSelectionDrag> {
565 match &mut self.drag_type {
566 ActiveDragType::TextSelection(drag) => Some(drag),
567 _ => None,
568 }
569 }
570
571 #[must_use]
573 pub const fn as_scrollbar_thumb(&self) -> Option<&ScrollbarThumbDrag> {
574 match &self.drag_type {
575 ActiveDragType::ScrollbarThumb(drag) => Some(drag),
576 _ => None,
577 }
578 }
579
580 pub const fn as_scrollbar_thumb_mut(&mut self) -> Option<&mut ScrollbarThumbDrag> {
582 match &mut self.drag_type {
583 ActiveDragType::ScrollbarThumb(drag) => Some(drag),
584 _ => None,
585 }
586 }
587
588 #[must_use]
590 pub const fn as_node_drag(&self) -> Option<&NodeDrag> {
591 match &self.drag_type {
592 ActiveDragType::Node(drag) => Some(drag),
593 _ => None,
594 }
595 }
596
597 pub const fn as_node_drag_mut(&mut self) -> Option<&mut NodeDrag> {
599 match &mut self.drag_type {
600 ActiveDragType::Node(drag) => Some(drag),
601 _ => None,
602 }
603 }
604
605 #[must_use]
607 pub const fn as_window_move(&self) -> Option<&WindowMoveDrag> {
608 match &self.drag_type {
609 ActiveDragType::WindowMove(drag) => Some(drag),
610 _ => None,
611 }
612 }
613
614 #[must_use]
616 pub const fn as_file_drop(&self) -> Option<&FileDropDrag> {
617 match &self.drag_type {
618 ActiveDragType::FileDrop(drag) => Some(drag),
619 _ => None,
620 }
621 }
622
623 pub const fn as_file_drop_mut(&mut self) -> Option<&mut FileDropDrag> {
625 match &mut self.drag_type {
626 ActiveDragType::FileDrop(drag) => Some(drag),
627 _ => None,
628 }
629 }
630
631 #[must_use]
635 pub fn calculate_scrollbar_scroll_offset(&self) -> Option<f32> {
636 let drag = self.as_scrollbar_thumb()?;
637
638 let mouse_delta = match drag.axis {
640 ScrollbarAxis::Vertical => drag.current_mouse_position.y - drag.start_mouse_position.y,
641 ScrollbarAxis::Horizontal => {
642 drag.current_mouse_position.x - drag.start_mouse_position.x
643 }
644 };
645
646 let scrollable_range = drag.content_length_px - drag.viewport_length_px;
648 if scrollable_range <= 0.0 || scrollable_range.is_nan() || drag.track_length_px <= 0.0 {
653 return Some(drag.start_scroll_offset);
654 }
655
656 let thumb_length =
658 (drag.viewport_length_px / drag.content_length_px) * drag.track_length_px;
659 let scrollable_track = drag.track_length_px - thumb_length;
660
661 if scrollable_track <= 0.0 {
662 return Some(drag.start_scroll_offset);
663 }
664
665 let scroll_ratio = mouse_delta / scrollable_track;
667 let scroll_delta = scroll_ratio * scrollable_range;
668
669 let new_offset = drag.start_scroll_offset + scroll_delta;
671
672 Some(new_offset.clamp(0.0, scrollable_range))
674 }
675
676 fn remap_drop_target(
679 target: &mut OptionDomNodeId,
680 dom_id: DomId,
681 node_id_map: &alloc::collections::BTreeMap<NodeId, NodeId>,
682 ) {
683 let dt = match target.into_option() {
684 Some(dt) if dt.dom == dom_id => dt,
685 _ => return,
686 };
687 let Some(old_nid) = dt.node.into_crate_internal() else {
688 return;
689 };
690 if let Some(&new_nid) = node_id_map.get(&old_nid) {
691 *target = Some(DomNodeId {
692 dom: dom_id,
693 node: crate::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(new_nid)),
694 })
695 .into();
696 } else {
697 *target = OptionDomNodeId::None;
698 }
699 }
700
701 pub fn remap_node_ids(
707 &mut self,
708 dom_id: DomId,
709 node_id_map: &alloc::collections::BTreeMap<NodeId, NodeId>,
710 ) -> bool {
711 match &mut self.drag_type {
712 ActiveDragType::TextSelection(ref mut drag) => {
713 if drag.dom_id != dom_id {
714 return true;
715 }
716 if let Some(&new_id) = node_id_map.get(&drag.anchor_ifc_node) {
717 drag.anchor_ifc_node = new_id;
718 } else {
719 return false; }
721 true
722 }
723 ActiveDragType::ScrollbarThumb(ref mut drag) => {
724 if drag.dom_id != dom_id {
727 return true;
728 }
729 if let Some(&new_id) = node_id_map.get(&drag.scroll_container_node) {
730 drag.scroll_container_node = new_id;
731 true
732 } else {
733 false }
735 }
736 ActiveDragType::Node(ref mut drag) => {
737 if drag.dom_id != dom_id {
738 return true;
739 }
740 if let Some(&new_id) = node_id_map.get(&drag.node_id) {
741 drag.node_id = new_id;
742 } else {
743 return false; }
745 Self::remap_drop_target(&mut drag.current_drop_target, dom_id, node_id_map);
750 Self::remap_drop_target(&mut drag.previous_drop_target, dom_id, node_id_map);
751 true
752 }
753 ActiveDragType::WindowMove(_) | ActiveDragType::WindowResize(_) => true,
755 ActiveDragType::FileDrop(ref mut drag) => {
756 Self::remap_drop_target(&mut drag.drop_target, dom_id, node_id_map);
757 true
758 }
759 }
760 }
761}
762
763azul_css::impl_option!(
764 DragContext,
765 OptionDragContext,
766 copy = false,
767 [Debug, Clone, PartialEq]
768);
769
770#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
773#[repr(C)]
774pub struct DragDelta {
775 pub dx: f32,
776 pub dy: f32,
777}
778
779impl DragDelta {
780 #[inline]
781 #[must_use]
782 pub const fn new(dx: f32, dy: f32) -> Self {
783 Self { dx, dy }
784 }
785 #[inline]
786 #[must_use]
787 pub const fn zero() -> Self {
788 Self::new(0.0, 0.0)
789 }
790}
791
792impl_option!(
793 DragDelta,
794 OptionDragDelta,
795 [Debug, Copy, Clone, PartialEq, PartialOrd]
796);
797
798#[cfg(test)]
799#[path = "drag_test.rs"]
800mod drag_test;