egui_components/
titlebar.rs1use egui::{pos2, vec2, Align, Layout, Rect, Sense, Ui, UiBuilder, ViewportCommand};
15use egui_components_theme::Theme;
16
17use crate::icon::{paint_icon, IconKind};
18
19pub struct TitleBar {
20 title: String,
21 height: f32,
22 window_controls: bool,
23}
24
25impl TitleBar {
26 pub fn new(title: impl Into<String>) -> Self {
27 Self {
28 title: title.into(),
29 height: 38.0,
30 window_controls: true,
31 }
32 }
33 pub fn height(mut self, h: f32) -> Self {
34 self.height = h;
35 self
36 }
37 pub fn no_window_controls(mut self) -> Self {
39 self.window_controls = false;
40 self
41 }
42
43 pub fn show(self, ui: &mut Ui, right_content: impl FnOnce(&mut Ui)) {
44 let theme = Theme::get(ui.ctx());
45 let c = theme.colors;
46
47 let width = ui.available_width();
48 let (rect, bar_resp) =
49 ui.allocate_exact_size(vec2(width, self.height), Sense::click_and_drag());
50
51 if bar_resp.drag_started() {
53 ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag);
54 }
55 if bar_resp.double_clicked() {
56 let maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false));
57 ui.ctx()
58 .send_viewport_cmd(ViewportCommand::Maximized(!maximized));
59 }
60
61 ui.painter().rect_filled(rect, 0.0, c.background);
63 ui.painter().line_segment(
64 [rect.left_bottom(), rect.right_bottom()],
65 theme.border_stroke(),
66 );
67
68 ui.painter().text(
70 pos2(rect.left() + 14.0, rect.center().y),
71 egui::Align2::LEFT_CENTER,
72 &self.title,
73 egui::FontId::proportional(theme.metrics.font_size_md),
74 c.foreground,
75 );
76
77 let mut right_edge = rect.right();
79 if self.window_controls {
80 let btn_w = self.height * 1.2;
81 let close = control_button(ui, btn_rect(rect, right_edge, btn_w), IconKind::Close, true);
82 if close {
83 ui.ctx().send_viewport_cmd(ViewportCommand::Close);
84 }
85 right_edge -= btn_w;
86
87 let is_max = ui.input(|i| i.viewport().maximized.unwrap_or(false));
88 if maximize_button(ui, btn_rect(rect, right_edge, btn_w)) {
89 ui.ctx()
90 .send_viewport_cmd(ViewportCommand::Maximized(!is_max));
91 }
92 right_edge -= btn_w;
93
94 if control_button(ui, btn_rect(rect, right_edge, btn_w), IconKind::Minus, false) {
95 ui.ctx().send_viewport_cmd(ViewportCommand::Minimized(true));
96 }
97 right_edge -= btn_w;
98 }
99
100 let content_rect = Rect::from_min_max(
102 pos2(rect.left() + 120.0, rect.top()),
103 pos2(right_edge - 4.0, rect.bottom()),
104 );
105 if content_rect.width() > 0.0 {
106 let mut content = ui.new_child(
107 UiBuilder::new()
108 .max_rect(content_rect)
109 .layout(Layout::right_to_left(Align::Center)),
110 );
111 right_content(&mut content);
112 }
113 }
114}
115
116fn maximize_button(ui: &mut Ui, rect: Rect) -> bool {
118 let theme = Theme::get(ui.ctx());
119 let c = theme.colors;
120 let resp = ui.interact(
121 rect,
122 ui.id().with(("titlebar-max", rect.center().x as i32)),
123 Sense::click(),
124 );
125 if ui.is_rect_visible(rect) {
126 let painter = ui.painter();
127 if resp.hovered() {
128 painter.rect_filled(rect, 0.0, c.accent_background);
129 }
130 let sq = Rect::from_center_size(rect.center(), vec2(11.0, 11.0));
131 painter.rect_stroke(
132 sq,
133 egui::CornerRadius::same(1),
134 egui::Stroke::new(1.5, c.foreground),
135 egui::StrokeKind::Inside,
136 );
137 if resp.hovered() {
138 ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
139 }
140 }
141 resp.clicked()
142}
143
144fn btn_rect(bar: Rect, right_edge: f32, w: f32) -> Rect {
145 Rect::from_min_max(
146 pos2(right_edge - w, bar.top()),
147 pos2(right_edge, bar.bottom()),
148 )
149}
150
151fn control_button(ui: &mut Ui, rect: Rect, icon: IconKind, danger: bool) -> bool {
153 let theme = Theme::get(ui.ctx());
154 let c = theme.colors;
155 let resp = ui.interact(rect, ui.id().with(("titlebar-ctl", rect.center().x as i32)), Sense::click());
156 if ui.is_rect_visible(rect) {
157 let painter = ui.painter();
158 if resp.hovered() {
159 let bg = if danger {
160 c.danger_background
161 } else {
162 c.accent_background
163 };
164 painter.rect_filled(rect, 0.0, bg);
165 }
166 let fg = if danger && resp.hovered() {
167 c.danger_foreground
168 } else {
169 c.foreground
170 };
171 let ir = Rect::from_center_size(rect.center(), vec2(14.0, 14.0));
172 paint_icon(painter, icon, ir, fg, 1.5);
173 if resp.hovered() {
174 ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
175 }
176 }
177 resp.clicked()
178}