egui/containers/
frame.rs

1//! Frame container
2
3use crate::{layers::ShapeIdx, *};
4use epaint::*;
5
6/// Add a background, frame and/or margin to a rectangular background of a [`Ui`].
7///
8/// ```
9/// # egui::__run_test_ui(|ui| {
10/// egui::Frame::none()
11///     .fill(egui::Color32::RED)
12///     .show(ui, |ui| {
13///         ui.label("Label with red background");
14///     });
15/// # });
16/// ```
17///
18/// ## Dynamic color
19/// If you want to change the color of the frame based on the response of
20/// the widget, you needs to break it up into multiple steps:
21///
22/// ```
23/// # egui::__run_test_ui(|ui| {
24/// let mut frame = egui::Frame::default().inner_margin(4.0).begin(ui);
25/// {
26///     let response = frame.content_ui.label("Inside the frame");
27///     if response.hovered() {
28///         frame.frame.fill = egui::Color32::RED;
29///     }
30/// }
31/// frame.end(ui); // Will "close" the frame.
32/// # });
33/// ```
34///
35/// You can also respond to the hovering of the frame itself:
36///
37/// ```
38/// # egui::__run_test_ui(|ui| {
39/// let mut frame = egui::Frame::default().inner_margin(4.0).begin(ui);
40/// {
41///     frame.content_ui.label("Inside the frame");
42///     frame.content_ui.label("This too");
43/// }
44/// let response = frame.allocate_space(ui);
45/// if response.hovered() {
46///     frame.frame.fill = egui::Color32::RED;
47/// }
48/// frame.paint(ui);
49/// # });
50/// ```
51///
52/// Note that you cannot change the margins after calling `begin`.
53#[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    /// Margin within the painted frame.
59    pub inner_margin: Margin,
60
61    /// Margin outside the painted frame.
62    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    /// For when you want to group a few widgets together within a frame.
79    pub fn group(style: &Style) -> Self {
80        Self {
81            inner_margin: Margin::same(6.0), // same and symmetric looks best in corners when nesting groups
82            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    /// A canvas to draw on.
138    ///
139    /// In bright mode this will be very bright,
140    /// and in dark mode this will be very dark.
141    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    /// A dark canvas to draw on.
152    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    /// Margin within the painted frame.
180    #[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    /// Margin outside the painted frame.
187    #[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    /// Opacity multiplier in gamma space.
200    ///
201    /// For instance, multiplying with `0.5`
202    /// will make the frame half transparent.
203    #[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    /// inner margin plus outer margin.
214    #[inline]
215    pub fn total_margin(&self) -> Margin {
216        self.inner_margin + self.outer_margin
217    }
218}
219
220// ----------------------------------------------------------------------------
221
222pub struct Prepared {
223    /// The frame that was prepared.
224    ///
225    /// The margin has already been read and used,
226    /// but the rest of the fields may be modified.
227    pub frame: Frame,
228
229    /// This is where we will insert the frame shape so it ends up behind the content.
230    where_to_put_background: ShapeIdx,
231
232    /// Add your widgets to this UI so it ends up within the frame.
233    pub content_ui: Ui,
234}
235
236impl Frame {
237    /// Begin a dynamically colored frame.
238    ///
239    /// This is a more advanced API.
240    /// Usually you want to use [`Self::show`] instead.
241    ///
242    /// See docs for [`Frame`] for an example.
243    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        // Make sure we don't shrink to the negative:
250        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        // content_ui.set_clip_rect(outer_rect_bounds.shrink(self.stroke.width * 0.5)); // Can't do this since we don't know final size yet
260
261        Prepared {
262            frame: self,
263            where_to_put_background,
264            content_ui,
265        }
266    }
267
268    /// Show the given ui surrounded by this frame.
269    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    /// Show using dynamic dispatch.
274    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    /// Paint this frame as a shape.
286    ///
287    /// The margin is ignored.
288    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    /// Allocate the space that was used by [`Self::content_ui`].
315    ///
316    /// This MUST be called, or the parent ui will not know how much space this widget used.
317    ///
318    /// This can be called before or after [`Self::paint`].
319    pub fn allocate_space(&self, ui: &mut Ui) -> Response {
320        ui.allocate_rect(self.content_with_margin(), Sense::hover())
321    }
322
323    /// Paint the frame.
324    ///
325    /// This can be called before or after [`Self::allocate_space`].
326    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    /// Convenience for calling [`Self::allocate_space`] and [`Self::paint`].
336    pub fn end(self, ui: &mut Ui) -> Response {
337        self.paint(ui);
338        self.allocate_space(ui)
339    }
340}