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}