dioxus_swdir_tree_core/drag.rs
1//! Drag-and-drop state and messages for [`crate::DirectoryTree`].
2//!
3//! The widget tracks mouse press → hover → release and emits
4//! [`DragOutcome::Completed`] on a valid drop. **No filesystem operations
5//! are ever performed** — moving, copying, or rejecting the drop is the
6//! application's decision (S7.5).
7//!
8//! ## Flow
9//!
10//! ```text
11//! onmousedown → DragMsg::Pressed → drag becomes active
12//! onmouseenter → DragMsg::Entered → hovered_target set iff valid
13//! onmouseleave → DragMsg::Exited → hovered_target cleared
14//! onmouseup → DragMsg::Released → click (S7.2) or DragCompleted
15//! Escape key → DragMsg::Cancelled → drag cleared
16//! ```
17//!
18//! The view crate calls [`crate::DirectoryTree::on_drag_msg`] with each
19//! message and handles the returned [`DragOutcome`].
20
21use std::path::PathBuf;
22
23/// Active drag session.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct DragState {
26 /// Paths being dragged. If `started_at ∈ selected_paths` at press
27 /// time, this is a clone of the full selection; otherwise `[started_at]`.
28 pub sources: Vec<PathBuf>,
29 /// The current valid drop target, if any. Only a directory that is
30 /// neither a source nor a descendant of a source qualifies (S7.3).
31 pub hovered_target: Option<PathBuf>,
32 /// The row where the mouse was pressed.
33 pub started_at: PathBuf,
34 /// Whether `started_at` is a directory. Used to emit the correct
35 /// `Selected` event in the click case (S7.2).
36 pub started_is_dir: bool,
37}
38
39/// A drag gesture event sent by the view to
40/// [`crate::DirectoryTree::on_drag_msg`].
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum DragMsg {
43 /// Mouse pressed on a row — starts a drag session.
44 Pressed { path: PathBuf, is_dir: bool },
45 /// Mouse entered a row during an active drag.
46 Entered(PathBuf),
47 /// Mouse left a row during an active drag.
48 Exited(PathBuf),
49 /// Mouse released over a row. If the path equals `started_at` this
50 /// was a click (S7.2); otherwise it is a genuine drop.
51 Released(PathBuf),
52 /// Drag explicitly cancelled (Escape key or mouse-up with no target).
53 Cancelled,
54}
55
56/// The side effect produced by [`crate::DirectoryTree::on_drag_msg`].
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum DragOutcome {
59 /// Nothing to do.
60 None,
61 /// This was a click (press and release on the same row, S7.2).
62 /// The host should call `tree.on_selected(&path, is_dir, Replace)`.
63 Clicked { path: PathBuf, is_dir: bool },
64 /// A genuine drop. The host performs the move/copy/upload.
65 /// The widget does NOT refresh automatically afterwards (S7.5).
66 Completed {
67 sources: Vec<PathBuf>,
68 destination: PathBuf,
69 },
70}
71
72// ── Target validity ───────────────────────────────────────────────────────────
73
74/// `true` iff `path` is a valid drop target given the current drag state
75/// and the tree's node graph (S7.3).
76///
77/// A path is valid iff:
78/// 1. It is a directory currently present in the tree.
79/// 2. It is not a drag source.
80/// 3. It is not a descendant of any drag source (component-wise prefix,
81/// never a bare string prefix).
82pub(crate) fn is_valid_target(path: &std::path::Path, sources: &[PathBuf], is_dir: bool) -> bool {
83 if !is_dir {
84 return false;
85 }
86 // `path.starts_with(s)` uses component-wise comparison — it returns
87 // true for `s` itself AND all of its descendants, which handles both
88 // conditions 2 and 3 in one check.
89 !sources.iter().any(|s| path.starts_with(s))
90}