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 App) + 'static>,
 17}
 18
 19impl IconThemePickerDelegate {
 20    fn new(
 21        current_theme: SharedString,
 22        on_theme_changed: impl Fn(SharedString, &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_themes| *icon_themes == current_theme)
 36            .unwrap_or(0);
 37
 38        let filtered_themes = icon_themes
 39            .iter()
 40            .enumerate()
 41            .map(|(index, icon_themes)| StringMatch {
 42                candidate_id: index,
 43                string: icon_themes.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(&mut self, ix: usize, _: &mut Window, cx: &mut Context<IconThemePicker>) {
 71        self.selected_index = ix.min(self.filtered_themes.len().saturating_sub(1));
 72        cx.notify();
 73    }
 74
 75    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
 76        "Search icon theme…".into()
 77    }
 78
 79    fn update_matches(
 80        &mut self,
 81        query: String,
 82        _window: &mut Window,
 83        cx: &mut Context<IconThemePicker>,
 84    ) -> Task<()> {
 85        let icon_themes = self.icon_themes.clone();
 86        let current_theme = self.current_theme.clone();
 87
 88        let matches: Vec<StringMatch> = if query.is_empty() {
 89            icon_themes
 90                .iter()
 91                .enumerate()
 92                .map(|(index, icon_theme)| StringMatch {
 93                    candidate_id: index,
 94                    string: icon_theme.to_string(),
 95                    positions: Vec::new(),
 96                    score: 0.0,
 97                })
 98                .collect()
 99        } else {
100            let _candidates: Vec<StringMatchCandidate> = icon_themes
101                .iter()
102                .enumerate()
103                .map(|(id, icon_theme)| StringMatchCandidate::new(id, icon_theme.as_ref()))
104                .collect();
105
106            icon_themes
107                .iter()
108                .enumerate()
109                .filter(|(_, icon_theme)| icon_theme.to_lowercase().contains(&query.to_lowercase()))
110                .map(|(index, icon_theme)| StringMatch {
111                    candidate_id: index,
112                    string: icon_theme.to_string(),
113                    positions: Vec::new(),
114                    score: 0.0,
115                })
116                .collect()
117        };
118
119        let selected_index = if query.is_empty() {
120            icon_themes
121                .iter()
122                .position(|icon_theme| *icon_theme == current_theme)
123                .unwrap_or(0)
124        } else {
125            matches
126                .iter()
127                .position(|m| icon_themes[m.candidate_id] == current_theme)
128                .unwrap_or(0)
129        };
130
131        self.filtered_themes = matches;
132        self.selected_index = selected_index;
133        cx.notify();
134
135        Task::ready(())
136    }
137
138    fn confirm(
139        &mut self,
140        _secondary: bool,
141        _window: &mut Window,
142        cx: &mut Context<IconThemePicker>,
143    ) {
144        if let Some(theme_match) = self.filtered_themes.get(self.selected_index) {
145            let theme = theme_match.string.clone();
146            (self.on_theme_changed)(theme.into(), cx);
147        }
148    }
149
150    fn dismissed(&mut self, window: &mut Window, cx: &mut Context<IconThemePicker>) {
151        cx.defer_in(window, |picker, window, cx| {
152            picker.set_query("", window, cx);
153        });
154        cx.emit(DismissEvent);
155    }
156
157    fn render_match(
158        &self,
159        ix: usize,
160        selected: bool,
161        _window: &mut Window,
162        _cx: &mut Context<IconThemePicker>,
163    ) -> Option<Self::ListItem> {
164        let theme_match = self.filtered_themes.get(ix)?;
165
166        Some(
167            ListItem::new(ix)
168                .inset(true)
169                .spacing(ListItemSpacing::Sparse)
170                .toggle_state(selected)
171                .child(Label::new(theme_match.string.clone()))
172                .into_any_element(),
173        )
174    }
175}
176
177pub fn icon_theme_picker(
178    current_theme: SharedString,
179    on_theme_changed: impl Fn(SharedString, &mut App) + 'static,
180    window: &mut Window,
181    cx: &mut Context<IconThemePicker>,
182) -> IconThemePicker {
183    let delegate = IconThemePickerDelegate::new(current_theme, on_theme_changed, cx);
184
185    Picker::uniform_list(delegate, window, cx)
186        .show_scrollbar(true)
187        .width(rems_from_px(210.))
188        .max_height(Some(rems(18.).into()))
189}