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}