223 lines
6.6 KiB
Rust
223 lines
6.6 KiB
Rust
use std::cell::{Cell, RefCell};
|
|
use std::time::{Duration, Instant};
|
|
|
|
use cagire_markdown::ParsedMarkdown;
|
|
use cagire_ratatui::Sparkles;
|
|
use tachyonfx::{fx, Effect, EffectManager, Interpolation, Motion};
|
|
|
|
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<String>,
|
|
pub flash_until: Option<Instant>,
|
|
pub flash_kind: FlashKind,
|
|
pub modal: Modal,
|
|
pub help_focus: HelpFocus,
|
|
pub help_topic: usize,
|
|
pub help_scrolls: Vec<usize>,
|
|
pub help_search_active: bool,
|
|
pub help_search_query: String,
|
|
pub help_focused_block: Option<usize>,
|
|
pub help_parsed: RefCell<Vec<Option<ParsedMarkdown>>>,
|
|
pub dict_focus: DictFocus,
|
|
pub dict_category: usize,
|
|
pub dict_scrolls: Vec<usize>,
|
|
pub dict_search_query: String,
|
|
pub dict_search_active: bool,
|
|
pub help_max_scroll: Cell<usize>,
|
|
pub dict_max_scroll: Cell<usize>,
|
|
pub help_collapsed: Vec<bool>,
|
|
pub help_on_section: Option<usize>,
|
|
pub dict_collapsed: Vec<bool>,
|
|
pub dict_on_section: Option<usize>,
|
|
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<EffectManager<FxId>>,
|
|
pub modal_fx: RefCell<Option<Effect>>,
|
|
pub title_fx: RefCell<Option<Effect>>,
|
|
pub prev_modal_open: bool,
|
|
pub prev_page: Page,
|
|
pub prev_show_title: bool,
|
|
pub onboarding_dismissed: Vec<String>,
|
|
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,
|
|
pub nav_indicator_until: Option<Instant>,
|
|
pub nav_fx: RefCell<Option<Effect>>,
|
|
}
|
|
|
|
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,
|
|
nav_indicator_until: None,
|
|
nav_fx: RefCell::new(None),
|
|
}
|
|
}
|
|
}
|
|
|
|
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<FlashKind> {
|
|
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 show_nav_indicator(&mut self, duration_ms: u64, direction: Motion) {
|
|
self.nav_indicator_until = Some(Instant::now() + Duration::from_millis(duration_ms));
|
|
let bg = crate::theme::get().ui.bg;
|
|
*self.nav_fx.borrow_mut() = Some(fx::fade_from_fg(bg, (150, Interpolation::QuadOut)));
|
|
crate::state::effects::nav_sweep(self, direction);
|
|
}
|
|
|
|
pub fn nav_indicator_visible(&self) -> bool {
|
|
self.nav_indicator_until
|
|
.map(|t| Instant::now() < t)
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
pub fn invalidate_help_cache(&self) {
|
|
self.help_parsed
|
|
.borrow_mut()
|
|
.iter_mut()
|
|
.for_each(|slot| *slot = None);
|
|
}
|
|
}
|