iced_swdir_tree/directory_tree/message.rs
1//! Message types.
2//!
3//! [`DirectoryTreeEvent`] is the single message type flowing in and out
4//! of the widget. Two variants are user-facing (`Toggled`, `Selected`);
5//! a third (`Loaded`) carries async scan results and is opaque — parent
6//! applications pass it through `update` without needing to introspect
7//! it.
8
9use std::path::PathBuf;
10use std::sync::Arc;
11
12use super::drag::DragMsg;
13use super::node::LoadedEntry;
14use super::selection::SelectionMode;
15
16/// A message emitted by or consumed by the [`DirectoryTree`](crate::DirectoryTree) widget.
17///
18/// ## For parent applications
19///
20/// Wrap this in one of your own `Message` variants:
21///
22/// ```ignore
23/// enum MyMessage {
24/// Tree(iced_swdir_tree::DirectoryTreeEvent),
25/// // ...
26/// }
27/// ```
28///
29/// Route every `Tree(event)` to [`DirectoryTree::update`] and map its
30/// returned `Task` back. Pattern-match on `Toggled` / `Selected` *before*
31/// forwarding if you want app-level side effects (e.g. previewing the
32/// selected file):
33///
34/// ```ignore
35/// fn update(&mut self, msg: MyMessage) -> Task<MyMessage> {
36/// match msg {
37/// MyMessage::Tree(event) => {
38/// if let DirectoryTreeEvent::Selected(path, _, _) = &event {
39/// self.preview(path);
40/// }
41/// self.tree.update(event).map(MyMessage::Tree)
42/// }
43/// }
44/// }
45/// ```
46///
47/// [`DirectoryTree::update`]: crate::DirectoryTree::update
48#[derive(Debug, Clone)]
49pub enum DirectoryTreeEvent {
50 /// A folder was toggled open/closed by the user.
51 ///
52 /// On first expansion the widget issues an async scan whose result
53 /// arrives later as [`DirectoryTreeEvent::Loaded`]. Subsequent
54 /// toggles of the same folder are instant — children stay in the
55 /// internal cache.
56 Toggled(PathBuf),
57
58 /// A row was selected.
59 ///
60 /// The `bool` indicates whether the path is a directory (`true`)
61 /// or a file (`false`). The [`SelectionMode`] controls how the
62 /// click composes with any existing selection — see its docs for
63 /// the full matrix.
64 ///
65 /// The built-in view always emits this with
66 /// [`SelectionMode::Replace`] because iced 0.14's button
67 /// callbacks cannot observe modifier keys at press time.
68 /// Applications that want multi-select track modifier state
69 /// themselves (see `examples/multi_select.rs`) and rewrite the
70 /// mode before forwarding the event — [`SelectionMode::from_modifiers`]
71 /// makes that a one-liner.
72 Selected(PathBuf, bool, SelectionMode),
73
74 /// Internal drag-machinery event.
75 ///
76 /// Emitted by the widget's built-in view as the user presses a
77 /// row, moves across others, and releases. Applications should
78 /// treat these as opaque and always route them back to
79 /// [`DirectoryTree::update`](crate::DirectoryTree::update) —
80 /// just like [`Loaded`](Self::Loaded).
81 ///
82 /// The widget's internal state machine may produce a
83 /// [`Selected`](Self::Selected) or a [`DragCompleted`](Self::DragCompleted)
84 /// as the downstream effect of a [`Drag`](Self::Drag) message.
85 /// Apps observe those downstream events via the usual
86 /// `.map(MyMessage::Tree)` routing — no extra plumbing needed.
87 Drag(DragMsg),
88
89 /// The user completed a drag gesture with intent to move (or
90 /// otherwise transplant) `sources` into `destination`.
91 ///
92 /// The widget performs **no filesystem operation** on its own.
93 /// Applications observe this event, perform whatever action
94 /// they wish (move, copy, symlink, upload, ignore), and
95 /// re-scan affected folders by emitting `Toggled` events
96 /// (collapse then re-expand) to refresh the tree view.
97 ///
98 /// `destination` is guaranteed to be a directory that is not
99 /// itself in `sources` nor a descendant of any source — see
100 /// [`DragMsg`](crate::DragMsg) for the validity rules.
101 /// `sources` is non-empty.
102 DragCompleted {
103 /// One or more paths the user started dragging. This is
104 /// the [selected set](crate::DirectoryTree::selected_paths)
105 /// at drag start if the pressed row was in the selection,
106 /// otherwise just the pressed row.
107 sources: Vec<PathBuf>,
108 /// The folder over which the user released the mouse.
109 destination: PathBuf,
110 },
111
112 /// Internal: an asynchronous scan completed.
113 ///
114 /// Parent applications should not construct this variant themselves;
115 /// it is produced by `iced::Task`s that [`DirectoryTree::update`]
116 /// returns and is routed back to `update` through the app's message
117 /// plumbing. Treat it as opaque.
118 ///
119 /// [`DirectoryTree::update`]: crate::DirectoryTree::update
120 Loaded(LoadPayload),
121}
122
123/// The payload of [`DirectoryTreeEvent::Loaded`].
124///
125/// The fields are crate-private so the internal representation can
126/// evolve without breaking callers — `Clone` / `Debug` are sufficient
127/// for anything a parent application needs to do with the message.
128#[derive(Debug, Clone)]
129pub struct LoadPayload {
130 /// Directory whose scan completed.
131 pub(crate) path: PathBuf,
132 /// Generation counter snapshot taken when the scan was issued.
133 /// Used to drop stale results if the user has since collapsed and
134 /// re-expanded the folder.
135 pub(crate) generation: u64,
136 /// Depth of `path` relative to the tree's root — kept here so the
137 /// update layer doesn't have to re-walk the tree to find it, and
138 /// reserved for future per-depth UI feedback (e.g. showing a
139 /// "reached max depth" indicator on the triggering node).
140 #[allow(dead_code)]
141 pub(crate) depth: u32,
142 /// The scan outcome.
143 ///
144 /// `Arc` keeps the message cheaply cloneable even when the entry
145 /// vector is large — iced passes messages by value and clones them
146 /// en route to subscribers.
147 pub(crate) result: Arc<Result<Vec<LoadedEntry>, crate::Error>>,
148}