egui/containers/
sides.rs

1use emath::Align;
2
3use crate::{Layout, Ui, UiBuilder};
4
5/// Put some widgets on the left and right sides of a ui.
6///
7/// The result will look like this:
8/// ```text
9///                        parent Ui
10///  ______________________________________________________
11/// |                    |           |                     |  ^
12/// | -> left widgets -> |    gap    | <- right widgets <- |  | height
13/// |____________________|           |_____________________|  v
14/// |                                                      |
15/// |                                                      |
16/// ```
17///
18/// The width of the gap is dynamic, based on the max width of the parent [`Ui`].
19/// When the parent is being auto-sized ([`Ui::is_sizing_pass`]) the gap will be as small as possible.
20///
21/// If the parent is not wide enough to fit all widgets, the parent will be expanded to the right.
22///
23/// The left widgets are first added to the ui, left-to-right.
24/// Then the right widgets are added, right-to-left.
25///
26/// ```
27/// # egui::__run_test_ui(|ui| {
28/// egui::containers::Sides::new().show(ui,
29///     |ui| {
30///         ui.label("Left");
31///     },
32///     |ui| {
33///         ui.label("Right");
34///     }
35/// );
36/// # });
37/// ```
38#[must_use = "You should call sides.show()"]
39#[derive(Clone, Copy, Debug, Default)]
40pub struct Sides {
41    height: Option<f32>,
42    spacing: Option<f32>,
43}
44
45impl Sides {
46    #[inline]
47    pub fn new() -> Self {
48        Default::default()
49    }
50
51    /// The minimum height of the sides.
52    ///
53    /// The content will be centered vertically within this height.
54    /// The default height is [`crate::Spacing::interact_size`]`.y`.
55    #[inline]
56    pub fn height(mut self, height: f32) -> Self {
57        self.height = Some(height);
58        self
59    }
60
61    /// The horizontal spacing between the left and right UIs.
62    ///
63    /// This is the minimum gap.
64    /// The default is [`crate::Spacing::item_spacing`]`.x`.
65    #[inline]
66    pub fn spacing(mut self, spacing: f32) -> Self {
67        self.spacing = Some(spacing);
68        self
69    }
70
71    pub fn show<RetL, RetR>(
72        self,
73        ui: &mut Ui,
74        add_left: impl FnOnce(&mut Ui) -> RetL,
75        add_right: impl FnOnce(&mut Ui) -> RetR,
76    ) -> (RetL, RetR) {
77        let Self { height, spacing } = self;
78        let height = height.unwrap_or_else(|| ui.spacing().interact_size.y);
79        let spacing = spacing.unwrap_or_else(|| ui.spacing().item_spacing.x);
80
81        let mut top_rect = ui.available_rect_before_wrap();
82        top_rect.max.y = top_rect.min.y + height;
83
84        let result_left;
85        let result_right;
86
87        let left_rect = {
88            let left_max_rect = top_rect;
89            let mut left_ui = ui.new_child(
90                UiBuilder::new()
91                    .max_rect(left_max_rect)
92                    .layout(Layout::left_to_right(Align::Center)),
93            );
94            result_left = add_left(&mut left_ui);
95            left_ui.min_rect()
96        };
97
98        let right_rect = {
99            let right_max_rect = top_rect.with_min_x(left_rect.max.x);
100            let mut right_ui = ui.new_child(
101                UiBuilder::new()
102                    .max_rect(right_max_rect)
103                    .layout(Layout::right_to_left(Align::Center)),
104            );
105            result_right = add_right(&mut right_ui);
106            right_ui.min_rect()
107        };
108
109        let mut final_rect = left_rect.union(right_rect);
110        let min_width = left_rect.width() + spacing + right_rect.width();
111
112        if ui.is_sizing_pass() {
113            // Make as small as possible:
114            final_rect.max.x = left_rect.min.x + min_width;
115        } else {
116            // If the rects overlap, make sure we expand the allocated rect so that the parent
117            // ui knows we overflowed, and resizes:
118            final_rect.max.x = final_rect.max.x.max(left_rect.min.x + min_width);
119        }
120
121        ui.advance_cursor_after_rect(final_rect);
122
123        (result_left, result_right)
124    }
125}