1use crate::{
4 epaint, layers::ShapeIdx, InnerResponse, Response, Sense, Style, Ui, UiBuilder, UiKind,
5 UiStackInfo,
6};
7use epaint::{Color32, Margin, Rect, Rounding, Shadow, Shape, Stroke};
8
9#[doc(alias = "border")]
57#[derive(Clone, Copy, Debug, Default, PartialEq)]
58#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
59#[must_use = "You should call .show()"]
60pub struct Frame {
61 pub inner_margin: Margin,
63
64 pub outer_margin: Margin,
66
67 pub rounding: Rounding,
68
69 pub shadow: Shadow,
70
71 pub fill: Color32,
72
73 pub stroke: Stroke,
74}
75
76impl Frame {
77 pub fn none() -> Self {
78 Self::default()
79 }
80
81 pub fn group(style: &Style) -> Self {
83 Self {
84 inner_margin: Margin::same(6.0), rounding: style.visuals.widgets.noninteractive.rounding,
86 stroke: style.visuals.widgets.noninteractive.bg_stroke,
87 ..Default::default()
88 }
89 }
90
91 pub fn side_top_panel(style: &Style) -> Self {
92 Self {
93 inner_margin: Margin::symmetric(8.0, 2.0),
94 fill: style.visuals.panel_fill,
95 ..Default::default()
96 }
97 }
98
99 pub fn central_panel(style: &Style) -> Self {
100 Self {
101 inner_margin: Margin::same(8.0),
102 fill: style.visuals.panel_fill,
103 ..Default::default()
104 }
105 }
106
107 pub fn window(style: &Style) -> Self {
108 Self {
109 inner_margin: style.spacing.window_margin,
110 rounding: style.visuals.window_rounding,
111 shadow: style.visuals.window_shadow,
112 fill: style.visuals.window_fill(),
113 stroke: style.visuals.window_stroke(),
114 ..Default::default()
115 }
116 }
117
118 pub fn menu(style: &Style) -> Self {
119 Self {
120 inner_margin: style.spacing.menu_margin,
121 rounding: style.visuals.menu_rounding,
122 shadow: style.visuals.popup_shadow,
123 fill: style.visuals.window_fill(),
124 stroke: style.visuals.window_stroke(),
125 ..Default::default()
126 }
127 }
128
129 pub fn popup(style: &Style) -> Self {
130 Self {
131 inner_margin: style.spacing.menu_margin,
132 rounding: style.visuals.menu_rounding,
133 shadow: style.visuals.popup_shadow,
134 fill: style.visuals.window_fill(),
135 stroke: style.visuals.window_stroke(),
136 ..Default::default()
137 }
138 }
139
140 pub fn canvas(style: &Style) -> Self {
145 Self {
146 inner_margin: Margin::same(2.0),
147 rounding: style.visuals.widgets.noninteractive.rounding,
148 fill: style.visuals.extreme_bg_color,
149 stroke: style.visuals.window_stroke(),
150 ..Default::default()
151 }
152 }
153
154 pub fn dark_canvas(style: &Style) -> Self {
156 Self {
157 fill: Color32::from_black_alpha(250),
158 ..Self::canvas(style)
159 }
160 }
161}
162
163impl Frame {
164 #[inline]
165 pub fn fill(mut self, fill: Color32) -> Self {
166 self.fill = fill;
167 self
168 }
169
170 #[inline]
171 pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
172 self.stroke = stroke.into();
173 self
174 }
175
176 #[inline]
177 pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
178 self.rounding = rounding.into();
179 self
180 }
181
182 #[inline]
184 pub fn inner_margin(mut self, inner_margin: impl Into<Margin>) -> Self {
185 self.inner_margin = inner_margin.into();
186 self
187 }
188
189 #[inline]
191 pub fn outer_margin(mut self, outer_margin: impl Into<Margin>) -> Self {
192 self.outer_margin = outer_margin.into();
193 self
194 }
195
196 #[inline]
197 pub fn shadow(mut self, shadow: Shadow) -> Self {
198 self.shadow = shadow;
199 self
200 }
201
202 #[inline]
207 pub fn multiply_with_opacity(mut self, opacity: f32) -> Self {
208 self.fill = self.fill.gamma_multiply(opacity);
209 self.stroke.color = self.stroke.color.gamma_multiply(opacity);
210 self.shadow.color = self.shadow.color.gamma_multiply(opacity);
211 self
212 }
213}
214
215impl Frame {
216 #[inline]
218 pub fn total_margin(&self) -> Margin {
219 self.inner_margin + self.outer_margin
220 }
221}
222
223pub struct Prepared {
226 pub frame: Frame,
231
232 where_to_put_background: ShapeIdx,
234
235 pub content_ui: Ui,
237}
238
239impl Frame {
240 pub fn begin(self, ui: &mut Ui) -> Prepared {
247 let where_to_put_background = ui.painter().add(Shape::Noop);
248 let outer_rect_bounds = ui.available_rect_before_wrap();
249
250 let mut inner_rect = outer_rect_bounds - self.outer_margin - self.inner_margin;
251
252 inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
254 inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y);
255
256 let content_ui = ui.new_child(
257 UiBuilder::new()
258 .ui_stack_info(UiStackInfo::new(UiKind::Frame).with_frame(self))
259 .max_rect(inner_rect),
260 );
261
262 Prepared {
265 frame: self,
266 where_to_put_background,
267 content_ui,
268 }
269 }
270
271 pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
273 self.show_dyn(ui, Box::new(add_contents))
274 }
275
276 pub fn show_dyn<'c, R>(
278 self,
279 ui: &mut Ui,
280 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
281 ) -> InnerResponse<R> {
282 let mut prepared = self.begin(ui);
283 let ret = add_contents(&mut prepared.content_ui);
284 let response = prepared.end(ui);
285 InnerResponse::new(ret, response)
286 }
287
288 pub fn paint(&self, outer_rect: Rect) -> Shape {
292 let Self {
293 inner_margin: _,
294 outer_margin: _,
295 rounding,
296 shadow,
297 fill,
298 stroke,
299 } = *self;
300
301 let frame_shape = Shape::Rect(epaint::RectShape::new(outer_rect, rounding, fill, stroke));
302
303 if shadow == Default::default() {
304 frame_shape
305 } else {
306 let shadow = shadow.as_shape(outer_rect, rounding);
307 Shape::Vec(vec![Shape::from(shadow), frame_shape])
308 }
309 }
310}
311
312impl Prepared {
313 fn content_with_margin(&self) -> Rect {
314 self.content_ui.min_rect() + self.frame.inner_margin + self.frame.outer_margin
315 }
316
317 pub fn allocate_space(&self, ui: &mut Ui) -> Response {
323 ui.allocate_rect(self.content_with_margin(), Sense::hover())
324 }
325
326 pub fn paint(&self, ui: &Ui) {
330 let paint_rect = self.content_ui.min_rect() + self.frame.inner_margin;
331
332 if ui.is_rect_visible(paint_rect) {
333 let shape = self.frame.paint(paint_rect);
334 ui.painter().set(self.where_to_put_background, shape);
335 }
336 }
337
338 pub fn end(self, ui: &mut Ui) -> Response {
340 self.paint(ui);
341 self.allocate_space(ui)
342 }
343}