Files
oak-gpui/crates/theme_selector/src/theme_selector.rs
T
Max Brunsfeld ef0b584532 Remove AppState from workspace actions
This allows those actions to be bound to keystrokes in the keymap.
Also, remove the WorkspaceParams struct, simplify how Workspaces are
constructed.
2022-05-19 14:37:26 -07:00

256 lines
7.5 KiB
Rust

use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
actions, elements::*, AppContext, Element, ElementBox, Entity, MutableAppContext,
RenderContext, View, ViewContext, ViewHandle,
};
use picker::{Picker, PickerDelegate};
use settings::Settings;
use std::sync::Arc;
use theme::{Theme, ThemeRegistry};
use workspace::{AppState, Workspace};
pub struct ThemeSelector {
registry: Arc<ThemeRegistry>,
theme_names: Vec<String>,
matches: Vec<StringMatch>,
original_theme: Arc<Theme>,
picker: ViewHandle<Picker<Self>>,
selection_completed: bool,
selected_index: usize,
}
actions!(theme_selector, [Toggle, Reload]);
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
Picker::<ThemeSelector>::init(cx);
cx.add_action({
let theme_registry = app_state.themes.clone();
move |workspace, _: &Toggle, cx| {
ThemeSelector::toggle(workspace, theme_registry.clone(), cx)
}
});
}
pub enum Event {
Dismissed,
}
impl ThemeSelector {
fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<Self>) -> Self {
let handle = cx.weak_handle();
let picker = cx.add_view(|cx| Picker::new(handle, cx));
let original_theme = cx.global::<Settings>().theme.clone();
let mut theme_names = registry.list().collect::<Vec<_>>();
theme_names.sort_unstable_by(|a, b| {
a.ends_with("dark")
.cmp(&b.ends_with("dark"))
.then_with(|| a.cmp(&b))
});
let matches = theme_names
.iter()
.map(|name| StringMatch {
candidate_id: 0,
score: 0.0,
positions: Default::default(),
string: name.clone(),
})
.collect();
let mut this = Self {
registry,
theme_names,
matches,
picker,
original_theme: original_theme.clone(),
selected_index: 0,
selection_completed: false,
};
this.select_if_matching(&original_theme.name);
this
}
fn toggle(
workspace: &mut Workspace,
themes: Arc<ThemeRegistry>,
cx: &mut ViewContext<Workspace>,
) {
workspace.toggle_modal(cx, |_, cx| {
let this = cx.add_view(|cx| Self::new(themes, cx));
cx.subscribe(&this, Self::on_event).detach();
this
});
}
#[cfg(debug_assertions)]
pub fn reload(themes: Arc<ThemeRegistry>, cx: &mut MutableAppContext) {
let current_theme_name = cx.global::<Settings>().theme.name.clone();
themes.clear();
match themes.get(&current_theme_name) {
Ok(theme) => {
Self::set_theme(theme, cx);
log::info!("reloaded theme {}", current_theme_name);
}
Err(error) => {
log::error!("failed to load theme {}: {:?}", current_theme_name, error)
}
}
}
fn show_selected_theme(&mut self, cx: &mut ViewContext<Self>) {
if let Some(mat) = self.matches.get(self.selected_index) {
match self.registry.get(&mat.string) {
Ok(theme) => Self::set_theme(theme, cx),
Err(error) => {
log::error!("error loading theme {}: {}", mat.string, error)
}
}
}
}
fn select_if_matching(&mut self, theme_name: &str) {
self.selected_index = self
.matches
.iter()
.position(|mat| mat.string == theme_name)
.unwrap_or(self.selected_index);
}
fn on_event(
workspace: &mut Workspace,
_: ViewHandle<ThemeSelector>,
event: &Event,
cx: &mut ViewContext<Workspace>,
) {
match event {
Event::Dismissed => {
workspace.dismiss_modal(cx);
}
}
}
fn set_theme(theme: Arc<Theme>, cx: &mut MutableAppContext) {
cx.update_global::<Settings, _, _>(|settings, cx| {
settings.theme = theme;
cx.refresh_windows();
});
}
}
impl PickerDelegate for ThemeSelector {
fn match_count(&self) -> usize {
self.matches.len()
}
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
self.selection_completed = true;
cx.emit(Event::Dismissed);
}
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
if !self.selection_completed {
Self::set_theme(self.original_theme.clone(), cx);
self.selection_completed = true;
}
cx.emit(Event::Dismissed);
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
self.selected_index = ix;
self.show_selected_theme(cx);
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
let background = cx.background().clone();
let candidates = self
.theme_names
.iter()
.enumerate()
.map(|(id, name)| StringMatchCandidate {
id,
char_bag: name.as_str().into(),
string: name.clone(),
})
.collect::<Vec<_>>();
cx.spawn(|this, mut cx| async move {
let matches = if query.is_empty() {
candidates
.into_iter()
.enumerate()
.map(|(index, candidate)| StringMatch {
candidate_id: index,
string: candidate.string,
positions: Vec::new(),
score: 0.0,
})
.collect()
} else {
match_strings(
&candidates,
&query,
false,
100,
&Default::default(),
background,
)
.await
};
this.update(&mut cx, |this, cx| {
this.matches = matches;
this.selected_index = this
.selected_index
.min(this.matches.len().saturating_sub(1));
this.show_selected_theme(cx);
cx.notify();
});
})
}
fn render_match(
&self,
ix: usize,
mouse_state: &MouseState,
selected: bool,
cx: &AppContext,
) -> ElementBox {
let settings = cx.global::<Settings>();
let theme = &settings.theme;
let theme_match = &self.matches[ix];
let style = theme.picker.item.style_for(mouse_state, selected);
Label::new(theme_match.string.clone(), style.label.clone())
.with_highlights(theme_match.positions.clone())
.contained()
.with_style(style.container)
.boxed()
}
}
impl Entity for ThemeSelector {
type Event = Event;
fn release(&mut self, cx: &mut MutableAppContext) {
if !self.selection_completed {
Self::set_theme(self.original_theme.clone(), cx);
}
}
}
impl View for ThemeSelector {
fn ui_name() -> &'static str {
"ThemeSelector"
}
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
ChildView::new(self.picker.clone()).boxed()
}
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
cx.focus(&self.picker);
}
}