Skip to main content

fret_runtime/menu/
normalize.rs

1use super::{MenuBar, MenuItem};
2
3impl MenuBar {
4    /// Normalize the menu structure for display across menu surfaces.
5    ///
6    /// This is a best-effort "shape cleanup" pass intended to prevent drift between:
7    /// - OS menubars (runner mappings),
8    /// - in-window menubars (overlay renderers),
9    /// - other menu-like surfaces that derive from `MenuBar`.
10    ///
11    /// Current normalization rules:
12    /// - remove leading separators,
13    /// - collapse duplicate separators,
14    /// - remove trailing separators,
15    /// - recursively drop empty submenus (after normalizing their children).
16    ///
17    /// Note: this does **not** apply enable/disable gating; that is handled by
18    /// `WindowCommandGatingSnapshot` and surface-specific policies.
19    pub fn normalize(&mut self) {
20        for menu in &mut self.menus {
21            normalize_menu_items(&mut menu.items);
22        }
23    }
24
25    pub fn normalized(mut self) -> Self {
26        self.normalize();
27        self
28    }
29}
30
31fn normalize_menu_items(items: &mut Vec<MenuItem>) {
32    let mut out: Vec<MenuItem> = Vec::with_capacity(items.len());
33    let mut last_was_separator = false;
34
35    for item in std::mem::take(items) {
36        match item {
37            MenuItem::Separator => {
38                if out.is_empty() || last_was_separator {
39                    continue;
40                }
41                out.push(MenuItem::Separator);
42                last_was_separator = true;
43            }
44            MenuItem::Submenu {
45                title,
46                when,
47                mut items,
48            } => {
49                normalize_menu_items(&mut items);
50                if items.is_empty() {
51                    continue;
52                }
53                out.push(MenuItem::Submenu { title, when, items });
54                last_was_separator = false;
55            }
56            other => {
57                out.push(other);
58                last_was_separator = false;
59            }
60        }
61    }
62
63    while matches!(out.last(), Some(MenuItem::Separator)) {
64        out.pop();
65    }
66
67    *items = out;
68}