Skip to main content

fresh/view/ui/
expanded_menus_cache.rs

1//! Memoised expansion of `MenuConfig` against the active `ThemeRegistry`.
2//!
3//! Without this cache, `MenuRenderer::render` rescans the themes directory
4//! and re-deserialises every theme JSON file on every frame to expand any
5//! `DynamicSubmenu` items in the menu config. The cache builds the
6//! expansion once per registry instance and reuses it until the registry
7//! is replaced (e.g. by `reload_themes`), at which point callers
8//! `invalidate` and re-`update`.
9
10use std::path::Path;
11use std::sync::Arc;
12
13use crate::config::{MenuConfig, MenuExt};
14use crate::view::theme::ThemeRegistry;
15
16#[derive(Default)]
17pub struct ExpandedMenusCache {
18    cached: Option<(Arc<ThemeRegistry>, MenuConfig)>,
19}
20
21impl ExpandedMenusCache {
22    /// Ensure the cache holds an expansion built against `registry`. No-op
23    /// when the cached entry was already built against the same `Arc`
24    /// instance (compared via `Arc::ptr_eq`).
25    pub fn update(&mut self, registry: &Arc<ThemeRegistry>, base: &MenuConfig, themes_dir: &Path) {
26        if let Some((cached_reg, _)) = &self.cached {
27            if Arc::ptr_eq(cached_reg, registry) {
28                return;
29            }
30        }
31        let mut menus = base.clone();
32        for menu in &mut menus.menus {
33            menu.expand_dynamic_items(themes_dir);
34        }
35        self.cached = Some((Arc::clone(registry), menus));
36    }
37
38    /// The currently-cached expansion, or `None` if not yet built / just
39    /// invalidated.
40    pub fn get(&self) -> Option<&MenuConfig> {
41        self.cached.as_ref().map(|(_, m)| m)
42    }
43
44    /// Drop the cached expansion. The next `update` will rebuild.
45    pub fn invalidate(&mut self) {
46        self.cached = None;
47    }
48}