egui/containers/sides.rs
1use emath::{Align, NumExt as _};
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 added left-to-right.
24/// The right widgets are added right-to-left.
25///
26/// Which side is first depends on the configuration:
27/// - [`Sides::extend`] - left widgets are added first
28/// - [`Sides::shrink_left`] - right widgets are added first
29/// - [`Sides::shrink_right`] - left widgets are added first
30///
31/// ```
32/// # egui::__run_test_ui(|ui| {
33/// egui::containers::Sides::new().show(ui,
34/// |ui| {
35/// ui.label("Left");
36/// },
37/// |ui| {
38/// ui.label("Right");
39/// }
40/// );
41/// # });
42/// ```
43#[must_use = "You should call sides.show()"]
44#[derive(Clone, Copy, Debug, Default)]
45pub struct Sides {
46 height: Option<f32>,
47 spacing: Option<f32>,
48 kind: SidesKind,
49 wrap_mode: Option<crate::TextWrapMode>,
50}
51
52#[derive(Clone, Copy, Debug, Default)]
53enum SidesKind {
54 #[default]
55 Extend,
56 ShrinkLeft,
57 ShrinkRight,
58}
59
60impl Sides {
61 #[inline]
62 pub fn new() -> Self {
63 Default::default()
64 }
65
66 /// The minimum height of the sides.
67 ///
68 /// The content will be centered vertically within this height.
69 /// The default height is [`crate::Spacing::interact_size`]`.y`.
70 #[inline]
71 pub fn height(mut self, height: f32) -> Self {
72 self.height = Some(height);
73 self
74 }
75
76 /// The horizontal spacing between the left and right UIs.
77 ///
78 /// This is the minimum gap.
79 /// The default is [`crate::Spacing::item_spacing`]`.x`.
80 #[inline]
81 pub fn spacing(mut self, spacing: f32) -> Self {
82 self.spacing = Some(spacing);
83 self
84 }
85
86 /// Try to shrink widgets on the left side.
87 ///
88 /// Right widgets will be added first. The left [`Ui`]s max rect will be limited to the
89 /// remaining space.
90 #[inline]
91 pub fn shrink_left(mut self) -> Self {
92 self.kind = SidesKind::ShrinkLeft;
93 self
94 }
95
96 /// Try to shrink widgets on the right side.
97 ///
98 /// Left widgets will be added first. The right [`Ui`]s max rect will be limited to the
99 /// remaining space.
100 #[inline]
101 pub fn shrink_right(mut self) -> Self {
102 self.kind = SidesKind::ShrinkRight;
103 self
104 }
105
106 /// Extend the left and right sides to fill the available space.
107 ///
108 /// This is the default behavior.
109 /// The left widgets will be added first, followed by the right widgets.
110 #[inline]
111 pub fn extend(mut self) -> Self {
112 self.kind = SidesKind::Extend;
113 self
114 }
115
116 /// The text wrap mode for the shrinking side.
117 ///
118 /// Does nothing if [`Self::extend`] is used (the default).
119 #[inline]
120 pub fn wrap_mode(mut self, wrap_mode: crate::TextWrapMode) -> Self {
121 self.wrap_mode = Some(wrap_mode);
122 self
123 }
124
125 /// Truncate the text on the shrinking side.
126 ///
127 /// This is a shortcut for [`Self::wrap_mode`].
128 /// Does nothing if [`Self::extend`] is used (the default).
129 #[inline]
130 pub fn truncate(mut self) -> Self {
131 self.wrap_mode = Some(crate::TextWrapMode::Truncate);
132 self
133 }
134
135 /// Wrap the text on the shrinking side.
136 ///
137 /// This is a shortcut for [`Self::wrap_mode`].
138 /// Does nothing if [`Self::extend`] is used (the default).
139 #[inline]
140 pub fn wrap(mut self) -> Self {
141 self.wrap_mode = Some(crate::TextWrapMode::Wrap);
142 self
143 }
144
145 pub fn show<RetL, RetR>(
146 self,
147 ui: &mut Ui,
148 add_left: impl FnOnce(&mut Ui) -> RetL,
149 add_right: impl FnOnce(&mut Ui) -> RetR,
150 ) -> (RetL, RetR) {
151 let Self {
152 height,
153 spacing,
154 mut kind,
155 mut wrap_mode,
156 } = self;
157 let height = height.unwrap_or_else(|| ui.spacing().interact_size.y);
158 let spacing = spacing.unwrap_or_else(|| ui.spacing().item_spacing.x);
159
160 let mut top_rect = ui.available_rect_before_wrap();
161 top_rect.max.y = top_rect.min.y + height;
162
163 if ui.is_sizing_pass() {
164 kind = SidesKind::Extend;
165 wrap_mode = None;
166 }
167
168 match kind {
169 SidesKind::ShrinkLeft => {
170 let (right_rect, result_right) = Self::create_ui(
171 ui,
172 top_rect,
173 Layout::right_to_left(Align::Center),
174 add_right,
175 None,
176 );
177 let available_width = top_rect.width() - right_rect.width() - spacing;
178 let left_rect_constraint =
179 top_rect.with_max_x(top_rect.min.x + available_width.at_least(0.0));
180 let (left_rect, result_left) = Self::create_ui(
181 ui,
182 left_rect_constraint,
183 Layout::left_to_right(Align::Center),
184 add_left,
185 wrap_mode,
186 );
187
188 ui.advance_cursor_after_rect(left_rect | right_rect);
189 (result_left, result_right)
190 }
191 SidesKind::ShrinkRight => {
192 let (left_rect, result_left) = Self::create_ui(
193 ui,
194 top_rect,
195 Layout::left_to_right(Align::Center),
196 add_left,
197 None,
198 );
199 let right_rect_constraint = top_rect.with_min_x(left_rect.max.x + spacing);
200 let (right_rect, result_right) = Self::create_ui(
201 ui,
202 right_rect_constraint,
203 Layout::right_to_left(Align::Center),
204 add_right,
205 wrap_mode,
206 );
207
208 ui.advance_cursor_after_rect(left_rect | right_rect);
209 (result_left, result_right)
210 }
211 SidesKind::Extend => {
212 let (left_rect, result_left) = Self::create_ui(
213 ui,
214 top_rect,
215 Layout::left_to_right(Align::Center),
216 add_left,
217 None,
218 );
219 let right_max_rect = top_rect.with_min_x(left_rect.max.x);
220 let (right_rect, result_right) = Self::create_ui(
221 ui,
222 right_max_rect,
223 Layout::right_to_left(Align::Center),
224 add_right,
225 None,
226 );
227
228 let mut final_rect = left_rect | right_rect;
229 let min_width = left_rect.width() + spacing + right_rect.width();
230
231 if ui.is_sizing_pass() {
232 final_rect.max.x = left_rect.min.x + min_width;
233 } else {
234 final_rect.max.x = final_rect.max.x.max(left_rect.min.x + min_width);
235 }
236
237 ui.advance_cursor_after_rect(final_rect);
238 (result_left, result_right)
239 }
240 }
241 }
242
243 fn create_ui<Ret>(
244 ui: &mut Ui,
245 max_rect: emath::Rect,
246 layout: Layout,
247 add_content: impl FnOnce(&mut Ui) -> Ret,
248 wrap_mode: Option<crate::TextWrapMode>,
249 ) -> (emath::Rect, Ret) {
250 let mut child_ui = ui.new_child(UiBuilder::new().max_rect(max_rect).layout(layout));
251 if let Some(wrap_mode) = wrap_mode {
252 child_ui.style_mut().wrap_mode = Some(wrap_mode);
253 }
254 let result = add_content(&mut child_ui);
255 (child_ui.min_rect(), result)
256 }
257}