egui_desktop/titlebar/
control_buttons.rs

1use egui::{
2    Color32, CursorIcon, Painter, Pos2, Rect, Response, Sense, Stroke, StrokeKind, Ui, Vec2,
3};
4
5use crate::TitleBar;
6
7/// Window control icon types used by the title bar.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum WindowControlIcon {
10    /// Close the window.
11    Close,
12    /// Maximize the window.
13    Maximize,
14    /// Restore the window from maximized state.
15    Restore,
16    /// Minimize the window.
17    Minimize,
18}
19
20impl TitleBar {
21    /// Draw the close button icon (X shape)
22    ///
23    /// Draws two diagonal lines forming an X shape for the close button.
24    ///
25    /// # Arguments
26    /// * `painter` - The egui painter to draw with
27    /// * `rect` - The bounding rectangle for the icon
28    /// * `color` - The color of the icon lines
29    fn draw_close_icon(&self, painter: &egui::Painter, rect: Rect, color: Color32) {
30        let center = rect.center();
31        let size = rect.width().min(rect.height()) * 0.6;
32        let half_size = size / 2.0;
33
34        let stroke = Stroke::new(1.5, color);
35        painter.line_segment(
36            [
37                center + Vec2::new(-half_size, -half_size),
38                center + Vec2::new(half_size, half_size),
39            ],
40            stroke,
41        );
42        painter.line_segment(
43            [
44                center + Vec2::new(half_size, -half_size),
45                center + Vec2::new(-half_size, half_size),
46            ],
47            stroke,
48        );
49    }
50
51    /// Draw the maximize button icon (square shape)
52    ///
53    /// Draws a square outline representing the maximize button.
54    ///
55    /// # Arguments
56    /// * `painter` - The egui painter to draw with
57    /// * `rect` - The bounding rectangle for the icon
58    /// * `color` - The color of the icon lines
59    fn draw_maximize_icon(&self, painter: &Painter, rect: egui::Rect, color: Color32) {
60        let center = rect.center();
61        let size = rect.width().min(rect.height()) * 0.75;
62        let stroke = Stroke::new(1.5, color);
63        let square_rect = Rect::from_center_size(center, Vec2::new(size, size));
64        painter.rect_stroke(square_rect, 0.0, stroke, StrokeKind::Inside);
65    }
66
67    /// Draw the restore button icon (overlapping squares)
68    ///
69    /// Draws a main square with two perpendicular lines representing an overlapping
70    /// second square, indicating the restore down functionality.
71    ///
72    /// # Arguments
73    /// * `painter` - The egui painter to draw with
74    /// * `rect` - The bounding rectangle for the icon
75    /// * `color` - The color of the icon lines
76    fn draw_restore_icon(&self, painter: &Painter, rect: Rect, color: Color32) {
77        let button_size = rect.width().min(rect.height());
78        let square_size = button_size * 0.85;
79        let icon_rect = Rect::from_center_size(rect.center(), Vec2::new(square_size, square_size));
80
81        let center = icon_rect.center();
82        let half_size = square_size / 2.0;
83
84        let stroke = Stroke::new(1.5, color);
85
86        let main_square_size = square_size * 0.7;
87        let main_square_center = center + Vec2::new(-half_size * 0.2, 0.0);
88        let main_square = Rect::from_center_size(
89            main_square_center,
90            Vec2::new(main_square_size, main_square_size),
91        );
92        painter.rect_stroke(main_square, 0.0, stroke, StrokeKind::Inside);
93
94        let spacing = half_size * 0.12;
95
96        let horizontal_start = center + Vec2::new(-half_size * 0.3, -half_size + spacing);
97        let horizontal_end = center + Vec2::new(half_size - spacing, -half_size + spacing);
98
99        let vertical_start = center + Vec2::new(half_size - spacing, -half_size + spacing);
100        let vertical_end = center + Vec2::new(half_size - spacing, half_size * 0.2);
101
102        painter.line_segment([horizontal_start, horizontal_end], stroke);
103        painter.line_segment([vertical_start, vertical_end], stroke);
104    }
105
106    /// Draw the minimize button icon (horizontal line)
107    ///
108    /// Draws a horizontal line representing the minimize button.
109    ///
110    /// # Arguments
111    /// * `painter` - The egui painter to draw with
112    /// * `rect` - The bounding rectangle for the icon
113    /// * `color` - The color of the icon line
114    fn draw_minimize_icon(&self, painter: &Painter, rect: Rect, color: Color32) {
115        let center = rect.center();
116        let size = rect.width().min(rect.height()) * 0.8;
117        let half_size = size / 2.0;
118
119        let stroke = Stroke::new(2.0, color);
120        painter.line_segment(
121            [
122                center + Vec2::new(-half_size, 0.0),
123                center + Vec2::new(half_size, 0.0),
124            ],
125            stroke,
126        );
127    }
128
129    /// Render a macOS-style traffic light button.
130    pub fn render_traffic_light(&self, ui: &mut Ui, color: Color32, size: f32) -> egui::Response {
131        let button_size = Vec2::new(size, size);
132        let (button_id, button_rect) = ui.allocate_space(button_size);
133
134        let y_center = 14.0;
135        let centered_pos = Pos2::new(button_rect.center().x, y_center);
136
137        ui.painter().circle_filled(centered_pos, size / 2.0, color);
138        ui.painter().circle_stroke(
139            centered_pos,
140            size / 2.0,
141            Stroke::new(0.5, Color32::from_rgba_premultiplied(0, 0, 0, 30)),
142        );
143
144        let centered_rect = Rect::from_center_size(centered_pos, button_size);
145        let response = ui.interact(centered_rect, button_id, Sense::click());
146
147        if response.hovered() {
148            ui.ctx().set_cursor_icon(CursorIcon::PointingHand);
149        }
150
151        response
152    }
153
154    /// Render a window control button with a drawn icon
155    ///
156    /// This method creates an interactive button for window controls (close, maximize,
157    /// restore, minimize) with custom drawn icons instead of SVG images.
158    ///
159    /// # Arguments
160    /// * `ui` - The egui UI context
161    /// * `icon_type` - The type of icon to draw
162    /// * `hover_color` - The background color when hovering
163    /// * `icon_color` - The color of the icon
164    /// * `icon_size` - The size of the icon
165    ///
166    /// # Returns
167    /// * `egui::Response` - The interaction response for the button
168    pub fn render_window_control_button_with_drawn_icon(
169        &self,
170        ui: &mut Ui,
171        icon_type: WindowControlIcon,
172        hover_color: Color32,
173        icon_color: Color32,
174        icon_size: f32,
175    ) -> Response {
176        let desired_size = Vec2::new(46.0, 32.0);
177        let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
178
179        if response.hovered() {
180            ui.painter().rect_filled(rect, 2.0, hover_color);
181            ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
182        }
183
184        let icon_rect = Rect::from_center_size(rect.center(), Vec2::new(icon_size, icon_size));
185
186        let final_icon_color = if response.hovered() && hover_color == self.close_hover_color {
187            Color32::WHITE
188        } else {
189            icon_color
190        };
191
192        match icon_type {
193            WindowControlIcon::Close => {
194                self.draw_close_icon(ui.painter(), icon_rect, final_icon_color)
195            }
196            WindowControlIcon::Maximize => {
197                self.draw_maximize_icon(ui.painter(), icon_rect, final_icon_color)
198            }
199            WindowControlIcon::Restore => {
200                self.draw_restore_icon(ui.painter(), icon_rect, final_icon_color)
201            }
202            WindowControlIcon::Minimize => {
203                self.draw_minimize_icon(ui.painter(), icon_rect, final_icon_color)
204            }
205        }
206
207        response
208    }
209}