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