egui_macview/controls/
box_view.rs1use crate::controls::MacInsets;
8use crate::layout::subpixel_snap_rect;
9use crate::theme::MacTheme;
10use egui::{
11 CornerRadius, InnerResponse, Pos2, Rect, Response, Sense, Stroke, StrokeKind, Ui,
12 UiBuilder, Vec2, WidgetText,
13};
14
15pub struct MacBox<'a> {
36 title: Option<WidgetText>,
37 theme: Option<&'a MacTheme>,
38}
39
40impl<'a> MacBox<'a> {
41 pub fn new(title: impl Into<WidgetText>) -> Self {
43 Self {
44 title: Some(title.into()),
45 theme: None,
46 }
47 }
48
49 pub fn untitled() -> Self {
51 Self {
52 title: None,
53 theme: None,
54 }
55 }
56
57 pub fn theme(mut self, theme: &'a MacTheme) -> Self {
59 self.theme = Some(theme);
60 self
61 }
62
63 pub fn content_insets(&self) -> MacInsets {
65 let theme_cow = MacTheme::resolve_or_default(self.theme);
66 let m = theme_cow.box_view();
67 MacInsets {
68 top: m.inset_top.points(),
69 bottom: m.inset_bottom.points(),
70 left: m.inset_left.points(),
71 right: m.inset_right.points(),
72 }
73 }
74
75 pub fn corner_radius(&self) -> f32 {
77 let theme_cow = MacTheme::resolve_or_default(self.theme);
78 theme_cow.box_view().corner_radius.points()
79 }
80
81 pub fn border_width(&self) -> f32 {
83 let theme_cow = MacTheme::resolve_or_default(self.theme);
84 theme_cow.box_view().border_width.points()
85 }
86
87 pub fn show<R>(
89 self,
90 ui: &mut Ui,
91 add_contents: impl FnOnce(&mut Ui) -> R,
92 ) -> InnerResponse<R> {
93 let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
94 let theme: &MacTheme = &theme_cow;
95 let insets = self.content_insets();
96 let corner_r = self.corner_radius();
97 let border_w = self.border_width();
98
99 let available_w = ui.available_width();
100 let start_pos = ui.cursor().min;
101
102 let content_rect = Rect::from_min_max(
103 Pos2::new(start_pos.x + insets.left, start_pos.y + insets.top),
104 Pos2::new(start_pos.x + available_w - insets.right, f32::INFINITY),
105 );
106
107 let bg_shape_idx = ui.painter().add(egui::Shape::Noop);
109 let box_scope_id = ui.next_auto_id().with("mac_box_content");
110
111 let mut child_ui = ui.new_child(
112 UiBuilder::new()
113 .id_salt(box_scope_id)
114 .max_rect(content_rect),
115 );
116 let inner = add_contents(&mut child_ui);
117 let content_min_rect = child_ui.min_rect();
118
119 let box_h = insets.top + content_min_rect.height() + insets.bottom;
120 let box_rect = Rect::from_min_size(start_pos, Vec2::new(available_w, box_h));
121 let box_rect = subpixel_snap_rect(box_rect, ui.ctx().pixels_per_point());
122
123 let (rect, response) = ui.allocate_exact_size(box_rect.size(), Sense::hover());
124
125 ui.painter().set(
127 bg_shape_idx,
128 egui::epaint::RectShape::new(
129 rect,
130 CornerRadius::same(corner_r as u8),
131 theme.control_background(),
132 Stroke::new(border_w, theme.separator()),
133 StrokeKind::Inside,
134 ),
135 );
136
137 if let Some(title) = self.title {
139 let galley = title.into_galley(
140 ui,
141 Some(egui::TextWrapMode::Extend),
142 available_w,
143 egui::TextStyle::Body,
144 );
145 let title_pos = Pos2::new(rect.min.x + insets.left, rect.min.y + insets.top * 0.5 - galley.size().y * 0.5);
146 ui.painter().galley(title_pos, galley, theme.label());
147 }
148
149 InnerResponse::new(inner, response)
150 }
151
152 pub fn separator(ui: &mut Ui) -> Response {
154 let theme = MacTheme::resolve(None, ui.ctx());
155 let frame_h = theme.box_view().separator_frame_height.points();
156 let border_w = theme.box_view().border_width.points();
157
158 let (rect, response) = ui.allocate_exact_size(Vec2::new(ui.available_width(), frame_h), Sense::hover());
159 let painter = ui.painter_at(rect);
160 let cy = rect.center().y;
161
162 painter.line_segment(
163 [Pos2::new(rect.min.x, cy), Pos2::new(rect.max.x, cy)],
164 Stroke::new(border_w, theme.separator()),
165 );
166
167 response
168 }
169}