Skip to main content

fret_runtime/window_command_gating/
service.rs

1use std::collections::HashMap;
2
3use fret_core::AppWindowId;
4
5use super::snapshot::WindowCommandGatingSnapshot;
6
7/// Token identifying a pushed, overlay-scoped gating override.
8///
9/// The intent is to allow nested overlays (command palette -> menu -> sub-menu, etc.) to publish
10/// independent gating snapshots without clobbering each other.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12struct WindowCommandGatingToken(u64);
13
14/// Handle identifying a pushed, overlay-scoped gating override.
15///
16/// Returned by `push_snapshot` so overlays can remove or update only their own snapshot.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct WindowCommandGatingHandle {
19    window: AppWindowId,
20    token: WindowCommandGatingToken,
21}
22
23#[derive(Debug, Default)]
24struct WindowCommandGatingWindowState {
25    base: Option<WindowCommandGatingSnapshot>,
26    stack: Vec<(WindowCommandGatingToken, WindowCommandGatingSnapshot)>,
27}
28
29#[derive(Debug, Default)]
30pub struct WindowCommandGatingService {
31    by_window: HashMap<AppWindowId, WindowCommandGatingWindowState>,
32    next_token: u64,
33}
34
35impl WindowCommandGatingService {
36    pub fn snapshot(&self, window: AppWindowId) -> Option<&WindowCommandGatingSnapshot> {
37        self.by_window.get(&window).and_then(|state| {
38            state
39                .stack
40                .last()
41                .map(|(_, snap)| snap)
42                .or(state.base.as_ref())
43        })
44    }
45
46    pub fn base_snapshot(&self, window: AppWindowId) -> Option<&WindowCommandGatingSnapshot> {
47        self.by_window
48            .get(&window)
49            .and_then(|state| state.base.as_ref())
50    }
51
52    /// Sets the base override snapshot for the window.
53    ///
54    /// Nested overlays should prefer `push_snapshot` so they do not overwrite other overrides.
55    pub fn set_base_snapshot(
56        &mut self,
57        window: AppWindowId,
58        snapshot: WindowCommandGatingSnapshot,
59    ) {
60        let state = self.by_window.entry(window).or_default();
61        state.base = Some(snapshot);
62        self.gc_window(window);
63    }
64
65    /// Sets the base override snapshot for the window.
66    ///
67    /// Nested overlays should prefer `push_snapshot` so they do not overwrite other overrides.
68    pub fn set_snapshot(&mut self, window: AppWindowId, snapshot: WindowCommandGatingSnapshot) {
69        self.set_base_snapshot(window, snapshot);
70    }
71
72    pub fn clear_base_snapshot(&mut self, window: AppWindowId) {
73        if let Some(state) = self.by_window.get_mut(&window) {
74            state.base = None;
75        }
76        self.gc_window(window);
77    }
78
79    pub fn clear_snapshot(&mut self, window: AppWindowId) {
80        self.clear_base_snapshot(window);
81    }
82
83    /// Pushes an overlay-scoped gating snapshot and returns a handle that can later remove it.
84    ///
85    /// The most recently pushed snapshot wins (`snapshot()` returns the stack top).
86    pub fn push_snapshot(
87        &mut self,
88        window: AppWindowId,
89        snapshot: WindowCommandGatingSnapshot,
90    ) -> WindowCommandGatingHandle {
91        let token = WindowCommandGatingToken(self.next_token.max(1));
92        self.next_token = token.0.saturating_add(1);
93        let state = self.by_window.entry(window).or_default();
94        state.stack.push((token, snapshot));
95        WindowCommandGatingHandle { window, token }
96    }
97
98    pub fn update_pushed_snapshot(
99        &mut self,
100        handle: WindowCommandGatingHandle,
101        snapshot: WindowCommandGatingSnapshot,
102    ) -> bool {
103        let Some(state) = self.by_window.get_mut(&handle.window) else {
104            return false;
105        };
106        for (t, s) in &mut state.stack {
107            if *t == handle.token {
108                *s = snapshot;
109                return true;
110            }
111        }
112        false
113    }
114
115    pub fn pop_snapshot(
116        &mut self,
117        handle: WindowCommandGatingHandle,
118    ) -> Option<WindowCommandGatingSnapshot> {
119        let state = self.by_window.get_mut(&handle.window)?;
120        let idx = state.stack.iter().position(|(t, _)| *t == handle.token)?;
121        let (_, snapshot) = state.stack.remove(idx);
122        self.gc_window(handle.window);
123        Some(snapshot)
124    }
125
126    pub fn remove_window(&mut self, window: AppWindowId) {
127        self.by_window.remove(&window);
128    }
129
130    fn gc_window(&mut self, window: AppWindowId) {
131        let remove = self
132            .by_window
133            .get(&window)
134            .is_some_and(|state| state.base.is_none() && state.stack.is_empty());
135        if remove {
136            self.by_window.remove(&window);
137        }
138    }
139}