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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
use egui::{Color32, Id, ImageSource, Painter};
use crate::TitleBarOptions;
use crate::menu::items::MenuItem;
use crate::theme::{ThemeMode, ThemeProvider, TitleBarTheme, detect_system_dark_mode};
/// Custom icon for the title bar
pub enum CustomIcon {
/// SVG/PNG/JPEG image icon
Image(ImageSource<'static>),
/// Custom drawing function
Drawn(Box<dyn Fn(&Painter, egui::Rect, Color32) + Send + Sync>),
/// Animated icon with framework-managed animation state and context
Animated(
Box<
dyn Fn(&Painter, egui::Rect, Color32, &mut IconAnimationState, AnimationCtx)
+ Send
+ Sync,
>,
),
/// Animated icon that renders using Ui primitives instead of Painter
AnimatedUi(
Box<
dyn Fn(&mut egui::Ui, egui::Rect, Color32, &mut IconAnimationState, AnimationCtx)
+ Send
+ Sync,
>,
),
}
/// Configuration for a custom icon button (internal use only).
pub struct CustomIconButton {
/// Icon kind to render.
pub icon: CustomIcon,
/// Optional tooltip displayed on hover.
pub tooltip: Option<String>,
/// Override hover background color.
pub hover_color: Option<Color32>,
/// Override icon color.
pub icon_color: Option<Color32>,
/// Optional click callback.
pub callback: Option<Box<dyn Fn() + Send + Sync>>,
/// Optional keyboard shortcut for this icon.
pub shortcut: Option<crate::KeyboardShortcut>,
}
/// Title bar state and configuration.
pub struct TitleBar {
/// Optional title text.
pub title: Option<String>,
/// Unique egui id for interactions.
pub id: Id,
/// Background color of the bar.
pub background_color: Color32,
/// Hover color for window controls.
pub hover_color: Color32,
/// Hover color for the close button.
pub close_hover_color: Color32,
/// Close icon color.
pub close_icon_color: Color32,
/// Maximize icon color.
pub maximize_icon_color: Color32,
/// Restore icon color.
pub restore_icon_color: Color32,
/// Minimize icon color.
pub minimize_icon_color: Color32,
/// Simple menu items (label, optional callback).
pub menu_items: Vec<(String, Option<Box<dyn Fn() + Send + Sync>>)>,
/// Menus with submenus.
pub menu_items_with_submenus: Vec<MenuItem>,
/// Index of currently open submenu.
pub open_submenu: Option<usize>,
/// Time when submenu was opened.
pub submenu_open_time: Option<f64>,
/// Guard to prevent immediate close after open in same frame.
pub submenu_just_opened_frame: bool,
/// Last click time used for overlay logic.
pub last_click_time: f64,
/// Monotonic id of last click used to open submenu.
pub last_click_id: usize,
/// Cached x positions for submenu alignment.
pub menu_positions: Vec<f32>,
/// Custom icon buttons shown on the right.
pub custom_icons: Vec<CustomIconButton>,
/// Optional app icon displayed next to the title (Windows/Linux).
pub app_icon: Option<ImageSource<'static>>,
/// Title text color.
pub title_color: Color32,
/// Title font size in points.
pub title_font_size: f32,
/// Selected theme mode for rendering.
pub theme_mode: ThemeMode,
/// Whether to display title on macOS.
pub show_title_on_macos: bool,
/// Whether to display title on Windows.
pub show_title_on_windows: bool,
/// Whether to display title on Linux.
pub show_title_on_linux: bool,
// Keyboard navigation state
/// Whether keyboard navigation is active.
pub keyboard_navigation_active: bool,
/// Currently selected top-level menu index.
pub selected_menu_index: Option<usize>,
/// Currently selected submenu item index (deprecated; use `submenu_selections`).
pub selected_submenu_index: Option<usize>,
/// Time of last keyboard navigation.
pub last_keyboard_nav_time: f64,
/// When set, force-open child submenu for this subitem index.
pub force_open_child_subitem: Option<usize>,
/// Currently selected child submenu item (deprecated; use `child_submenu_selections`).
pub selected_child_submenu_index: Option<usize>,
/// Map submenu index to selected item index.
pub submenu_selections: std::collections::HashMap<usize, usize>,
/// Map submenu index to selected child index.
pub child_submenu_selections: std::collections::HashMap<usize, usize>,
/// Menu text color.
pub menu_text_color: Color32,
/// Menu text size in points.
pub menu_text_size: f32,
/// Menu hover background color.
pub menu_hover_color: Color32,
/// Keyboard selection highlight color.
pub keyboard_selection_color: Color32,
// Submenu colors
/// Submenu background color.
pub submenu_background_color: Color32,
/// Submenu text color.
pub submenu_text_color: Color32,
/// Submenu text size in points.
pub submenu_text_size: f32,
/// Submenu hover background color.
pub submenu_hover_color: Color32,
/// Submenu disabled item color.
pub submenu_disabled_color: Color32,
/// Submenu shortcut text color.
pub submenu_shortcut_color: Color32,
/// Submenu border color.
pub submenu_border_color: Color32,
/// Submenu keyboard selection highlight color.
pub submenu_keyboard_selection_color: Color32,
// Optional external theme provider
/// Optional external theme provider.
pub theme_provider: Option<Box<dyn ThemeProvider + Send + Sync>>,
/// Current theme id, if any.
pub current_theme_id: Option<String>,
// Control button visibility
/// Whether to show the close button.
pub show_close_button: bool,
/// Whether to show the maximize button.
pub show_maximize_button: bool,
/// Whether to show the minimize button.
pub show_minimize_button: bool,
// Per custom icon animation states (kept aligned with custom_icons)
/// Per-icon animation state list aligned with `custom_icons`.
pub icon_animation_states: Vec<IconAnimationState>,
/// Spacing between custom icons in pixels.
pub icon_spacing: f32,
}
impl TitleBar {
/// Create a new title bar with options
///
/// # Examples
///
/// ```rust
/// // Simple title bar with title
/// TitleBar::new(TitleBarOptions::new().with_title("My App"))
///
/// // Icon-only title bar
/// TitleBar::new(TitleBarOptions::new())
///
/// // Custom themed title bar
/// TitleBar::new(
/// TitleBarOptions::new()
/// .with_title("My App")
/// .with_theme_mode(ThemeMode::Dark)
/// .with_background_color(Color32::from_rgb(30, 30, 30))
/// )
/// ```
pub fn new(options: TitleBarOptions) -> Self {
let theme = match options.theme_mode {
ThemeMode::Light => TitleBarTheme::light(),
ThemeMode::Dark => TitleBarTheme::dark(),
ThemeMode::System => {
if detect_system_dark_mode() {
TitleBarTheme::dark()
} else {
TitleBarTheme::light()
}
}
};
let title_bar = Self {
title: options.title,
id: Id::new("title_bar"),
background_color: options.background_color.unwrap_or(theme.background_color),
hover_color: options.hover_color.unwrap_or(theme.hover_color),
close_hover_color: options.close_hover_color.unwrap_or(theme.close_hover_color),
close_icon_color: options.close_icon_color.unwrap_or(theme.close_icon_color),
maximize_icon_color: options
.maximize_icon_color
.unwrap_or(theme.maximize_icon_color),
restore_icon_color: options
.restore_icon_color
.unwrap_or(theme.restore_icon_color),
minimize_icon_color: options
.minimize_icon_color
.unwrap_or(theme.minimize_icon_color),
menu_items: Vec::new(),
menu_items_with_submenus: Vec::new(),
open_submenu: None,
submenu_open_time: None,
submenu_just_opened_frame: false,
last_click_time: 0.0,
last_click_id: 0,
menu_positions: Vec::new(),
custom_icons: Vec::new(),
app_icon: options.app_icon,
// Initialize keyboard navigation state
keyboard_navigation_active: false,
selected_menu_index: None,
selected_submenu_index: None,
last_keyboard_nav_time: 0.0,
force_open_child_subitem: None,
selected_child_submenu_index: None,
submenu_selections: std::collections::HashMap::new(),
child_submenu_selections: std::collections::HashMap::new(),
title_color: options.title_color.unwrap_or(theme.title_color),
title_font_size: options.title_font_size.unwrap_or(12.0),
theme_mode: options.theme_mode,
show_title_on_macos: options.show_title_on_macos,
show_title_on_windows: options.show_title_on_windows,
show_title_on_linux: options.show_title_on_linux,
menu_text_color: options.menu_text_color.unwrap_or(theme.menu_text_color),
menu_text_size: options.menu_text_size.unwrap_or(theme.menu_text_size),
menu_hover_color: options.menu_hover_color.unwrap_or(theme.menu_hover_color),
keyboard_selection_color: options
.keyboard_selection_color
.unwrap_or(theme.keyboard_selection_color),
// Submenu colors
submenu_background_color: theme.submenu_background_color,
submenu_text_color: theme.submenu_text_color,
submenu_text_size: theme.submenu_text_size,
submenu_hover_color: theme.submenu_hover_color,
submenu_disabled_color: theme.submenu_disabled_color,
submenu_shortcut_color: theme.submenu_shortcut_color,
submenu_border_color: theme.submenu_border_color,
submenu_keyboard_selection_color: theme.submenu_keyboard_selection_color,
// Theme provider
theme_provider: None,
current_theme_id: None,
// Control button visibility (default to true if not specified)
show_close_button: options.show_close_button.unwrap_or(true),
show_maximize_button: options.show_maximize_button.unwrap_or(true),
show_minimize_button: options.show_minimize_button.unwrap_or(true),
icon_animation_states: Vec::new(),
icon_spacing: options.icon_spacing.unwrap_or(4.0),
};
title_bar
}
}
/// Public animation context passed to animated icon callbacks.
#[derive(Clone, Copy)]
pub struct AnimationCtx {
/// Absolute time in seconds.
pub time: f64,
/// Delta time since last frame in seconds.
pub delta_seconds: f32,
/// Whether the icon is hovered this frame.
pub hovered: bool,
/// Whether the icon is pressed this frame.
pub pressed: bool,
}
/// Per-icon animation state managed by the framework
#[derive(Clone, Copy, Default)]
pub struct IconAnimationState {
/// 0..1 smooth hover progress
pub hover_t: f32,
/// 0..1 smooth press progress
pub press_t: f32,
/// Last absolute time seen by this icon
pub last_time: f64,
/// Generic 0..1 progress you can drive from the callback
pub progress: f32,
}