egui_desktop/titlebar/
main.rs

1use egui::{Color32, Id, ImageSource, Painter};
2
3use crate::TitleBarOptions;
4use crate::menu::items::MenuItem;
5use crate::theme::{ThemeMode, ThemeProvider, TitleBarTheme, detect_system_dark_mode};
6
7/// Custom icon for the title bar
8pub enum CustomIcon {
9    /// SVG/PNG/JPEG image icon
10    Image(ImageSource<'static>),
11    /// Custom drawing function
12    Drawn(Box<dyn Fn(&Painter, egui::Rect, Color32) + Send + Sync>),
13    /// Animated icon with framework-managed animation state and context
14    Animated(
15        Box<
16            dyn Fn(&Painter, egui::Rect, Color32, &mut IconAnimationState, AnimationCtx)
17                + Send
18                + Sync,
19        >,
20    ),
21    /// Animated icon that renders using Ui primitives instead of Painter
22    AnimatedUi(
23        Box<
24            dyn Fn(&mut egui::Ui, egui::Rect, Color32, &mut IconAnimationState, AnimationCtx)
25                + Send
26                + Sync,
27        >,
28    ),
29}
30
31/// Configuration for a custom icon button (internal use only).
32pub struct CustomIconButton {
33    /// Icon kind to render.
34    pub icon: CustomIcon,
35    /// Optional tooltip displayed on hover.
36    pub tooltip: Option<String>,
37    /// Override hover background color.
38    pub hover_color: Option<Color32>,
39    /// Override icon color.
40    pub icon_color: Option<Color32>,
41    /// Optional click callback.
42    pub callback: Option<Box<dyn Fn() + Send + Sync>>,
43    /// Optional keyboard shortcut for this icon.
44    pub shortcut: Option<crate::KeyboardShortcut>,
45}
46
47/// Title bar state and configuration.
48pub struct TitleBar {
49    /// Optional title text.
50    pub title: Option<String>,
51    /// Unique egui id for interactions.
52    pub id: Id,
53    /// Background color of the bar.
54    pub background_color: Color32,
55    /// Hover color for window controls.
56    pub hover_color: Color32,
57    /// Hover color for the close button.
58    pub close_hover_color: Color32,
59    /// Close icon color.
60    pub close_icon_color: Color32,
61    /// Maximize icon color.
62    pub maximize_icon_color: Color32,
63    /// Restore icon color.
64    pub restore_icon_color: Color32,
65    /// Minimize icon color.
66    pub minimize_icon_color: Color32,
67    /// Simple menu items (label, optional callback).
68    pub menu_items: Vec<(String, Option<Box<dyn Fn() + Send + Sync>>)>,
69    /// Menus with submenus.
70    pub menu_items_with_submenus: Vec<MenuItem>,
71    /// Index of currently open submenu.
72    pub open_submenu: Option<usize>,
73    /// Time when submenu was opened.
74    pub submenu_open_time: Option<f64>,
75    /// Guard to prevent immediate close after open in same frame.
76    pub submenu_just_opened_frame: bool,
77    /// Last click time used for overlay logic.
78    pub last_click_time: f64,
79    /// Monotonic id of last click used to open submenu.
80    pub last_click_id: usize,
81    /// Cached x positions for submenu alignment.
82    pub menu_positions: Vec<f32>,
83    /// Custom icon buttons shown on the right.
84    pub custom_icons: Vec<CustomIconButton>,
85    /// Optional app icon displayed next to the title (Windows/Linux).
86    pub app_icon: Option<ImageSource<'static>>,
87    /// Title text color.
88    pub title_color: Color32,
89    /// Title font size in points.
90    pub title_font_size: f32,
91    /// Selected theme mode for rendering.
92    pub theme_mode: ThemeMode,
93    /// Whether to display title on macOS.
94    pub show_title_on_macos: bool,
95    /// Whether to display title on Windows.
96    pub show_title_on_windows: bool,
97    /// Whether to display title on Linux.
98    pub show_title_on_linux: bool,
99    // Keyboard navigation state
100    /// Whether keyboard navigation is active.
101    pub keyboard_navigation_active: bool,
102    /// Currently selected top-level menu index.
103    pub selected_menu_index: Option<usize>,
104    /// Currently selected submenu item index (deprecated; use `submenu_selections`).
105    pub selected_submenu_index: Option<usize>,
106    /// Time of last keyboard navigation.
107    pub last_keyboard_nav_time: f64,
108    /// When set, force-open child submenu for this subitem index.
109    pub force_open_child_subitem: Option<usize>,
110    /// Currently selected child submenu item (deprecated; use `child_submenu_selections`).
111    pub selected_child_submenu_index: Option<usize>,
112    /// Map submenu index to selected item index.
113    pub submenu_selections: std::collections::HashMap<usize, usize>,
114    /// Map submenu index to selected child index.
115    pub child_submenu_selections: std::collections::HashMap<usize, usize>,
116    /// Menu text color.
117    pub menu_text_color: Color32,
118    /// Menu text size in points.
119    pub menu_text_size: f32,
120    /// Menu hover background color.
121    pub menu_hover_color: Color32,
122    /// Keyboard selection highlight color.
123    pub keyboard_selection_color: Color32,
124    // Submenu colors
125    /// Submenu background color.
126    pub submenu_background_color: Color32,
127    /// Submenu text color.
128    pub submenu_text_color: Color32,
129    /// Submenu text size in points.
130    pub submenu_text_size: f32,
131    /// Submenu hover background color.
132    pub submenu_hover_color: Color32,
133    /// Submenu disabled item color.
134    pub submenu_disabled_color: Color32,
135    /// Submenu shortcut text color.
136    pub submenu_shortcut_color: Color32,
137    /// Submenu border color.
138    pub submenu_border_color: Color32,
139    /// Submenu keyboard selection highlight color.
140    pub submenu_keyboard_selection_color: Color32,
141    // Optional external theme provider
142    /// Optional external theme provider.
143    pub theme_provider: Option<Box<dyn ThemeProvider + Send + Sync>>,
144    /// Current theme id, if any.
145    pub current_theme_id: Option<String>,
146    // Control button visibility
147    /// Whether to show the close button.
148    pub show_close_button: bool,
149    /// Whether to show the maximize button.
150    pub show_maximize_button: bool,
151    /// Whether to show the minimize button.
152    pub show_minimize_button: bool,
153    // Per custom icon animation states (kept aligned with custom_icons)
154    /// Per-icon animation state list aligned with `custom_icons`.
155    pub icon_animation_states: Vec<IconAnimationState>,
156    /// Spacing between custom icons in pixels.
157    pub icon_spacing: f32,
158}
159
160impl TitleBar {
161    /// Create a new title bar with options
162    ///
163    /// # Examples
164    ///
165    /// ```rust
166    /// // Simple title bar with title
167    /// TitleBar::new(TitleBarOptions::new().with_title("My App"))
168    ///
169    /// // Icon-only title bar
170    /// TitleBar::new(TitleBarOptions::new())
171    ///
172    /// // Custom themed title bar
173    /// TitleBar::new(
174    ///     TitleBarOptions::new()
175    ///         .with_title("My App")
176    ///         .with_theme_mode(ThemeMode::Dark)
177    ///         .with_background_color(Color32::from_rgb(30, 30, 30))
178    /// )
179    /// ```
180    pub fn new(options: TitleBarOptions) -> Self {
181        let theme = match options.theme_mode {
182            ThemeMode::Light => TitleBarTheme::light(),
183            ThemeMode::Dark => TitleBarTheme::dark(),
184            ThemeMode::System => {
185                if detect_system_dark_mode() {
186                    TitleBarTheme::dark()
187                } else {
188                    TitleBarTheme::light()
189                }
190            }
191        };
192
193        let title_bar = Self {
194            title: options.title,
195            id: Id::new("title_bar"),
196            background_color: options.background_color.unwrap_or(theme.background_color),
197            hover_color: options.hover_color.unwrap_or(theme.hover_color),
198            close_hover_color: options.close_hover_color.unwrap_or(theme.close_hover_color),
199            close_icon_color: options.close_icon_color.unwrap_or(theme.close_icon_color),
200            maximize_icon_color: options
201                .maximize_icon_color
202                .unwrap_or(theme.maximize_icon_color),
203            restore_icon_color: options
204                .restore_icon_color
205                .unwrap_or(theme.restore_icon_color),
206            minimize_icon_color: options
207                .minimize_icon_color
208                .unwrap_or(theme.minimize_icon_color),
209            menu_items: Vec::new(),
210            menu_items_with_submenus: Vec::new(),
211            open_submenu: None,
212            submenu_open_time: None,
213            submenu_just_opened_frame: false,
214            last_click_time: 0.0,
215            last_click_id: 0,
216            menu_positions: Vec::new(),
217            custom_icons: Vec::new(),
218            app_icon: options.app_icon,
219            // Initialize keyboard navigation state
220            keyboard_navigation_active: false,
221            selected_menu_index: None,
222            selected_submenu_index: None,
223            last_keyboard_nav_time: 0.0,
224            force_open_child_subitem: None,
225            selected_child_submenu_index: None,
226            submenu_selections: std::collections::HashMap::new(),
227            child_submenu_selections: std::collections::HashMap::new(),
228            title_color: options.title_color.unwrap_or(theme.title_color),
229            title_font_size: options.title_font_size.unwrap_or(12.0),
230            theme_mode: options.theme_mode,
231            show_title_on_macos: options.show_title_on_macos,
232            show_title_on_windows: options.show_title_on_windows,
233            show_title_on_linux: options.show_title_on_linux,
234            menu_text_color: options.menu_text_color.unwrap_or(theme.menu_text_color),
235            menu_text_size: options.menu_text_size.unwrap_or(theme.menu_text_size),
236            menu_hover_color: options.menu_hover_color.unwrap_or(theme.menu_hover_color),
237            keyboard_selection_color: options
238                .keyboard_selection_color
239                .unwrap_or(theme.keyboard_selection_color),
240            // Submenu colors
241            submenu_background_color: theme.submenu_background_color,
242            submenu_text_color: theme.submenu_text_color,
243            submenu_text_size: theme.submenu_text_size,
244            submenu_hover_color: theme.submenu_hover_color,
245            submenu_disabled_color: theme.submenu_disabled_color,
246            submenu_shortcut_color: theme.submenu_shortcut_color,
247            submenu_border_color: theme.submenu_border_color,
248            submenu_keyboard_selection_color: theme.submenu_keyboard_selection_color,
249            // Theme provider
250            theme_provider: None,
251            current_theme_id: None,
252            // Control button visibility (default to true if not specified)
253            show_close_button: options.show_close_button.unwrap_or(true),
254            show_maximize_button: options.show_maximize_button.unwrap_or(true),
255            show_minimize_button: options.show_minimize_button.unwrap_or(true),
256            icon_animation_states: Vec::new(),
257            icon_spacing: options.icon_spacing.unwrap_or(4.0),
258        };
259
260        title_bar
261    }
262}
263
264/// Public animation context passed to animated icon callbacks.
265#[derive(Clone, Copy)]
266pub struct AnimationCtx {
267    /// Absolute time in seconds.
268    pub time: f64,
269    /// Delta time since last frame in seconds.
270    pub delta_seconds: f32,
271    /// Whether the icon is hovered this frame.
272    pub hovered: bool,
273    /// Whether the icon is pressed this frame.
274    pub pressed: bool,
275}
276
277/// Per-icon animation state managed by the framework
278#[derive(Clone, Copy, Default)]
279pub struct IconAnimationState {
280    /// 0..1 smooth hover progress
281    pub hover_t: f32,
282    /// 0..1 smooth press progress
283    pub press_t: f32,
284    /// Last absolute time seen by this icon
285    pub last_time: f64,
286    /// Generic 0..1 progress you can drive from the callback
287    pub progress: f32,
288}