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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Layout and measurement helpers for the responsive menu bar.
use egui::{Color32, FontId, Ui};
use crate::TitleBar;
impl TitleBar {
/// Build overlay items list based on current mode (matches navigation logic)
pub fn build_overlay_items(&self) -> Vec<(usize, bool, usize)> {
let mut overlay_items = Vec::new();
for (chronological_index, &(is_submenu, index)) in self.menu_order.iter().enumerate() {
// Logic must match navigation logic
let should_include = if self.items_fitted.is_empty() {
// Mode minimal: tous les items vont dans les dots
true
} else {
// Mode normal: seulement les items après le dernier item fitted
if let Some(&last_fitted_index) = self.items_fitted.last() {
chronological_index > last_fitted_index
} else {
false
}
};
if should_include {
overlay_items.push((chronological_index, is_submenu, index));
}
}
overlay_items
}
/// Get a contrasting text color for keyboard selection
pub(super) fn get_contrasting_text_color(&self) -> Color32 {
// For now, use white text which works well with blue selection backgrounds
// This could be made more sophisticated in the future to adapt to the exact background color
Color32::WHITE
}
/// Calculate the width of a menu item label
pub(super) fn calculate_menu_item_width(&self, ui: &mut Ui, label: &str) -> f32 {
ui.fonts_mut(|f| {
f.layout_no_wrap(
label.to_string(),
FontId::proportional(self.menu_text_size),
self.menu_text_color,
)
.size()
.x
}) + 16.0
}
/// Create unified list of menu items with their widths
pub(super) fn create_unified_items(&self, ui: &mut Ui) -> Vec<(usize, f32, bool)> {
let mut unified_items = Vec::new();
// Use menu_order to preserve the exact order in which items were added
for &(is_submenu, index) in &self.menu_order {
if is_submenu {
// Submenu item
if let Some(menu_item) = self.menu_items_with_submenus.get(index) {
let label_width = self.calculate_menu_item_width(ui, &menu_item.label);
unified_items.push((index, label_width, true)); // true = submenu
}
} else {
// Simple menu item
if let Some((label, _)) = self.menu_items.get(index) {
let label_width = self.calculate_menu_item_width(ui, label);
unified_items.push((index, label_width, false)); // false = simple menu
}
}
}
unified_items
}
/// Calculate which items fit and overflow details
pub(super) fn calculate_overflow_details(
&self,
unified_items: &[(usize, f32, bool)],
effective_available_width: f32,
) -> (Vec<usize>, bool, f32, bool) {
let mut items_fitted = Vec::new();
let mut visible_width = 0.0;
// Determine if we need overflow indicator and reserve space for it
let needs_overflow =
unified_items.iter().map(|(_, w, _)| *w).sum::<f32>() > effective_available_width;
let overflow_width = if needs_overflow {
30.0 // "..." width for compact mode
} else {
0.0
};
// Now calculate which items fit with overflow space reserved
let effective_available_width = effective_available_width - overflow_width;
let items_to_fit_count = unified_items.len();
for i in 0..items_to_fit_count {
let (_index, width, _is_submenu) = unified_items[i];
if visible_width + width <= effective_available_width {
visible_width += width;
items_fitted.push(i);
} else {
break; // Stop when items no longer fit
}
}
// Check if we're in minimal mode (very small window - no items fit)
let is_minimal_mode = items_fitted.is_empty();
// Adjust overflow width for minimal mode (hamburger instead of "...")
let final_overflow_width = if needs_overflow {
if is_minimal_mode {
28.0 // Hamburger width for minimal mode (same as icon height)
} else {
30.0 // "..." width for compact mode
}
} else {
0.0
};
(
items_fitted,
needs_overflow,
final_overflow_width,
is_minimal_mode,
)
}
}