1use super::{Theme, utils};
2use egui::*;
3
4pub struct WindowCtx {
5 pub frame: Frame,
6 pub title: String,
7 pub bar_height: f32,
8 pub title_text_size: f32,
9 pub title_text_color: Color32,
10 pub line_stroke: Stroke,
11 pub button_text_size: f32,
12 pub button_text_color: Color32,
13 pub on_hover_color: Color32,
14 pub close_on_hover_color: Color32,
15}
16
17impl WindowCtx {
18 pub fn new(title: &str, bar_height: f32, theme: &Theme) -> Self {
19 let frame = theme.window_frame;
20 let title_text_size = theme.text_sizes.heading;
21 let title_text_color = theme.colors.text;
22 let line_stroke = Stroke::new(0.0, theme.colors.border);
23 let button_text_size = theme.text_sizes.large;
24 let button_text_color = theme.colors.text;
25 let on_hover_color = theme.colors.hover;
26 let close_on_hover_color = theme.colors.error;
27
28 Self {
29 frame,
30 title: title.to_owned(),
31 bar_height,
32 title_text_size,
33 title_text_color,
34 line_stroke,
35 button_text_size,
36 button_text_color,
37 on_hover_color,
38 close_on_hover_color,
39 }
40 }
41
42 pub fn with_frame(mut self, frame: Frame) -> Self {
43 self.frame = frame;
44 self
45 }
46
47 pub fn with_line_stroke(mut self, stroke: Stroke) -> Self {
48 self.line_stroke = stroke;
49 self
50 }
51
52 pub fn with_title_text_size(mut self, size: f32) -> Self {
53 self.title_text_size = size;
54 self
55 }
56
57 pub fn with_title_text_color(mut self, color: Color32) -> Self {
58 self.title_text_color = color;
59 self
60 }
61
62 pub fn with_button_text_color(mut self, color: Color32) -> Self {
63 self.button_text_color = color;
64 self
65 }
66
67 pub fn with_on_hover_color(mut self, color: Color32) -> Self {
68 self.on_hover_color = color;
69 self
70 }
71
72 pub fn with_close_on_hover_color(mut self, color: Color32) -> Self {
73 self.close_on_hover_color = color;
74 self
75 }
76}
77
78pub fn window_frame(ui: &mut Ui, window_ctx: WindowCtx, add_contents: impl FnOnce(&mut Ui)) {
79 CentralPanel::default().frame(window_ctx.frame).show_inside(ui, |ui| {
80 let app_rect = ui.max_rect();
81
82 let title_bar_rect = {
83 let mut rect = app_rect;
84 rect.max.y = window_ctx.bar_height;
85 rect
86 };
87
88 title_bar_ui(ui, &window_ctx, title_bar_rect);
89
90 let content_rect = {
92 let mut rect = app_rect;
93 rect.min.y = window_ctx.bar_height;
94 rect
95 };
96
97 let ui_builder = UiBuilder::default().max_rect(content_rect).style(ui.global_style().clone());
98 let mut content_ui = ui.new_child(ui_builder);
99 add_contents(&mut content_ui);
100 });
101}
102
103fn title_bar_ui(ui: &mut Ui, window: &WindowCtx, title_bar_rect: Rect) {
104 let painter = ui.painter();
105
106 let title_bar_response = ui.interact(
107 title_bar_rect,
108 Id::new("title_bar"),
109 Sense::click_and_drag(),
110 );
111
112 painter.text(
114 title_bar_rect.center(),
115 Align2::CENTER_CENTER,
116 window.title.clone(),
117 FontId::proportional(window.title_text_size),
118 window.title_text_color,
119 );
120
121 let y = title_bar_rect.bottom() - window.line_stroke.width / 2.0;
123 painter.line_segment(
124 [
125 pos2(title_bar_rect.left() + 1.0, y),
126 pos2(title_bar_rect.right() - 1.0, y),
127 ],
128 window.line_stroke,
129 );
130
131 if title_bar_response.double_clicked() {
133 let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false));
134 ui.ctx().send_viewport_cmd(ViewportCommand::Maximized(!is_maximized));
135 }
136
137 if title_bar_response.drag_started_by(PointerButton::Primary) {
138 ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag);
139 }
140
141 let ui_builder = UiBuilder::default().max_rect(title_bar_rect).style(ui.style().clone());
142 let layout = Layout::right_to_left(Align::Center);
143
144 ui.scope_builder(ui_builder, |ui| {
145 ui.with_layout(layout, |ui| {
146 close_maximize_minimize(ui, &window);
147 });
148 });
149}
150
151fn close_maximize_minimize(ui: &mut Ui, window: &WindowCtx) {
153 ui.spacing_mut().button_padding = vec2(0.0, 0.0);
154 ui.spacing_mut().item_spacing = vec2(0.0, 0.0);
155
156 utils::bg_color_on_idle(ui, Color32::TRANSPARENT);
157 utils::no_border(ui);
158
159 ui.style_mut().visuals.widgets.inactive.expansion = 0.0;
160 ui.style_mut().visuals.widgets.hovered.expansion = 0.0;
161 ui.style_mut().visuals.widgets.active.expansion = 0.0;
162 ui.style_mut().visuals.widgets.inactive.corner_radius = CornerRadius::ZERO;
163 ui.style_mut().visuals.widgets.hovered.corner_radius = CornerRadius::ZERO;
164 ui.style_mut().visuals.widgets.active.corner_radius = CornerRadius::ZERO;
165
166 let button_size = vec2(45.0, window.bar_height);
167 let text_size = window.button_text_size;
168 let text_color = window.button_text_color;
169
170 let add_title_button = |ui: &mut Ui, text: &str, hover_color: Color32| -> bool {
171 ui.scope(|ui| {
172 utils::bg_color_on_hover(ui, hover_color);
173 let rich_text = RichText::new(text).color(text_color).size(text_size);
174 let button = Button::new(rich_text).min_size(button_size);
175
176 ui.add_sized(button_size, button).clicked()
177 })
178 .inner
179 };
180
181 if add_title_button(ui, "❌", window.close_on_hover_color) {
183 ui.ctx().send_viewport_cmd(ViewportCommand::Close);
184 }
185
186 let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false));
187
188 let max_icon = if is_maximized { "🗗" } else { "🗖" };
190 if add_title_button(ui, max_icon, window.on_hover_color) {
191 ui.ctx().send_viewport_cmd(ViewportCommand::Maximized(!is_maximized));
192 }
193
194 if add_title_button(ui, "🗕", window.on_hover_color) {
196 ui.ctx().send_viewport_cmd(ViewportCommand::Minimized(true));
197 }
198}