1use std::fs;
32use std::path::PathBuf;
33
34use iced::widget::{button, column, container, row, text, text_input};
35use iced::{Element, Length, Task};
36use iced_swdir_tree::{DirectoryFilter, DirectoryTree, DirectoryTreeEvent};
37
38#[derive(Debug, Clone)]
39enum Message {
40 Tree(DirectoryTreeEvent),
41 SearchChanged(String),
42 ExpandAll,
43}
44
45struct App {
46 tree: DirectoryTree,
47 query: String,
48}
49
50impl App {
51 fn new() -> (Self, Task<Message>) {
52 let root = resolve_root();
53 let tree = DirectoryTree::new(root.clone())
54 .with_filter(DirectoryFilter::FilesAndFolders)
55 .with_prefetch_limit(20);
58 let mut app = App {
59 tree,
60 query: String::new(),
61 };
62 let task = app
64 .tree
65 .update(DirectoryTreeEvent::Toggled(root))
66 .map(Message::Tree);
67 (app, task)
68 }
69
70 fn update(&mut self, msg: Message) -> Task<Message> {
71 match msg {
72 Message::Tree(ev) => self.tree.update(ev).map(Message::Tree),
73 Message::SearchChanged(q) => {
74 self.query = q.clone();
75 self.tree.set_search_query(q);
76 Task::none()
77 }
78 Message::ExpandAll => {
79 let mut tasks = Vec::new();
83 let to_expand = collect_collapsed_folders(&self.tree);
84 for p in to_expand {
85 tasks.push(
86 self.tree
87 .update(DirectoryTreeEvent::Toggled(p))
88 .map(Message::Tree),
89 );
90 }
91 Task::batch(tasks)
92 }
93 }
94 }
95
96 fn view(&self) -> Element<'_, Message> {
97 let search_bar = text_input("Search filenames...", &self.query)
98 .on_input(Message::SearchChanged)
99 .padding(6);
100
101 let status_text = if self.tree.is_searching() {
102 format!(
103 "{} match{} for \"{}\"",
104 self.tree.search_match_count(),
105 if self.tree.search_match_count() == 1 {
106 ""
107 } else {
108 "es"
109 },
110 self.query,
111 )
112 } else {
113 "Type above to filter. Press Expand-all to load deeper \
114 folders for broader coverage."
115 .into()
116 };
117
118 let controls = row![
119 search_bar,
120 button(text("Expand all")).on_press(Message::ExpandAll),
121 ]
122 .spacing(8);
123
124 column![
125 controls,
126 container(self.tree.view(Message::Tree))
127 .width(Length::Fill)
128 .height(Length::Fill),
129 text(status_text).size(13),
130 ]
131 .spacing(8)
132 .padding(10)
133 .into()
134 }
135}
136
137fn collect_collapsed_folders(tree: &DirectoryTree) -> Vec<PathBuf> {
141 let mut out = Vec::new();
147 fn recurse(p: &std::path::Path, out: &mut Vec<PathBuf>) {
148 if !p.is_dir() {
149 return;
150 }
151 out.push(p.to_path_buf());
152 if let Ok(read) = fs::read_dir(p) {
153 for entry in read.flatten() {
154 let ep = entry.path();
155 if ep.is_dir() {
156 recurse(&ep, out);
157 }
158 }
159 }
160 }
161 recurse(tree.root_path(), &mut out);
162 out
163}
164
165fn resolve_root() -> PathBuf {
166 if let Some(arg) = std::env::args().nth(1) {
167 return PathBuf::from(arg);
168 }
169 let scratch = std::env::temp_dir().join("iced-swdir-tree-search-demo");
170 let _ = fs::create_dir_all(&scratch);
171 for dir in &[
173 "project",
174 "project/src",
175 "project/src/lib",
176 "project/tests",
177 "notes",
178 "notes/ideas",
179 ] {
180 let _ = fs::create_dir_all(scratch.join(dir));
181 }
182 for (path, body) in &[
183 ("project/README.md", "# Project\n"),
184 ("project/src/main.rs", "fn main() {}\n"),
185 ("project/src/lib/config.rs", ""),
186 ("project/src/lib/parser.rs", ""),
187 ("project/tests/integration.rs", ""),
188 ("project/tests/readme.md", "test notes\n"),
189 ("notes/todo.md", "- buy milk\n"),
190 ("notes/ideas/app_idea.md", ""),
191 ("notes/ideas/README.md", ""),
192 ("scratch_note.txt", ""),
193 ] {
194 let p = scratch.join(path);
195 if !p.exists() {
196 let _ = fs::write(p, body);
197 }
198 }
199 scratch
200}
201
202fn main() -> iced::Result {
203 iced::application(App::new, App::update, App::view)
204 .title("iced-swdir-tree · search example")
205 .run()
206}