use super::*;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(crate) enum ThemeSaveState {
#[default]
Idle,
Pending,
Failed(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ThemePickerState {
pub(crate) entries: Vec<crate::appearance::ThemeCatalogEntry>,
pub(crate) filtered_indices: Vec<usize>,
pub(crate) active_variant: opaline::ThemeVariant,
catalog_initialized: bool,
pub(crate) query: String,
pub(crate) query_cursor: usize,
pub(crate) selected: usize,
pub(crate) preview_theme_id: String,
pub(crate) opening_revision: u64,
pub(crate) loading: bool,
pub(crate) notice: Option<String>,
pub(crate) error: Option<String>,
pub(crate) catalog_request_id: Option<u64>,
pub(crate) save_state: ThemeSaveState,
}
impl ThemePickerState {
pub(super) fn new(
preview_theme_id: String,
opening_revision: u64,
loading: bool,
catalog_request_id: Option<u64>,
) -> Self {
Self {
entries: Vec::new(),
filtered_indices: Vec::new(),
active_variant: opaline::ThemeVariant::Dark,
catalog_initialized: false,
query: String::new(),
query_cursor: 0,
selected: 0,
preview_theme_id,
opening_revision,
loading,
notice: None,
error: None,
catalog_request_id,
save_state: ThemeSaveState::Idle,
}
}
fn selected_entry(&self) -> Option<&crate::appearance::ThemeCatalogEntry> {
self.filtered_indices
.get(self.selected)
.and_then(|index| self.entries.get(*index))
}
pub(super) fn selected_id(&self) -> Option<String> {
self.selected_entry().map(|entry| entry.id.clone())
}
pub(super) fn clamp(&mut self) {
let item_count = self.filtered_indices.len();
clamp_picker_selection(&mut self.selected, item_count);
}
fn filtered_selection(
&self,
query: &str,
variant: opaline::ThemeVariant,
) -> (Vec<usize>, usize) {
let selected_id = self.selected_entry().map(|entry| entry.id.as_str());
let needle = query.to_lowercase();
let indices: Vec<_> = self
.entries
.iter()
.enumerate()
.filter(|(_, entry)| {
entry.variant == variant
&& (needle.is_empty()
|| entry.id.to_lowercase().contains(&needle)
|| entry.display_name.to_lowercase().contains(&needle))
})
.map(|(index, _)| index)
.collect();
let selected = selected_id
.and_then(|id| {
indices
.iter()
.position(|index| self.entries[*index].id == id)
})
.unwrap_or(0);
(indices, selected)
}
pub(crate) fn selected_id_after_filter(
&self,
query: &str,
variant: opaline::ThemeVariant,
) -> Option<String> {
let (indices, selected) = self.filtered_selection(query, variant);
indices
.get(selected)
.map(|index| self.entries[*index].id.clone())
}
pub(crate) fn next_variant(&self) -> opaline::ThemeVariant {
match self.active_variant {
opaline::ThemeVariant::Dark => opaline::ThemeVariant::Light,
opaline::ThemeVariant::Light => opaline::ThemeVariant::Dark,
}
}
pub(super) fn refilter(&mut self) {
(self.filtered_indices, self.selected) =
self.filtered_selection(&self.query, self.active_variant);
}
pub(super) fn set_catalog(
&mut self,
entries: Vec<crate::appearance::ThemeCatalogEntry>,
notice: Option<String>,
) {
if !self.catalog_initialized {
self.active_variant = entries
.iter()
.find(|entry| entry.id == self.preview_theme_id)
.map_or(opaline::ThemeVariant::Dark, |entry| entry.variant);
self.catalog_initialized = true;
}
self.entries = entries;
self.loading = false;
self.error = None;
self.notice = notice;
self.catalog_request_id = None;
self.refilter();
if let Some(index) = self.filtered_indices.iter().position(|index| {
self.entries
.get(*index)
.is_some_and(|entry| entry.id == self.preview_theme_id)
}) {
self.selected = index;
}
}
pub(super) fn set_error(&mut self, error: String) {
self.loading = false;
self.catalog_request_id = None;
self.error = Some(error);
}
pub(super) fn edit_query(
&mut self,
edit: impl FnOnce(&mut String, &mut usize),
) -> Option<String> {
if self.loading
|| !matches!(
self.save_state,
ThemeSaveState::Idle | ThemeSaveState::Failed(_)
)
{
return None;
}
edit(&mut self.query, &mut self.query_cursor);
self.refilter();
self.selected_id()
}
pub(super) fn move_selection(
&mut self,
amount: usize,
down: bool,
visible_rows: usize,
) -> Option<String> {
if self.loading
|| !matches!(
self.save_state,
ThemeSaveState::Idle | ThemeSaveState::Failed(_)
)
{
return None;
}
if !self.filtered_indices.is_empty() {
if down {
self.selected = self
.selected
.saturating_add(amount)
.min(self.filtered_indices.len() - 1);
} else {
self.selected = self.selected.saturating_sub(amount);
}
}
self.clamp();
let _ = visible_rows;
self.selected_id()
}
}