rgpui_component/searchable_list/
vec.rs1use rgpui::{App, SharedString, Task, Window};
2
3use crate::IndexPath;
4
5use super::delegate::{SearchableListDelegate, SearchableListItem};
6
7impl SearchableListItem for String {
10 type Value = Self;
11
12 fn title(&self) -> SharedString {
13 SharedString::from(self.clone())
14 }
15
16 fn value(&self) -> &Self::Value {
17 self
18 }
19}
20
21impl SearchableListItem for SharedString {
22 type Value = Self;
23
24 fn title(&self) -> SharedString {
25 self.clone()
26 }
27
28 fn value(&self) -> &Self::Value {
29 self
30 }
31}
32
33impl SearchableListItem for &'static str {
34 type Value = Self;
35
36 fn title(&self) -> SharedString {
37 SharedString::from(*self)
38 }
39
40 fn value(&self) -> &Self::Value {
41 self
42 }
43}
44
45impl<T: SearchableListItem + 'static> SearchableListDelegate for Vec<T> {
48 type Item = T;
49
50 fn items_count(&self, _: usize) -> usize {
51 self.len()
52 }
53
54 fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
55 self.as_slice().get(ix.row)
56 }
57
58 fn position<V>(&self, value: &V) -> Option<IndexPath>
59 where
60 Self::Item: SearchableListItem<Value = V>,
61 V: PartialEq,
62 {
63 self.iter()
64 .position(|v| v.value() == value)
65 .map(|ix| IndexPath::default().row(ix))
66 }
67}
68
69#[derive(Debug, Clone)]
76pub struct SearchableVec<T> {
77 items: Vec<T>,
78 matched_items: Vec<T>,
79}
80
81impl<T: Clone> SearchableVec<T> {
82 pub fn new(items: impl Into<Vec<T>>) -> Self {
84 let items = items.into();
85
86 Self {
87 items: items.clone(),
88 matched_items: items,
89 }
90 }
91
92 pub fn push(&mut self, item: T) {
94 self.items.push(item.clone());
95 self.matched_items.push(item);
96 }
97}
98
99impl<T: SearchableListItem> From<Vec<T>> for SearchableVec<T> {
100 fn from(items: Vec<T>) -> Self {
101 Self {
102 items: items.clone(),
103 matched_items: items,
104 }
105 }
106}
107
108impl<I: SearchableListItem + 'static> SearchableListDelegate for SearchableVec<I> {
109 type Item = I;
110
111 fn items_count(&self, _: usize) -> usize {
112 self.matched_items.len()
113 }
114
115 fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
116 self.matched_items.get(ix.row)
117 }
118
119 fn position<V>(&self, value: &V) -> Option<IndexPath>
120 where
121 Self::Item: SearchableListItem<Value = V>,
122 V: PartialEq,
123 {
124 self.matched_items
125 .iter()
126 .position(|v| v.value() == value)
127 .map(|ix| IndexPath::default().row(ix))
128 }
129
130 fn perform_search(&mut self, query: &str, _: &mut Window, _: &mut App) -> Task<()> {
131 self.matched_items = self
132 .items
133 .iter()
134 .filter(|item| item.matches(query))
135 .cloned()
136 .collect();
137
138 Task::ready(())
139 }
140}
141
142#[derive(Debug, Clone)]
146pub struct SearchableGroup<I: SearchableListItem> {
147 pub title: SharedString,
148 pub items: Vec<I>,
149}
150
151impl<I: SearchableListItem> SearchableGroup<I> {
152 pub fn new(title: impl Into<SharedString>) -> Self {
154 Self {
155 title: title.into(),
156 items: vec![],
157 }
158 }
159
160 pub fn item(mut self, item: I) -> Self {
162 self.items.push(item);
163 self
164 }
165
166 pub fn items(mut self, items: impl IntoIterator<Item = I>) -> Self {
168 self.items.extend(items);
169 self
170 }
171
172 pub(super) fn matches(&self, query: &str) -> bool {
173 self.title.to_lowercase().contains(&query.to_lowercase())
174 || self.items.iter().any(|item| item.matches(query))
175 }
176}
177
178impl<I: SearchableListItem + 'static> SearchableListDelegate for SearchableVec<SearchableGroup<I>> {
179 type Item = I;
180
181 fn sections_count(&self, _: &App) -> usize {
182 self.matched_items.len()
183 }
184
185 fn items_count(&self, section: usize) -> usize {
186 self.matched_items
187 .get(section)
188 .map_or(0, |group| group.items.len())
189 }
190
191 fn section(&self, section: usize) -> Option<rgpui::AnyElement> {
192 use rgpui::IntoElement as _;
193
194 Some(
195 self.matched_items
196 .get(section)?
197 .title
198 .clone()
199 .into_any_element(),
200 )
201 }
202
203 fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
204 let section = self.matched_items.get(ix.section)?;
205
206 section.items.get(ix.row)
207 }
208
209 fn position<V>(&self, value: &V) -> Option<IndexPath>
210 where
211 Self::Item: SearchableListItem<Value = V>,
212 V: PartialEq,
213 {
214 for (ix, group) in self.matched_items.iter().enumerate() {
215 for (row_ix, item) in group.items.iter().enumerate() {
216 if item.value() == value {
217 return Some(IndexPath::default().section(ix).row(row_ix));
218 }
219 }
220 }
221
222 None
223 }
224
225 fn perform_search(&mut self, query: &str, _: &mut Window, _: &mut App) -> Task<()> {
226 self.matched_items = self
227 .items
228 .iter()
229 .filter(|item| item.matches(query))
230 .cloned()
231 .map(|mut item| {
232 item.items.retain(|item| item.matches(query));
233 item
234 })
235 .collect();
236
237 Task::ready(())
238 }
239}