Skip to main content

azul_core/
drag.rs

1//! Unified drag context for all drag operations.
2//!
3//! This module provides a single, coherent way to handle all drag operations:
4//! - Text selection drag
5//! - Scrollbar thumb drag
6//! - Node drag-and-drop
7//! - Window drag/resize
8//! - File drop from OS
9//!
10//! The `DragContext` struct tracks the current drag state and provides
11//! a unified interface for event processing.
12
13use 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/// Type of the active drag operation.
24///
25/// This enum unifies all drag types into a single discriminated union,
26/// making it easy to handle different drag behaviors in one place.
27#[derive(Debug, Clone, PartialEq)]
28#[repr(C, u8)]
29pub enum ActiveDragType {
30    /// Text selection drag - user is selecting text by dragging
31    TextSelection(TextSelectionDrag),
32    /// Scrollbar thumb drag - user is dragging a scrollbar thumb
33    ScrollbarThumb(ScrollbarThumbDrag),
34    /// Node drag-and-drop - user is dragging a DOM node
35    Node(NodeDrag),
36    /// Window drag - user is moving the window (titlebar drag)
37    WindowMove(WindowMoveDrag),
38    /// Window resize - user is resizing the window (edge/corner drag)
39    WindowResize(WindowResizeDrag),
40    /// File drop from OS - user is dragging file(s) from the OS
41    FileDrop(FileDropDrag),
42}
43
44/// Text selection drag state.
45///
46/// Tracks the anchor point (where selection started) and current position.
47#[derive(Debug, Clone, PartialEq)]
48#[repr(C)]
49pub struct TextSelectionDrag {
50    /// DOM ID where the selection started
51    pub dom_id: DomId,
52    /// The IFC root node where selection started (e.g., <p> element)
53    pub anchor_ifc_node: NodeId,
54    /// The anchor cursor position (fixed during drag)
55    pub anchor_cursor: Option<TextCursor>,
56    /// Mouse position where drag started
57    pub start_mouse_position: LogicalPosition,
58    /// Current mouse position
59    pub current_mouse_position: LogicalPosition,
60    /// Whether we should auto-scroll (mouse near edge of scroll container)
61    pub auto_scroll_direction: AutoScrollDirection,
62    /// The scroll container that should be auto-scrolled (if any)
63    pub auto_scroll_container: Option<NodeId>,
64}
65
66/// Scrollbar thumb drag state.
67///
68/// Tracks which scrollbar is being dragged and the current offset.
69#[derive(Debug, Clone, Copy, PartialEq)]
70#[repr(C)]
71pub struct ScrollbarThumbDrag {
72    /// The scroll container node being scrolled
73    pub scroll_container_node: NodeId,
74    /// Whether this is the vertical or horizontal scrollbar
75    pub axis: ScrollbarAxis,
76    /// Mouse Y position where drag started (for calculating delta)
77    pub start_mouse_position: LogicalPosition,
78    /// Scroll offset when drag started
79    pub start_scroll_offset: f32,
80    /// Current mouse position
81    pub current_mouse_position: LogicalPosition,
82    /// Track length in pixels (for calculating scroll ratio)
83    pub track_length_px: f32,
84    /// Content length in pixels (for calculating scroll ratio)
85    pub content_length_px: f32,
86    /// Viewport length in pixels (for calculating scroll ratio)
87    pub viewport_length_px: f32,
88}
89
90/// Which scrollbar axis is being dragged
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92#[repr(C)]
93pub enum ScrollbarAxis {
94    Vertical,
95    Horizontal,
96}
97
98/// Node drag-and-drop state.
99///
100/// Tracks a DOM node being dragged for reordering or moving.
101#[derive(Debug, Clone, PartialEq)]
102#[repr(C)]
103pub struct NodeDrag {
104    /// DOM ID of the node being dragged
105    pub dom_id: DomId,
106    /// Node ID being dragged
107    pub node_id: NodeId,
108    /// Position where drag started
109    pub start_position: LogicalPosition,
110    /// Current drag position
111    pub current_position: LogicalPosition,
112    /// Optional: DOM node currently under cursor (drop target)
113    pub current_drop_target: OptionDomNodeId,
114    /// Drag data (MIME types and content)
115    pub drag_data: DragData,
116}
117
118/// Window move drag state.
119///
120/// Tracks the window being moved via titlebar drag.
121#[derive(Debug, Clone, PartialEq)]
122#[repr(C)]
123pub struct WindowMoveDrag {
124    /// Position where window drag started (in screen coordinates)
125    pub start_position: LogicalPosition,
126    /// Current drag position
127    pub current_position: LogicalPosition,
128    /// Initial window position before drag
129    pub initial_window_position: WindowPosition,
130}
131
132/// Window resize drag state.
133///
134/// Tracks the window being resized via edge/corner drag.
135#[derive(Debug, Clone, Copy, PartialEq)]
136#[repr(C)]
137pub struct WindowResizeDrag {
138    /// Which edge/corner is being dragged
139    pub edge: WindowResizeEdge,
140    /// Position where resize started
141    pub start_position: LogicalPosition,
142    /// Current drag position
143    pub current_position: LogicalPosition,
144    /// Initial window size before resize
145    pub initial_width: u32,
146    /// Initial window height before resize
147    pub initial_height: u32,
148}
149
150/// Which edge or corner of the window is being resized
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152#[repr(C)]
153pub enum WindowResizeEdge {
154    Top,
155    Bottom,
156    Left,
157    Right,
158    TopLeft,
159    TopRight,
160    BottomLeft,
161    BottomRight,
162}
163
164/// File drop from OS drag state.
165///
166/// Tracks files being dragged from the operating system.
167#[derive(Debug, Clone, PartialEq)]
168#[repr(C)]
169pub struct FileDropDrag {
170    /// Files being dragged (as string paths)
171    pub files: StringVec,
172    /// Current position of drag cursor
173    pub position: LogicalPosition,
174    /// DOM node under cursor (potential drop target)
175    pub drop_target: OptionDomNodeId,
176    /// Allowed drop effect
177    pub drop_effect: DropEffect,
178}
179
180/// Direction for auto-scrolling during drag operations
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
182#[repr(C)]
183pub enum AutoScrollDirection {
184    /// No auto-scroll needed
185    #[default]
186    None,
187    /// Scroll up (mouse near top edge)
188    Up,
189    /// Scroll down (mouse near bottom edge)
190    Down,
191    /// Scroll left (mouse near left edge)
192    Left,
193    /// Scroll right (mouse near right edge)
194    Right,
195    /// Scroll up-left (mouse near top-left corner)
196    UpLeft,
197    /// Scroll up-right (mouse near top-right corner)
198    UpRight,
199    /// Scroll down-left (mouse near bottom-left corner)
200    DownLeft,
201    /// Scroll down-right (mouse near bottom-right corner)
202    DownRight,
203}
204
205/// Drop effect (what happens when dropped)
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
207#[repr(C)]
208pub enum DropEffect {
209    /// No effect
210    #[default]
211    None,
212    /// Copy the data
213    Copy,
214    /// Move the data
215    Move,
216    /// Create link
217    Link,
218}
219
220/// Drag data (like HTML5 DataTransfer)
221#[derive(Debug, Clone, PartialEq, Default)]
222pub struct DragData {
223    /// MIME type -> data mapping
224    ///
225    /// e.g., "text/plain" -> "Hello World"
226    pub data: BTreeMap<AzString, Vec<u8>>,
227    /// Allowed drag operations
228    pub effect_allowed: DragEffect,
229}
230
231/// Drag/drop effect (like HTML5 dropEffect)
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
233#[repr(C)]
234pub enum DragEffect {
235    /// No drop allowed
236    #[default]
237    None,
238    /// Copy operation
239    Copy,
240    /// Move operation
241    Move,
242    /// Link/shortcut operation
243    Link,
244}
245
246impl DragData {
247    /// Create new empty drag data
248    pub fn new() -> Self {
249        Self {
250            data: BTreeMap::new(),
251            effect_allowed: DragEffect::Copy,
252        }
253    }
254
255    /// Set data for a MIME type
256    pub fn set_data(&mut self, mime_type: impl Into<AzString>, data: Vec<u8>) {
257        self.data.insert(mime_type.into(), data);
258    }
259
260    /// Get data for a MIME type
261    pub fn get_data(&self, mime_type: &str) -> Option<&[u8]> {
262        self.data.get(&AzString::from(mime_type)).map(|v| v.as_slice())
263    }
264
265    /// Set plain text data
266    pub fn set_text(&mut self, text: impl Into<AzString>) {
267        let text_str = text.into();
268        self.set_data("text/plain", text_str.as_str().as_bytes().to_vec());
269    }
270
271    /// Get plain text data
272    pub fn get_text(&self) -> Option<AzString> {
273        self.get_data("text/plain")
274            .map(|bytes| AzString::from(core::str::from_utf8(bytes).unwrap_or("")))
275    }
276}
277
278/// The unified drag context.
279///
280/// This struct wraps `ActiveDragType` and provides common metadata
281/// that applies to all drag operations.
282#[derive(Debug, Clone, PartialEq)]
283pub struct DragContext {
284    /// The specific type of drag operation
285    pub drag_type: ActiveDragType,
286    /// Session ID from gesture detection (links back to GestureManager)
287    pub session_id: u64,
288    /// Whether the drag has been cancelled (e.g., Escape pressed)
289    pub cancelled: bool,
290}
291
292impl DragContext {
293    /// Create a new drag context
294    pub fn new(drag_type: ActiveDragType, session_id: u64) -> Self {
295        Self {
296            drag_type,
297            session_id,
298            cancelled: false,
299        }
300    }
301
302    /// Create a text selection drag
303    pub fn text_selection(
304        dom_id: DomId,
305        anchor_ifc_node: NodeId,
306        start_mouse_position: LogicalPosition,
307        session_id: u64,
308    ) -> Self {
309        Self::new(
310            ActiveDragType::TextSelection(TextSelectionDrag {
311                dom_id,
312                anchor_ifc_node,
313                anchor_cursor: None,
314                start_mouse_position,
315                current_mouse_position: start_mouse_position,
316                auto_scroll_direction: AutoScrollDirection::None,
317                auto_scroll_container: None,
318            }),
319            session_id,
320        )
321    }
322
323    /// Create a scrollbar thumb drag
324    pub fn scrollbar_thumb(
325        scroll_container_node: NodeId,
326        axis: ScrollbarAxis,
327        start_mouse_position: LogicalPosition,
328        start_scroll_offset: f32,
329        track_length_px: f32,
330        content_length_px: f32,
331        viewport_length_px: f32,
332        session_id: u64,
333    ) -> Self {
334        Self::new(
335            ActiveDragType::ScrollbarThumb(ScrollbarThumbDrag {
336                scroll_container_node,
337                axis,
338                start_mouse_position,
339                start_scroll_offset,
340                current_mouse_position: start_mouse_position,
341                track_length_px,
342                content_length_px,
343                viewport_length_px,
344            }),
345            session_id,
346        )
347    }
348
349    /// Create a node drag
350    pub fn node_drag(
351        dom_id: DomId,
352        node_id: NodeId,
353        start_position: LogicalPosition,
354        drag_data: DragData,
355        session_id: u64,
356    ) -> Self {
357        Self::new(
358            ActiveDragType::Node(NodeDrag {
359                dom_id,
360                node_id,
361                start_position,
362                current_position: start_position,
363                current_drop_target: OptionDomNodeId::None,
364                drag_data,
365            }),
366            session_id,
367        )
368    }
369
370    /// Create a window move drag
371    pub fn window_move(
372        start_position: LogicalPosition,
373        initial_window_position: WindowPosition,
374        session_id: u64,
375    ) -> Self {
376        Self::new(
377            ActiveDragType::WindowMove(WindowMoveDrag {
378                start_position,
379                current_position: start_position,
380                initial_window_position,
381            }),
382            session_id,
383        )
384    }
385
386    /// Create a file drop drag
387    pub fn file_drop(files: Vec<AzString>, position: LogicalPosition, session_id: u64) -> Self {
388        Self::new(
389            ActiveDragType::FileDrop(FileDropDrag {
390                files: files.into(),
391                position,
392                drop_target: OptionDomNodeId::None,
393                drop_effect: DropEffect::Copy,
394            }),
395            session_id,
396        )
397    }
398
399    /// Update the current mouse position for all drag types
400    pub fn update_position(&mut self, position: LogicalPosition) {
401        match &mut self.drag_type {
402            ActiveDragType::TextSelection(ref mut drag) => {
403                drag.current_mouse_position = position;
404            }
405            ActiveDragType::ScrollbarThumb(ref mut drag) => {
406                drag.current_mouse_position = position;
407            }
408            ActiveDragType::Node(ref mut drag) => {
409                drag.current_position = position;
410            }
411            ActiveDragType::WindowMove(ref mut drag) => {
412                drag.current_position = position;
413            }
414            ActiveDragType::WindowResize(ref mut drag) => {
415                drag.current_position = position;
416            }
417            ActiveDragType::FileDrop(ref mut drag) => {
418                drag.position = position;
419            }
420        }
421    }
422
423    /// Get the current mouse position
424    pub fn current_position(&self) -> LogicalPosition {
425        match &self.drag_type {
426            ActiveDragType::TextSelection(drag) => drag.current_mouse_position,
427            ActiveDragType::ScrollbarThumb(drag) => drag.current_mouse_position,
428            ActiveDragType::Node(drag) => drag.current_position,
429            ActiveDragType::WindowMove(drag) => drag.current_position,
430            ActiveDragType::WindowResize(drag) => drag.current_position,
431            ActiveDragType::FileDrop(drag) => drag.position,
432        }
433    }
434
435    /// Get the start position
436    pub fn start_position(&self) -> LogicalPosition {
437        match &self.drag_type {
438            ActiveDragType::TextSelection(drag) => drag.start_mouse_position,
439            ActiveDragType::ScrollbarThumb(drag) => drag.start_mouse_position,
440            ActiveDragType::Node(drag) => drag.start_position,
441            ActiveDragType::WindowMove(drag) => drag.start_position,
442            ActiveDragType::WindowResize(drag) => drag.start_position,
443            ActiveDragType::FileDrop(drag) => drag.position, // No start for file drops
444        }
445    }
446
447    /// Check if this is a text selection drag
448    pub fn is_text_selection(&self) -> bool {
449        matches!(self.drag_type, ActiveDragType::TextSelection(_))
450    }
451
452    /// Check if this is a scrollbar thumb drag
453    pub fn is_scrollbar_thumb(&self) -> bool {
454        matches!(self.drag_type, ActiveDragType::ScrollbarThumb(_))
455    }
456
457    /// Check if this is a node drag
458    pub fn is_node_drag(&self) -> bool {
459        matches!(self.drag_type, ActiveDragType::Node(_))
460    }
461
462    /// Check if this is a window move drag
463    pub fn is_window_move(&self) -> bool {
464        matches!(self.drag_type, ActiveDragType::WindowMove(_))
465    }
466
467    /// Check if this is a file drop
468    pub fn is_file_drop(&self) -> bool {
469        matches!(self.drag_type, ActiveDragType::FileDrop(_))
470    }
471
472    /// Get as text selection drag (if applicable)
473    pub fn as_text_selection(&self) -> Option<&TextSelectionDrag> {
474        match &self.drag_type {
475            ActiveDragType::TextSelection(drag) => Some(drag),
476            _ => None,
477        }
478    }
479
480    /// Get as mutable text selection drag (if applicable)
481    pub fn as_text_selection_mut(&mut self) -> Option<&mut TextSelectionDrag> {
482        match &mut self.drag_type {
483            ActiveDragType::TextSelection(drag) => Some(drag),
484            _ => None,
485        }
486    }
487
488    /// Get as scrollbar thumb drag (if applicable)
489    pub fn as_scrollbar_thumb(&self) -> Option<&ScrollbarThumbDrag> {
490        match &self.drag_type {
491            ActiveDragType::ScrollbarThumb(drag) => Some(drag),
492            _ => None,
493        }
494    }
495
496    /// Get as mutable scrollbar thumb drag (if applicable)
497    pub fn as_scrollbar_thumb_mut(&mut self) -> Option<&mut ScrollbarThumbDrag> {
498        match &mut self.drag_type {
499            ActiveDragType::ScrollbarThumb(drag) => Some(drag),
500            _ => None,
501        }
502    }
503
504    /// Get as node drag (if applicable)
505    pub fn as_node_drag(&self) -> Option<&NodeDrag> {
506        match &self.drag_type {
507            ActiveDragType::Node(drag) => Some(drag),
508            _ => None,
509        }
510    }
511
512    /// Get as mutable node drag (if applicable)
513    pub fn as_node_drag_mut(&mut self) -> Option<&mut NodeDrag> {
514        match &mut self.drag_type {
515            ActiveDragType::Node(drag) => Some(drag),
516            _ => None,
517        }
518    }
519
520    /// Get as window move drag (if applicable)
521    pub fn as_window_move(&self) -> Option<&WindowMoveDrag> {
522        match &self.drag_type {
523            ActiveDragType::WindowMove(drag) => Some(drag),
524            _ => None,
525        }
526    }
527
528    /// Get as file drop (if applicable)
529    pub fn as_file_drop(&self) -> Option<&FileDropDrag> {
530        match &self.drag_type {
531            ActiveDragType::FileDrop(drag) => Some(drag),
532            _ => None,
533        }
534    }
535
536    /// Get as mutable file drop (if applicable)
537    pub fn as_file_drop_mut(&mut self) -> Option<&mut FileDropDrag> {
538        match &mut self.drag_type {
539            ActiveDragType::FileDrop(drag) => Some(drag),
540            _ => None,
541        }
542    }
543
544    /// Calculate scroll delta for scrollbar thumb drag
545    ///
546    /// Returns the new scroll offset based on current mouse position.
547    pub fn calculate_scrollbar_scroll_offset(&self) -> Option<f32> {
548        let drag = self.as_scrollbar_thumb()?;
549        
550        // Calculate mouse delta along the drag axis
551        let mouse_delta = match drag.axis {
552            ScrollbarAxis::Vertical => {
553                drag.current_mouse_position.y - drag.start_mouse_position.y
554            }
555            ScrollbarAxis::Horizontal => {
556                drag.current_mouse_position.x - drag.start_mouse_position.x
557            }
558        };
559
560        // Calculate the scrollable range
561        let scrollable_range = drag.content_length_px - drag.viewport_length_px;
562        if scrollable_range <= 0.0 || drag.track_length_px <= 0.0 {
563            return Some(drag.start_scroll_offset);
564        }
565
566        // Calculate thumb length (proportional to viewport/content ratio)
567        let thumb_length = (drag.viewport_length_px / drag.content_length_px) * drag.track_length_px;
568        let scrollable_track = drag.track_length_px - thumb_length;
569
570        if scrollable_track <= 0.0 {
571            return Some(drag.start_scroll_offset);
572        }
573
574        // Convert mouse delta to scroll delta
575        let scroll_ratio = mouse_delta / scrollable_track;
576        let scroll_delta = scroll_ratio * scrollable_range;
577
578        // Calculate new scroll offset
579        let new_offset = drag.start_scroll_offset + scroll_delta;
580
581        // Clamp to valid range
582        Some(new_offset.clamp(0.0, scrollable_range))
583    }
584}
585
586azul_css::impl_option!(
587    DragContext,
588    OptionDragContext,
589    copy = false,
590    [Debug, Clone, PartialEq]
591);