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}