use std::cell::{Cell, RefCell}; use std::time::{Duration, Instant}; use cagire_markdown::ParsedMarkdown; use cagire_ratatui::Sparkles; use tachyonfx::{fx, Effect, EffectManager, Interpolation}; use crate::page::Page; use crate::state::effects::FxId; use crate::state::{ColorScheme, Modal}; #[derive(Clone, Copy, PartialEq, Eq, Default)] pub enum MinimapMode { #[default] Hidden, Timed(Instant), Sticky, } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum FlashKind { #[default] Success, Error, Info, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum DictFocus { #[default] Categories, Words, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum HelpFocus { #[default] Topics, Content, } pub struct UiState { pub sparkles: Sparkles, pub status_message: Option, pub flash_until: Option, pub flash_kind: FlashKind, pub modal: Modal, pub help_focus: HelpFocus, pub help_topic: usize, pub help_scrolls: Vec, pub help_search_active: bool, pub help_search_query: String, pub help_focused_block: Option, pub help_parsed: RefCell>>, pub dict_focus: DictFocus, pub dict_category: usize, pub dict_scrolls: Vec, pub dict_search_query: String, pub dict_search_active: bool, pub help_max_scroll: Cell, pub dict_max_scroll: Cell, pub help_collapsed: Vec, pub help_on_section: Option, pub dict_collapsed: Vec, pub dict_on_section: Option, pub show_title: bool, pub runtime_highlight: bool, pub show_completion: bool, pub minimap: MinimapMode, pub color_scheme: ColorScheme, pub hue_rotation: f32, pub effects: RefCell>, pub modal_fx: RefCell>, pub title_fx: RefCell>, pub prev_modal_open: bool, pub prev_page: Page, pub prev_show_title: bool, pub onboarding_dismissed: Vec, pub performance_mode: bool, pub font: String, pub zoom_factor: f32, pub window_width: u32, pub window_height: u32, pub load_demo_on_startup: bool, pub demo_index: usize, } impl Default for UiState { fn default() -> Self { Self { sparkles: Sparkles::default(), status_message: None, flash_until: None, flash_kind: FlashKind::Success, modal: Modal::None, help_focus: HelpFocus::default(), help_topic: 0, help_scrolls: vec![0; crate::model::docs::topic_count()], help_search_active: false, help_search_query: String::new(), help_focused_block: None, help_parsed: RefCell::new( (0..crate::model::docs::topic_count()) .map(|_| None) .collect(), ), dict_focus: DictFocus::default(), dict_category: 0, dict_scrolls: vec![0; crate::model::categories::category_count()], dict_search_query: String::new(), dict_search_active: false, help_max_scroll: Cell::new(usize::MAX), dict_max_scroll: Cell::new(usize::MAX), help_collapsed: vec![false; crate::model::docs::section_count()], help_on_section: None, dict_collapsed: vec![false; crate::model::categories::section_count()], dict_on_section: None, show_title: true, runtime_highlight: false, show_completion: true, minimap: MinimapMode::Hidden, color_scheme: ColorScheme::default(), hue_rotation: 0.0, effects: RefCell::new(EffectManager::default()), modal_fx: RefCell::new(None), title_fx: RefCell::new(Some(fx::coalesce((400, Interpolation::QuadOut)))), prev_modal_open: false, prev_page: Page::default(), prev_show_title: true, onboarding_dismissed: Vec::new(), performance_mode: false, font: "8x13".to_string(), zoom_factor: 1.5, window_width: 1200, window_height: 800, load_demo_on_startup: true, demo_index: 0, } } } impl UiState { pub fn help_scroll(&self) -> usize { self.help_scrolls[self.help_topic] } pub fn help_scroll_mut(&mut self) -> &mut usize { &mut self.help_scrolls[self.help_topic] } pub fn dict_scroll(&self) -> usize { self.dict_scrolls[self.dict_category] } pub fn dict_scroll_mut(&mut self) -> &mut usize { &mut self.dict_scrolls[self.dict_category] } pub fn flash(&mut self, msg: &str, duration_ms: u64, kind: FlashKind) { self.status_message = Some(msg.to_string()); self.flash_until = Some(Instant::now() + Duration::from_millis(duration_ms)); self.flash_kind = kind; } pub fn flash_kind(&self) -> Option { if self.is_flashing() { Some(self.flash_kind) } else { None } } pub fn set_status(&mut self, msg: String) { self.status_message = Some(msg); } pub fn clear_status(&mut self) { self.status_message = None; } pub fn is_flashing(&self) -> bool { self.flash_until .map(|t| Instant::now() < t) .unwrap_or(false) } pub fn show_minimap(&self) -> bool { match self.minimap { MinimapMode::Hidden => false, MinimapMode::Timed(until) => Instant::now() < until, MinimapMode::Sticky => true, } } pub fn dismiss_minimap(&mut self) { self.minimap = MinimapMode::Hidden; } pub fn invalidate_help_cache(&self) { self.help_parsed .borrow_mut() .iter_mut() .for_each(|slot| *slot = None); } }