1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 13:20:05 -04:00

Add egui::Sides shrink_left / shrink_right (#7295)

This allows contents (on one of the sides) in egui::Sides to shrink. 

* related https://github.com/rerun-io/rerun/issues/10494
This commit is contained in:
Lucas Meurer
2025-07-03 14:31:35 +02:00
committed by GitHub
parent 77df407f50
commit 2b62c68598
22 changed files with 308 additions and 40 deletions

View File

@@ -1,4 +1,4 @@
use emath::Align;
use emath::{Align, NumExt as _};
use crate::{Layout, Ui, UiBuilder};
@@ -20,8 +20,13 @@ use crate::{Layout, Ui, UiBuilder};
///
/// If the parent is not wide enough to fit all widgets, the parent will be expanded to the right.
///
/// The left widgets are first added to the ui, left-to-right.
/// Then the right widgets are added, right-to-left.
/// The left widgets are added left-to-right.
/// The right widgets are added right-to-left.
///
/// Which side is first depends on the configuration:
/// - [`Sides::extend`] - left widgets are added first
/// - [`Sides::shrink_left`] - right widgets are added first
/// - [`Sides::shrink_right`] - left widgets are added first
///
/// ```
/// # egui::__run_test_ui(|ui| {
@@ -40,6 +45,16 @@ use crate::{Layout, Ui, UiBuilder};
pub struct Sides {
height: Option<f32>,
spacing: Option<f32>,
kind: SidesKind,
wrap_mode: Option<crate::TextWrapMode>,
}
#[derive(Clone, Copy, Debug, Default)]
enum SidesKind {
#[default]
Extend,
ShrinkLeft,
ShrinkRight,
}
impl Sides {
@@ -68,58 +83,175 @@ impl Sides {
self
}
/// Try to shrink widgets on the left side.
///
/// Right widgets will be added first. The left [`Ui`]s max rect will be limited to the
/// remaining space.
#[inline]
pub fn shrink_left(mut self) -> Self {
self.kind = SidesKind::ShrinkLeft;
self
}
/// Try to shrink widgets on the right side.
///
/// Left widgets will be added first. The right [`Ui`]s max rect will be limited to the
/// remaining space.
#[inline]
pub fn shrink_right(mut self) -> Self {
self.kind = SidesKind::ShrinkRight;
self
}
/// Extend the left and right sides to fill the available space.
///
/// This is the default behavior.
/// The left widgets will be added first, followed by the right widgets.
#[inline]
pub fn extend(mut self) -> Self {
self.kind = SidesKind::Extend;
self
}
/// The text wrap mode for the shrinking side.
///
/// Does nothing if [`Self::extend`] is used (the default).
#[inline]
pub fn wrap_mode(mut self, wrap_mode: crate::TextWrapMode) -> Self {
self.wrap_mode = Some(wrap_mode);
self
}
/// Truncate the text on the shrinking side.
///
/// This is a shortcut for [`Self::wrap_mode`].
/// Does nothing if [`Self::extend`] is used (the default).
#[inline]
pub fn truncate(mut self) -> Self {
self.wrap_mode = Some(crate::TextWrapMode::Truncate);
self
}
/// Wrap the text on the shrinking side.
///
/// This is a shortcut for [`Self::wrap_mode`].
/// Does nothing if [`Self::extend`] is used (the default).
#[inline]
pub fn wrap(mut self) -> Self {
self.wrap_mode = Some(crate::TextWrapMode::Wrap);
self
}
pub fn show<RetL, RetR>(
self,
ui: &mut Ui,
add_left: impl FnOnce(&mut Ui) -> RetL,
add_right: impl FnOnce(&mut Ui) -> RetR,
) -> (RetL, RetR) {
let Self { height, spacing } = self;
let Self {
height,
spacing,
mut kind,
mut wrap_mode,
} = self;
let height = height.unwrap_or_else(|| ui.spacing().interact_size.y);
let spacing = spacing.unwrap_or_else(|| ui.spacing().item_spacing.x);
let mut top_rect = ui.available_rect_before_wrap();
top_rect.max.y = top_rect.min.y + height;
let result_left;
let result_right;
let left_rect = {
let left_max_rect = top_rect;
let mut left_ui = ui.new_child(
UiBuilder::new()
.max_rect(left_max_rect)
.layout(Layout::left_to_right(Align::Center)),
);
result_left = add_left(&mut left_ui);
left_ui.min_rect()
};
let right_rect = {
let right_max_rect = top_rect.with_min_x(left_rect.max.x);
let mut right_ui = ui.new_child(
UiBuilder::new()
.max_rect(right_max_rect)
.layout(Layout::right_to_left(Align::Center)),
);
result_right = add_right(&mut right_ui);
right_ui.min_rect()
};
let mut final_rect = left_rect.union(right_rect);
let min_width = left_rect.width() + spacing + right_rect.width();
if ui.is_sizing_pass() {
// Make as small as possible:
final_rect.max.x = left_rect.min.x + min_width;
} else {
// If the rects overlap, make sure we expand the allocated rect so that the parent
// ui knows we overflowed, and resizes:
final_rect.max.x = final_rect.max.x.max(left_rect.min.x + min_width);
kind = SidesKind::Extend;
wrap_mode = None;
}
ui.advance_cursor_after_rect(final_rect);
match kind {
SidesKind::ShrinkLeft => {
let (right_rect, result_right) = Self::create_ui(
ui,
top_rect,
Layout::right_to_left(Align::Center),
add_right,
None,
);
let available_width = top_rect.width() - right_rect.width() - spacing;
let left_rect_constraint =
top_rect.with_max_x(top_rect.min.x + available_width.at_least(0.0));
let (left_rect, result_left) = Self::create_ui(
ui,
left_rect_constraint,
Layout::left_to_right(Align::Center),
add_left,
wrap_mode,
);
(result_left, result_right)
ui.advance_cursor_after_rect(left_rect.union(right_rect));
(result_left, result_right)
}
SidesKind::ShrinkRight => {
let (left_rect, result_left) = Self::create_ui(
ui,
top_rect,
Layout::left_to_right(Align::Center),
add_left,
None,
);
let right_rect_constraint = top_rect.with_min_x(left_rect.max.x + spacing);
let (right_rect, result_right) = Self::create_ui(
ui,
right_rect_constraint,
Layout::right_to_left(Align::Center),
add_right,
wrap_mode,
);
ui.advance_cursor_after_rect(left_rect.union(right_rect));
(result_left, result_right)
}
SidesKind::Extend => {
let (left_rect, result_left) = Self::create_ui(
ui,
top_rect,
Layout::left_to_right(Align::Center),
add_left,
None,
);
let right_max_rect = top_rect.with_min_x(left_rect.max.x);
let (right_rect, result_right) = Self::create_ui(
ui,
right_max_rect,
Layout::right_to_left(Align::Center),
add_right,
None,
);
let mut final_rect = left_rect.union(right_rect);
let min_width = left_rect.width() + spacing + right_rect.width();
if ui.is_sizing_pass() {
final_rect.max.x = left_rect.min.x + min_width;
} else {
final_rect.max.x = final_rect.max.x.max(left_rect.min.x + min_width);
}
ui.advance_cursor_after_rect(final_rect);
(result_left, result_right)
}
}
}
fn create_ui<Ret>(
ui: &mut Ui,
max_rect: emath::Rect,
layout: Layout,
add_content: impl FnOnce(&mut Ui) -> Ret,
wrap_mode: Option<crate::TextWrapMode>,
) -> (emath::Rect, Ret) {
let mut child_ui = ui.new_child(UiBuilder::new().max_rect(max_rect).layout(layout));
if let Some(wrap_mode) = wrap_mode {
child_ui.style_mut().wrap_mode = Some(wrap_mode);
}
let result = add_content(&mut child_ui);
(child_ui.min_rect(), result)
}
}