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