icon_theme_picker.rs

  1use std::sync::Arc;
  2
  3use fuzzy::{StringMatch, StringMatchCandidate};
  4use gpui::{AnyElement, App, Context, DismissEvent, SharedString, Task, Window};
  5use picker::{Picker, PickerDelegate};
  6use theme::ThemeRegistry;
  7use ui::{ListItem, ListItemSpacing, prelude::*};
  8
  9type IconThemePicker = Picker<IconThemePickerDelegate>;
 10
 11pub struct IconThemePickerDelegate {
 12    icon_themes: Vec<SharedString>,
 13    filtered_themes: Vec<StringMatch>,
 14    selected_index: usize,
 15    current_theme: SharedString,
 16    on_theme_changed: Arc<dyn Fn(SharedString, &mut Window, &mut App) + 'static>,
 17}
 18
 19impl IconThemePickerDelegate {
 20    fn new(
 21        current_theme: SharedString,
 22        on_theme_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
 23        cx: &mut Context<IconThemePicker>,
 24    ) -> Self {
 25        let theme_registry = ThemeRegistry::global(cx);
 26
 27        let icon_themes: Vec<SharedString> = theme_registry
 28            .list_icon_themes()
 29            .into_iter()
 30            .map(|theme_meta| theme_meta.name)
 31            .collect();
 32
 33        let selected_index = icon_themes
 34            .iter()
 35            .position(|icon_theme| *icon_theme == current_theme)
 36            .unwrap_or(0);
 37
 38        let filtered_themes = icon_themes
 39            .iter()
 40            .enumerate()
 41            .map(|(index, theme)| StringMatch {
 42                candidate_id: index,
 43                string: theme.to_string(),
 44                positions: Vec::new(),
 45                score: 0.0,
 46            })
 47            .collect();
 48
 49        Self {
 50            icon_themes,
 51            filtered_themes,
 52            selected_index,
 53            current_theme,
 54            on_theme_changed: Arc::new(on_theme_changed),
 55        }
 56    }
 57}
 58
 59impl PickerDelegate for IconThemePickerDelegate {
 60    type ListItem = AnyElement;
 61
 62    fn match_count(&self) -> usize {
 63        self.filtered_themes.len()
 64    }
 65
 66    fn selected_index(&self) -> usize {
 67        self.selected_index
 68    }
 69
 70    fn set_selected_index(
 71        &mut self,
 72        index: usize,
 73        _window: &mut Window,
 74        cx: &mut Context<IconThemePicker>,
 75    ) {
 76        self.selected_index = index.min(self.filtered_themes.len().saturating_sub(1));
 77        cx.notify();
 78    }
 79
 80    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
 81        "Search icon themes…".into()
 82    }
 83
 84    fn update_matches(
 85        &mut self,
 86        query: String,
 87        _window: &mut Window,
 88        cx: &mut Context<IconThemePicker>,
 89    ) -> Task<()> {
 90        let icon_themes = self.icon_themes.clone();
 91        let current_theme = self.current_theme.clone();
 92
 93        let matches: Vec<StringMatch> = if query.is_empty() {
 94            icon_themes
 95                .iter()
 96                .enumerate()
 97                .map(|(index, theme)| StringMatch {
 98                    candidate_id: index,
 99                    string: theme.to_string(),
100                    positions: Vec::new(),
101                    score: 0.0,
102                })
103                .collect()
104        } else {
105            let _candidates: Vec<StringMatchCandidate> = icon_themes
106                .iter()
107                .enumerate()
108                .map(|(id, theme)| StringMatchCandidate::new(id, theme.as_ref()))
109                .collect();
110
111            icon_themes
112                .iter()
113                .enumerate()
114                .filter(|(_, theme)| theme.to_lowercase().contains(&query.to_lowercase()))
115                .map(|(index, theme)| StringMatch {
116                    candidate_id: index,
117                    string: theme.to_string(),
118                    positions: Vec::new(),
119                    score: 0.0,
120                })
121                .collect()
122        };
123
124        let selected_index = if query.is_empty() {
125            icon_themes
126                .iter()
127                .position(|theme| *theme == current_theme)
128                .unwrap_or(0)
129        } else {
130            matches
131                .iter()
132                .position(|m| icon_themes[m.candidate_id] == current_theme)
133                .unwrap_or(0)
134        };
135
136        self.filtered_themes = matches;
137        self.selected_index = selected_index;
138        cx.notify();
139
140        Task::ready(())
141    }
142
143    fn confirm(
144        &mut self,
145        _secondary: bool,
146        window: &mut Window,
147        cx: &mut Context<IconThemePicker>,
148    ) {
149        if let Some(theme_match) = self.filtered_themes.get(self.selected_index) {
150            let theme = theme_match.string.clone();
151            (self.on_theme_changed)(theme.into(), window, cx);
152        }
153    }
154
155    fn dismissed(&mut self, window: &mut Window, cx: &mut Context<IconThemePicker>) {
156        cx.defer_in(window, |picker, window, cx| {
157            picker.set_query("", window, cx);
158        });
159        cx.emit(DismissEvent);
160    }
161
162    fn render_match(
163        &self,
164        index: usize,
165        selected: bool,
166        _window: &mut Window,
167        _cx: &mut Context<IconThemePicker>,
168    ) -> Option<Self::ListItem> {
169        let theme_match = self.filtered_themes.get(index)?;
170
171        Some(
172            ListItem::new(index)
173                .inset(true)
174                .spacing(ListItemSpacing::Sparse)
175                .toggle_state(selected)
176                .child(Label::new(theme_match.string.clone()))
177                .into_any_element(),
178        )
179    }
180}
181
182pub fn icon_theme_picker(
183    current_theme: SharedString,
184    on_theme_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
185    window: &mut Window,
186    cx: &mut Context<IconThemePicker>,
187) -> IconThemePicker {
188    let delegate = IconThemePickerDelegate::new(current_theme, on_theme_changed, cx);
189
190    Picker::uniform_list(delegate, window, cx)
191        .show_scrollbar(true)
192        .width(rems_from_px(210.))
193        .max_height(Some(rems(18.).into()))
194}