1use alloc::collections::btree_map::BTreeMap;
14use alloc::vec::Vec;
15
16use crate::dom::{DomId, DomNodeId, NodeId, OptionDomNodeId};
17use crate::geom::LogicalPosition;
18use crate::selection::TextCursor;
19use crate::window::WindowPosition;
20
21use azul_css::{AzString, OptionString, StringVec};
22
23#[derive(Debug, Clone, PartialEq)]
28#[repr(C, u8)]
29pub enum ActiveDragType {
30 TextSelection(TextSelectionDrag),
32 ScrollbarThumb(ScrollbarThumbDrag),
34 Node(NodeDrag),
36 WindowMove(WindowMoveDrag),
38 WindowResize(WindowResizeDrag),
40 FileDrop(FileDropDrag),
42}
43
44#[derive(Debug, Clone, PartialEq)]
48#[repr(C)]
49pub struct TextSelectionDrag {
50 pub dom_id: DomId,
52 pub anchor_ifc_node: NodeId,
54 pub anchor_cursor: Option<TextCursor>,
56 pub start_mouse_position: LogicalPosition,
58 pub current_mouse_position: LogicalPosition,
60 pub auto_scroll_direction: AutoScrollDirection,
62 pub auto_scroll_container: Option<NodeId>,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq)]
70#[repr(C)]
71pub struct ScrollbarThumbDrag {
72 pub scroll_container_node: NodeId,
74 pub axis: ScrollbarAxis,
76 pub start_mouse_position: LogicalPosition,
78 pub start_scroll_offset: f32,
80 pub current_mouse_position: LogicalPosition,
82 pub track_length_px: f32,
84 pub content_length_px: f32,
86 pub viewport_length_px: f32,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92#[repr(C)]
93pub enum ScrollbarAxis {
94 Vertical,
95 Horizontal,
96}
97
98#[derive(Debug, Clone, PartialEq)]
102#[repr(C)]
103pub struct NodeDrag {
104 pub dom_id: DomId,
106 pub node_id: NodeId,
108 pub start_position: LogicalPosition,
110 pub current_position: LogicalPosition,
112 pub drag_offset: LogicalPosition,
114 pub current_drop_target: OptionDomNodeId,
116 pub previous_drop_target: OptionDomNodeId,
118 pub drag_data: DragData,
120 pub drop_accepted: bool,
122 pub drop_effect: DropEffect,
124}
125
126#[derive(Debug, Clone, PartialEq)]
130#[repr(C)]
131pub struct WindowMoveDrag {
132 pub start_position: LogicalPosition,
134 pub current_position: LogicalPosition,
136 pub initial_window_position: WindowPosition,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq)]
144#[repr(C)]
145pub struct WindowResizeDrag {
146 pub edge: WindowResizeEdge,
148 pub start_position: LogicalPosition,
150 pub current_position: LogicalPosition,
152 pub initial_width: u32,
154 pub initial_height: u32,
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160#[repr(C)]
161pub enum WindowResizeEdge {
162 Top,
163 Bottom,
164 Left,
165 Right,
166 TopLeft,
167 TopRight,
168 BottomLeft,
169 BottomRight,
170}
171
172#[derive(Debug, Clone, PartialEq)]
176#[repr(C)]
177pub struct FileDropDrag {
178 pub files: StringVec,
180 pub position: LogicalPosition,
182 pub drop_target: OptionDomNodeId,
184 pub drop_effect: DropEffect,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
190#[repr(C)]
191pub enum AutoScrollDirection {
192 #[default]
194 None,
195 Up,
197 Down,
199 Left,
201 Right,
203 UpLeft,
205 UpRight,
207 DownLeft,
209 DownRight,
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
215#[repr(C)]
216pub enum DropEffect {
217 #[default]
219 None,
220 Copy,
222 Move,
224 Link,
226}
227
228#[derive(Debug, Clone, PartialEq, Default)]
230pub struct DragData {
231 pub data: BTreeMap<AzString, Vec<u8>>,
235 pub effect_allowed: DragEffect,
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
241#[repr(C)]
242pub enum DragEffect {
243 #[default]
245 None,
246 Copy,
248 Move,
250 Link,
252}
253
254impl DragData {
255 pub fn new() -> Self {
257 Self {
258 data: BTreeMap::new(),
259 effect_allowed: DragEffect::Copy,
260 }
261 }
262
263 pub fn set_data(&mut self, mime_type: impl Into<AzString>, data: Vec<u8>) {
265 self.data.insert(mime_type.into(), data);
266 }
267
268 pub fn get_data(&self, mime_type: &str) -> Option<&[u8]> {
270 self.data.get(&AzString::from(mime_type)).map(|v| v.as_slice())
271 }
272
273 pub fn set_text(&mut self, text: impl Into<AzString>) {
275 let text_str = text.into();
276 self.set_data("text/plain", text_str.as_str().as_bytes().to_vec());
277 }
278
279 pub fn get_text(&self) -> Option<AzString> {
281 self.get_data("text/plain")
282 .map(|bytes| AzString::from(core::str::from_utf8(bytes).unwrap_or("")))
283 }
284}
285
286#[derive(Debug, Clone, PartialEq)]
291pub struct DragContext {
292 pub drag_type: ActiveDragType,
294 pub session_id: u64,
296 pub cancelled: bool,
298}
299
300impl DragContext {
301 pub fn new(drag_type: ActiveDragType, session_id: u64) -> Self {
303 Self {
304 drag_type,
305 session_id,
306 cancelled: false,
307 }
308 }
309
310 pub fn text_selection(
312 dom_id: DomId,
313 anchor_ifc_node: NodeId,
314 start_mouse_position: LogicalPosition,
315 session_id: u64,
316 ) -> Self {
317 Self::new(
318 ActiveDragType::TextSelection(TextSelectionDrag {
319 dom_id,
320 anchor_ifc_node,
321 anchor_cursor: None,
322 start_mouse_position,
323 current_mouse_position: start_mouse_position,
324 auto_scroll_direction: AutoScrollDirection::None,
325 auto_scroll_container: None,
326 }),
327 session_id,
328 )
329 }
330
331 pub fn scrollbar_thumb(
333 scroll_container_node: NodeId,
334 axis: ScrollbarAxis,
335 start_mouse_position: LogicalPosition,
336 start_scroll_offset: f32,
337 track_length_px: f32,
338 content_length_px: f32,
339 viewport_length_px: f32,
340 session_id: u64,
341 ) -> Self {
342 Self::new(
343 ActiveDragType::ScrollbarThumb(ScrollbarThumbDrag {
344 scroll_container_node,
345 axis,
346 start_mouse_position,
347 start_scroll_offset,
348 current_mouse_position: start_mouse_position,
349 track_length_px,
350 content_length_px,
351 viewport_length_px,
352 }),
353 session_id,
354 )
355 }
356
357 pub fn node_drag(
359 dom_id: DomId,
360 node_id: NodeId,
361 start_position: LogicalPosition,
362 drag_data: DragData,
363 session_id: u64,
364 ) -> Self {
365 Self::new(
366 ActiveDragType::Node(NodeDrag {
367 dom_id,
368 node_id,
369 start_position,
370 current_position: start_position,
371 drag_offset: LogicalPosition::zero(),
372 current_drop_target: OptionDomNodeId::None,
373 previous_drop_target: OptionDomNodeId::None,
374 drag_data,
375 drop_accepted: false,
376 drop_effect: DropEffect::None,
377 }),
378 session_id,
379 )
380 }
381
382 pub fn window_move(
384 start_position: LogicalPosition,
385 initial_window_position: WindowPosition,
386 session_id: u64,
387 ) -> Self {
388 Self::new(
389 ActiveDragType::WindowMove(WindowMoveDrag {
390 start_position,
391 current_position: start_position,
392 initial_window_position,
393 }),
394 session_id,
395 )
396 }
397
398 pub fn file_drop(files: Vec<AzString>, position: LogicalPosition, session_id: u64) -> Self {
400 Self::new(
401 ActiveDragType::FileDrop(FileDropDrag {
402 files: files.into(),
403 position,
404 drop_target: OptionDomNodeId::None,
405 drop_effect: DropEffect::Copy,
406 }),
407 session_id,
408 )
409 }
410
411 pub fn update_position(&mut self, position: LogicalPosition) {
413 match &mut self.drag_type {
414 ActiveDragType::TextSelection(ref mut drag) => {
415 drag.current_mouse_position = position;
416 }
417 ActiveDragType::ScrollbarThumb(ref mut drag) => {
418 drag.current_mouse_position = position;
419 }
420 ActiveDragType::Node(ref mut drag) => {
421 drag.current_position = position;
422 }
423 ActiveDragType::WindowMove(ref mut drag) => {
424 drag.current_position = position;
425 }
426 ActiveDragType::WindowResize(ref mut drag) => {
427 drag.current_position = position;
428 }
429 ActiveDragType::FileDrop(ref mut drag) => {
430 drag.position = position;
431 }
432 }
433 }
434
435 pub fn current_position(&self) -> LogicalPosition {
437 match &self.drag_type {
438 ActiveDragType::TextSelection(drag) => drag.current_mouse_position,
439 ActiveDragType::ScrollbarThumb(drag) => drag.current_mouse_position,
440 ActiveDragType::Node(drag) => drag.current_position,
441 ActiveDragType::WindowMove(drag) => drag.current_position,
442 ActiveDragType::WindowResize(drag) => drag.current_position,
443 ActiveDragType::FileDrop(drag) => drag.position,
444 }
445 }
446
447 pub fn start_position(&self) -> LogicalPosition {
449 match &self.drag_type {
450 ActiveDragType::TextSelection(drag) => drag.start_mouse_position,
451 ActiveDragType::ScrollbarThumb(drag) => drag.start_mouse_position,
452 ActiveDragType::Node(drag) => drag.start_position,
453 ActiveDragType::WindowMove(drag) => drag.start_position,
454 ActiveDragType::WindowResize(drag) => drag.start_position,
455 ActiveDragType::FileDrop(drag) => drag.position, }
457 }
458
459 pub fn is_text_selection(&self) -> bool {
461 matches!(self.drag_type, ActiveDragType::TextSelection(_))
462 }
463
464 pub fn is_scrollbar_thumb(&self) -> bool {
466 matches!(self.drag_type, ActiveDragType::ScrollbarThumb(_))
467 }
468
469 pub fn is_node_drag(&self) -> bool {
471 matches!(self.drag_type, ActiveDragType::Node(_))
472 }
473
474 pub fn is_window_move(&self) -> bool {
476 matches!(self.drag_type, ActiveDragType::WindowMove(_))
477 }
478
479 pub fn is_file_drop(&self) -> bool {
481 matches!(self.drag_type, ActiveDragType::FileDrop(_))
482 }
483
484 pub fn as_text_selection(&self) -> Option<&TextSelectionDrag> {
486 match &self.drag_type {
487 ActiveDragType::TextSelection(drag) => Some(drag),
488 _ => None,
489 }
490 }
491
492 pub fn as_text_selection_mut(&mut self) -> Option<&mut TextSelectionDrag> {
494 match &mut self.drag_type {
495 ActiveDragType::TextSelection(drag) => Some(drag),
496 _ => None,
497 }
498 }
499
500 pub fn as_scrollbar_thumb(&self) -> Option<&ScrollbarThumbDrag> {
502 match &self.drag_type {
503 ActiveDragType::ScrollbarThumb(drag) => Some(drag),
504 _ => None,
505 }
506 }
507
508 pub fn as_scrollbar_thumb_mut(&mut self) -> Option<&mut ScrollbarThumbDrag> {
510 match &mut self.drag_type {
511 ActiveDragType::ScrollbarThumb(drag) => Some(drag),
512 _ => None,
513 }
514 }
515
516 pub fn as_node_drag(&self) -> Option<&NodeDrag> {
518 match &self.drag_type {
519 ActiveDragType::Node(drag) => Some(drag),
520 _ => None,
521 }
522 }
523
524 pub fn as_node_drag_mut(&mut self) -> Option<&mut NodeDrag> {
526 match &mut self.drag_type {
527 ActiveDragType::Node(drag) => Some(drag),
528 _ => None,
529 }
530 }
531
532 pub fn as_window_move(&self) -> Option<&WindowMoveDrag> {
534 match &self.drag_type {
535 ActiveDragType::WindowMove(drag) => Some(drag),
536 _ => None,
537 }
538 }
539
540 pub fn as_file_drop(&self) -> Option<&FileDropDrag> {
542 match &self.drag_type {
543 ActiveDragType::FileDrop(drag) => Some(drag),
544 _ => None,
545 }
546 }
547
548 pub fn as_file_drop_mut(&mut self) -> Option<&mut FileDropDrag> {
550 match &mut self.drag_type {
551 ActiveDragType::FileDrop(drag) => Some(drag),
552 _ => None,
553 }
554 }
555
556 pub fn calculate_scrollbar_scroll_offset(&self) -> Option<f32> {
560 let drag = self.as_scrollbar_thumb()?;
561
562 let mouse_delta = match drag.axis {
564 ScrollbarAxis::Vertical => {
565 drag.current_mouse_position.y - drag.start_mouse_position.y
566 }
567 ScrollbarAxis::Horizontal => {
568 drag.current_mouse_position.x - drag.start_mouse_position.x
569 }
570 };
571
572 let scrollable_range = drag.content_length_px - drag.viewport_length_px;
574 if scrollable_range <= 0.0 || drag.track_length_px <= 0.0 {
575 return Some(drag.start_scroll_offset);
576 }
577
578 let thumb_length = (drag.viewport_length_px / drag.content_length_px) * drag.track_length_px;
580 let scrollable_track = drag.track_length_px - thumb_length;
581
582 if scrollable_track <= 0.0 {
583 return Some(drag.start_scroll_offset);
584 }
585
586 let scroll_ratio = mouse_delta / scrollable_track;
588 let scroll_delta = scroll_ratio * scrollable_range;
589
590 let new_offset = drag.start_scroll_offset + scroll_delta;
592
593 Some(new_offset.clamp(0.0, scrollable_range))
595 }
596
597 pub fn remap_node_ids(
603 &mut self,
604 dom_id: DomId,
605 node_id_map: &alloc::collections::BTreeMap<NodeId, NodeId>,
606 ) -> bool {
607 match &mut self.drag_type {
608 ActiveDragType::TextSelection(ref mut drag) => {
609 if drag.dom_id != dom_id {
610 return true;
611 }
612 if let Some(&new_id) = node_id_map.get(&drag.anchor_ifc_node) {
613 drag.anchor_ifc_node = new_id;
614 } else {
615 return false; }
617 if let Some(ref mut container) = drag.auto_scroll_container {
618 if let Some(&new_id) = node_id_map.get(container) {
619 *container = new_id;
620 } else {
621 drag.auto_scroll_container = None;
622 }
623 }
624 true
625 }
626 ActiveDragType::ScrollbarThumb(ref mut drag) => {
627 if let Some(&new_id) = node_id_map.get(&drag.scroll_container_node) {
628 drag.scroll_container_node = new_id;
629 true
630 } else {
631 false }
633 }
634 ActiveDragType::Node(ref mut drag) => {
635 if drag.dom_id != dom_id {
636 return true;
637 }
638 if let Some(&new_id) = node_id_map.get(&drag.node_id) {
639 drag.node_id = new_id;
640 } else {
641 return false; }
643 if let Some(dt) = drag.current_drop_target.into_option() {
645 if dt.dom == dom_id {
646 if let Some(old_nid) = dt.node.into_crate_internal() {
647 if let Some(&new_nid) = node_id_map.get(&old_nid) {
648 drag.current_drop_target = Some(DomNodeId {
649 dom: dom_id,
650 node: crate::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(new_nid)),
651 }).into();
652 } else {
653 drag.current_drop_target = OptionDomNodeId::None;
654 }
655 }
656 }
657 }
658 true
659 }
660 ActiveDragType::WindowMove(_) | ActiveDragType::WindowResize(_) => true,
662 ActiveDragType::FileDrop(ref mut drag) => {
663 if let Some(dt) = drag.drop_target.into_option() {
664 if dt.dom == dom_id {
665 if let Some(old_nid) = dt.node.into_crate_internal() {
666 if let Some(&new_nid) = node_id_map.get(&old_nid) {
667 drag.drop_target = Some(DomNodeId {
668 dom: dom_id,
669 node: crate::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(new_nid)),
670 }).into();
671 } else {
672 drag.drop_target = OptionDomNodeId::None;
673 }
674 }
675 }
676 }
677 true
678 }
679 }
680 }
681}
682
683azul_css::impl_option!(
684 DragContext,
685 OptionDragContext,
686 copy = false,
687 [Debug, Clone, PartialEq]
688);