use super::*;
impl MissionControlApp {
pub(super) fn open_theme_picker(&mut self, ui_state: &mut state::MissionControlState) {
if ui_state.theme_picker_visible() {
return;
}
let opening = self.appearance.clone();
let cached = self.theme_catalog.clone();
self.next_theme_request_id = self.next_theme_request_id.saturating_add(1);
let request_id = self.next_theme_request_id;
self.theme_picker_runtime = Some(ThemePickerRuntime {
opening_appearance: opening.clone(),
opening_revision: self.theme_revision,
catalog: cached.clone(),
preview_appearance: opening.clone(),
preview_revision: self.theme_revision,
});
let (entries, notice, loading) =
cached.as_ref().map_or((Vec::new(), None, true), |catalog| {
(
catalog.entries(),
(!catalog.diagnostics().is_empty()).then(|| catalog.diagnostics().join("; ")),
false,
)
});
ui_state.open_theme_picker(
opening.theme.id.clone(),
self.theme_revision,
loading,
Some(request_id),
entries,
notice,
);
let sender = self.events.clone();
let home = self.config.paths.root.clone();
let handle = thread::spawn(move || {
let result = Ok(crate::appearance::ThemeCatalog::load(&home));
let delivery =
send_tui_event(&sender, TuiEvent::ThemeCatalogLoaded { request_id, result })
.map_err(|e| e.to_string());
ThemeWorkerResult { delivery }
});
self.theme_workers.push(ThemeWorker {
request_id,
kind: ThemeWorkerKind::Catalog,
handle,
});
}
fn install_theme(
&mut self,
appearance: crate::appearance::RuntimeAppearance,
ui_state: &mut state::MissionControlState,
) {
self.theme_revision = self.theme_revision.saturating_add(1);
self.appearance = appearance.clone();
let theme = crate::tui::theme::MissionControlTheme::from_runtime_appearance(
&appearance,
self.theme_revision,
);
ui_state.color_enabled = appearance.policy.color_enabled;
ui_state.set_theme(theme, appearance.reduced_motion);
if let Some(runtime) = self.theme_picker_runtime.as_mut() {
runtime.preview_appearance = appearance;
runtime.preview_revision = self.theme_revision;
}
}
pub(super) fn preview_theme(&mut self, id: &str, ui_state: &mut state::MissionControlState) {
let Some(runtime) = self.theme_picker_runtime.as_ref() else {
return;
};
let Some(catalog) = runtime.catalog.as_ref() else {
return;
};
let Some(appearance) = catalog.resolve_runtime_appearance(id, &self.appearance) else {
return;
};
if ui_state.theme_picker_selected_id().as_deref() != Some(id) {
return;
}
self.install_theme(appearance, ui_state);
if let Some(picker) = ui_state.modals.theme_picker.as_mut() {
picker.preview_theme_id = id.to_string();
}
}
pub(super) fn cancel_theme(&mut self, ui_state: &mut state::MissionControlState) {
if self.pending_theme_save.is_some() {
return;
}
if let Some(runtime) = self.theme_picker_runtime.take() {
self.theme_revision = self.theme_revision.max(runtime.opening_revision);
self.install_theme(runtime.opening_appearance, ui_state);
}
ui_state.close_theme_picker();
}
pub(super) fn save_theme(&mut self, ui_state: &mut state::MissionControlState) {
let Some(id) = ui_state.theme_picker_selected_id() else {
return;
};
if ui_state.theme_picker_preview_id() != Some(id.as_str())
|| self.pending_theme_save.is_some()
|| ui_state.theme_picker_save_pending()
{
return;
}
let revision = self.theme_revision;
self.next_theme_request_id = self.next_theme_request_id.saturating_add(1);
let request_id = self.next_theme_request_id;
self.pending_theme_save = Some(PendingThemeSave {
request_id,
theme_id: id.clone(),
revision,
});
ui_state.set_theme_picker_save_pending();
let paths = self.config.paths.clone();
let sender = self.events.clone();
let worker_id = request_id;
let handle = thread::spawn(move || {
let result =
crate::config::set_appearance_theme(&paths, &id).map_err(|e| e.to_string());
let delivery = send_tui_event(
&sender,
TuiEvent::ThemePersisted {
request_id,
theme_id: id,
revision,
result,
},
)
.map_err(|e| e.to_string());
ThemeWorkerResult { delivery }
});
self.theme_workers.push(ThemeWorker {
request_id: worker_id,
kind: ThemeWorkerKind::Save,
handle,
});
}
pub(super) fn handle_theme_catalog(
&mut self,
ui_state: &mut state::MissionControlState,
request_id: u64,
result: Result<crate::appearance::ThemeCatalog, String>,
) -> bool {
let current = ui_state.theme_picker_catalog_request_id() == Some(request_id);
match result {
Ok(catalog) => {
if request_id >= self.theme_catalog_request_id {
self.theme_catalog_request_id = request_id;
self.theme_catalog = Some(catalog.clone());
}
if current {
if let Some(runtime) = self.theme_picker_runtime.as_mut() {
runtime.catalog = Some(catalog.clone());
}
let notice = (!catalog.diagnostics().is_empty())
.then(|| catalog.diagnostics().join("; "));
return ui_state.set_theme_picker_catalog(catalog.entries(), notice, 10);
}
false
}
Err(error) if current => ui_state.set_theme_picker_error(error),
Err(_) => false,
}
}
pub(super) fn handle_theme_persisted(
&mut self,
ui_state: &mut state::MissionControlState,
request_id: u64,
id: String,
revision: u64,
result: Result<(), String>,
) -> bool {
let Some(pending) = self.pending_theme_save.as_ref() else {
return false;
};
if pending.request_id != request_id
|| pending.theme_id != id
|| pending.revision != revision
{
return false;
}
self.pending_theme_save = None;
match result {
Ok(()) => {
self.theme_cli_override = false;
ui_state.close_theme_picker();
self.theme_picker_runtime = None;
true
}
Err(error) => ui_state.set_theme_picker_save_failed(error),
}
}
pub(super) fn reap_theme_workers(&mut self, ui_state: &mut state::MissionControlState) -> bool {
let mut changed = false;
let mut active = Vec::with_capacity(self.theme_workers.len());
for worker in std::mem::take(&mut self.theme_workers) {
if !worker.handle.is_finished() {
active.push(worker);
continue;
}
let kind = worker.kind;
let request_id = worker.request_id;
match worker.handle.join() {
Ok(result) => {
if let Err(error) = result.delivery {
changed = true;
if kind == ThemeWorkerKind::Save {
self.pending_theme_save = None;
ui_state.set_theme_picker_save_failed(format!(
"theme save failed: {error}"
));
} else if ui_state.theme_picker_visible() {
ui_state
.set_theme_picker_error(format!("theme catalog failed: {error}"));
}
}
}
Err(_) => {
changed = true;
if kind == ThemeWorkerKind::Save {
self.pending_theme_save = None;
ui_state.set_theme_picker_save_failed(format!(
"theme save worker {request_id} panicked"
));
} else if ui_state.theme_picker_visible() {
ui_state.set_theme_picker_error("theme catalog worker panicked".into());
}
}
}
}
self.theme_workers = active;
changed
}
pub(super) fn join_theme_workers_for_cleanup(&mut self) -> Vec<String> {
let mut errors = Vec::new();
for worker in std::mem::take(&mut self.theme_workers) {
match worker.handle.join() {
Ok(result) => {
if let Err(error) = result.delivery {
errors.push(format!(
"theme {:?} worker {} failed: {error}",
worker.kind, worker.request_id
));
}
}
Err(_) => errors.push(format!(
"theme {:?} worker {} panicked",
worker.kind, worker.request_id
)),
}
}
self.pending_theme_save = None;
errors
}
}