egui/containers/
frame.rs

1//! Frame container
2
3use 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/// Add a background, frame and/or margin to a rectangular background of a [`Ui`].
10///
11/// ```
12/// # egui::__run_test_ui(|ui| {
13/// egui::Frame::none()
14///     .fill(egui::Color32::RED)
15///     .show(ui, |ui| {
16///         ui.label("Label with red background");
17///     });
18/// # });
19/// ```
20///
21/// ## Dynamic color
22/// If you want to change the color of the frame based on the response of
23/// the widget, you needs to break it up into multiple steps:
24///
25/// ```
26/// # egui::__run_test_ui(|ui| {
27/// let mut frame = egui::Frame::default().inner_margin(4.0).begin(ui);
28/// {
29///     let response = frame.content_ui.label("Inside the frame");
30///     if response.hovered() {
31///         frame.frame.fill = egui::Color32::RED;
32///     }
33/// }
34/// frame.end(ui); // Will "close" the frame.
35/// # });
36/// ```
37///
38/// You can also respond to the hovering of the frame itself:
39///
40/// ```
41/// # egui::__run_test_ui(|ui| {
42/// let mut frame = egui::Frame::default().inner_margin(4.0).begin(ui);
43/// {
44///     frame.content_ui.label("Inside the frame");
45///     frame.content_ui.label("This too");
46/// }
47/// let response = frame.allocate_space(ui);
48/// if response.hovered() {
49///     frame.frame.fill = egui::Color32::RED;
50/// }
51/// frame.paint(ui);
52/// # });
53/// ```
54///
55/// Note that you cannot change the margins after calling `begin`.
56#[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    /// Margin within the painted frame.
62    pub inner_margin: Margin,
63
64    /// Margin outside the painted frame.
65    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    /// For when you want to group a few widgets together within a frame.
82    pub fn group(style: &Style) -> Self {
83        Self {
84            inner_margin: Margin::same(6.0), // same and symmetric looks best in corners when nesting groups
85            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    /// A canvas to draw on.
141    ///
142    /// In bright mode this will be very bright,
143    /// and in dark mode this will be very dark.
144    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    /// A dark canvas to draw on.
155    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    /// Margin within the painted frame.
183    #[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    /// Margin outside the painted frame.
190    #[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    /// Opacity multiplier in gamma space.
203    ///
204    /// For instance, multiplying with `0.5`
205    /// will make the frame half transparent.
206    #[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    /// inner margin plus outer margin.
217    #[inline]
218    pub fn total_margin(&self) -> Margin {
219        self.inner_margin + self.outer_margin
220    }
221}
222
223// ----------------------------------------------------------------------------
224
225pub struct Prepared {
226    /// The frame that was prepared.
227    ///
228    /// The margin has already been read and used,
229    /// but the rest of the fields may be modified.
230    pub frame: Frame,
231
232    /// This is where we will insert the frame shape so it ends up behind the content.
233    where_to_put_background: ShapeIdx,
234
235    /// Add your widgets to this UI so it ends up within the frame.
236    pub content_ui: Ui,
237}
238
239impl Frame {
240    /// Begin a dynamically colored frame.
241    ///
242    /// This is a more advanced API.
243    /// Usually you want to use [`Self::show`] instead.
244    ///
245    /// See docs for [`Frame`] for an example.
246    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        // Make sure we don't shrink to the negative:
253        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        // 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
263
264        Prepared {
265            frame: self,
266            where_to_put_background,
267            content_ui,
268        }
269    }
270
271    /// Show the given ui surrounded by this frame.
272    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    /// Show using dynamic dispatch.
277    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    /// Paint this frame as a shape.
289    ///
290    /// The margin is ignored.
291    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    /// Allocate the space that was used by [`Self::content_ui`].
318    ///
319    /// This MUST be called, or the parent ui will not know how much space this widget used.
320    ///
321    /// This can be called before or after [`Self::paint`].
322    pub fn allocate_space(&self, ui: &mut Ui) -> Response {
323        ui.allocate_rect(self.content_with_margin(), Sense::hover())
324    }
325
326    /// Paint the frame.
327    ///
328    /// This can be called before or after [`Self::allocate_space`].
329    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    /// Convenience for calling [`Self::allocate_space`] and [`Self::paint`].
339    pub fn end(self, ui: &mut Ui) -> Response {
340        self.paint(ui);
341        self.allocate_space(ui)
342    }
343}