multi_select/
multi_select.rs1use std::collections::HashSet;
29use std::path::{Path, PathBuf};
30
31use iced::keyboard::{self, Modifiers};
32use iced::widget::{Column, column, container, scrollable, text};
33use iced::{Element, Length, Subscription, Task};
34use iced_swdir_tree::{DirectoryFilter, DirectoryTree, DirectoryTreeEvent, SelectionMode};
35
36#[derive(Debug, Clone)]
37enum Message {
38 Tree(DirectoryTreeEvent),
39 ModifiersChanged(Modifiers),
40 Key(keyboard::Key, Modifiers),
41}
42
43struct App {
44 tree: DirectoryTree,
45 modifiers: Modifiers,
48}
49
50impl App {
51 fn new() -> (Self, Task<Message>) {
52 let root = std::env::args()
53 .nth(1)
54 .map(PathBuf::from)
55 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
56 let root_for_task = root.clone();
57 let tree = DirectoryTree::new(root).with_filter(DirectoryFilter::FilesAndFolders);
58 (
59 Self {
60 tree,
61 modifiers: Modifiers::default(),
62 },
63 Task::done(Message::Tree(DirectoryTreeEvent::Toggled(root_for_task))),
65 )
66 }
67
68 fn update(&mut self, message: Message) -> Task<Message> {
69 match message {
70 Message::Tree(DirectoryTreeEvent::Selected(path, is_dir, _from_view)) => {
75 let mode = SelectionMode::from_modifiers(self.modifiers);
76 let event = DirectoryTreeEvent::Selected(path, is_dir, mode);
77 self.tree.update(event).map(Message::Tree)
78 }
79 Message::Tree(event) => self.tree.update(event).map(Message::Tree),
80 Message::ModifiersChanged(m) => {
81 self.modifiers = m;
82 Task::none()
83 }
84 Message::Key(key, mods) => {
85 if let Some(event) = self.tree.handle_key(&key, mods) {
86 return self.tree.update(event).map(Message::Tree);
87 }
88 Task::none()
89 }
90 }
91 }
92
93 fn subscription(&self) -> Subscription<Message> {
94 keyboard::listen().map(|event| match event {
98 keyboard::Event::KeyPressed { key, modifiers, .. } => Message::Key(key, modifiers),
99 keyboard::Event::ModifiersChanged(modifiers) => Message::ModifiersChanged(modifiers),
100 _ => Message::Key(
103 keyboard::Key::Named(keyboard::key::Named::F35),
104 Modifiers::default(),
105 ),
106 })
107 }
108
109 fn view(&self) -> Element<'_, Message> {
110 let selected = self.tree.selected_paths();
111 let count = selected.len();
112
113 let summary_text = if count == 0 {
115 "No selection. Click to select, Shift+click for range, \
116 Ctrl/Cmd+click to toggle."
117 .to_string()
118 } else {
119 format!(
120 "{count} selected (anchor: {})",
121 self.tree
122 .anchor_path()
123 .map(short_name)
124 .unwrap_or_else(|| "-".into())
125 )
126 };
127
128 const MAX_SHOWN: usize = 10;
132 let names: HashSet<String> = selected.iter().map(|p| short_name(p)).collect();
133 let mut names_sorted: Vec<String> = names.into_iter().collect();
134 names_sorted.sort();
135 let shown: String = if names_sorted.len() <= MAX_SHOWN {
136 names_sorted.join(", ")
137 } else {
138 format!(
139 "{}, +{} more",
140 names_sorted
141 .iter()
142 .take(MAX_SHOWN)
143 .cloned()
144 .collect::<Vec<_>>()
145 .join(", "),
146 names_sorted.len() - MAX_SHOWN
147 )
148 };
149
150 let status = Column::new()
151 .push(text(summary_text).size(13))
152 .push(text(shown).size(11))
153 .spacing(2);
154
155 container(
156 column![
157 scrollable(self.tree.view(Message::Tree)).height(Length::Fill),
158 status,
159 ]
160 .spacing(8.0)
161 .padding(8.0),
162 )
163 .width(Length::Fill)
164 .height(Length::Fill)
165 .into()
166 }
167}
168
169fn short_name(p: &Path) -> String {
170 p.file_name()
171 .map(|s| s.to_string_lossy().into_owned())
172 .unwrap_or_else(|| p.display().to_string())
173}
174
175fn main() -> iced::Result {
176 iced::application(App::new, App::update, App::view)
177 .subscription(App::subscription)
178 .title("iced-swdir-tree · multi-select example")
179 .run()
180}