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