iced_swdir_tree/directory_tree/selection.rs
1//! Selection-mode type for click and keyboard interactions.
2//!
3//! [`SelectionMode`] controls how a new selection event composes
4//! with the existing [`DirectoryTree`](crate::DirectoryTree)
5//! selection state:
6//!
7//! | Mode | Effect on the selected set |
8//! |-------------------------|----------------------------|
9//! | [`SelectionMode::Replace`] | Clear everything; the path becomes the single selection. |
10//! | [`SelectionMode::Toggle`] | Add the path if absent; remove it if present. |
11//! | [`SelectionMode::ExtendRange`] | Select every visible row between the anchor and the target, inclusive. The anchor is unchanged. |
12//!
13//! The built-in view emits clicks as [`SelectionMode::Replace`]
14//! because iced 0.14's `button::on_press` callback cannot observe
15//! modifier keys at press time. Applications that want multi-select
16//! track modifier state themselves (see `examples/multi_select.rs`)
17//! and rewrite the mode before forwarding the event to
18//! [`DirectoryTree::update`](crate::DirectoryTree::update).
19//!
20//! [`from_modifiers`](SelectionMode::from_modifiers) exists to make
21//! that rewrite trivial.
22
23use iced::keyboard::Modifiers;
24
25/// How an incoming selection event composes with the existing set.
26///
27/// The three modes cover the three standard click idioms:
28///
29/// | Mode | Effect on the selected set |
30/// |-------------------------|----------------------------|
31/// | [`SelectionMode::Replace`] | Clear everything; the path becomes the single selection. |
32/// | [`SelectionMode::Toggle`] | Add the path if absent; remove it if present. |
33/// | [`SelectionMode::ExtendRange`] | Select every visible row between the anchor and the target, inclusive. The anchor is unchanged. |
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
35pub enum SelectionMode {
36 /// Clear any existing selection; the new path becomes the only
37 /// selected entry. This is what a plain left-click produces
38 /// from the built-in view.
39 #[default]
40 Replace,
41
42 /// Add the path to the selection if it isn't already there,
43 /// remove it otherwise. The anchor used for range extension is
44 /// updated to point at this path regardless of whether it was
45 /// added or removed.
46 Toggle,
47
48 /// Select every visible row between the current anchor and the
49 /// target path, inclusive, using the row order the widget
50 /// renders. The anchor itself is **not** moved — successive
51 /// range extensions all use the same starting pivot, which
52 /// matches how Windows Explorer, macOS Finder, and VS Code
53 /// behave.
54 ///
55 /// If the anchor is unset, or if either the anchor or the
56 /// target is not currently visible (filtered out, ancestor
57 /// collapsed, not yet loaded), the tree falls back to
58 /// [`SelectionMode::Replace`] semantics using just the target.
59 ExtendRange,
60}
61
62impl SelectionMode {
63 /// Pick the mode implied by the active modifier keys.
64 ///
65 /// The mapping is:
66 ///
67 /// * `Shift` → [`SelectionMode::ExtendRange`]
68 /// * `Ctrl` or `Logo` (Command on macOS / Windows key elsewhere) → [`SelectionMode::Toggle`]
69 /// * otherwise → [`SelectionMode::Replace`]
70 ///
71 /// `Shift` wins over `Ctrl` / `Logo` when both are held. A
72 /// future release may distinguish "extend with toggle" (the
73 /// Windows Explorer `Ctrl+Shift+click` behavior) with a fourth
74 /// variant; for now `Ctrl+Shift+click` behaves as Shift-click.
75 ///
76 /// # Example
77 ///
78 /// ```ignore
79 /// use iced::keyboard::Modifiers;
80 /// use iced_swdir_tree::SelectionMode;
81 ///
82 /// // In your update handler, when you've received a click event
83 /// // and have tracked the current modifier state:
84 /// let mode = SelectionMode::from_modifiers(current_modifiers);
85 /// // Forward a rewritten Selected event to the widget.
86 /// ```
87 pub fn from_modifiers(modifiers: Modifiers) -> Self {
88 if modifiers.shift() {
89 Self::ExtendRange
90 } else if modifiers.control() || modifiers.logo() {
91 Self::Toggle
92 } else {
93 Self::Replace
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests;