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:
parent
77df407f50
commit
2b62c68598
|
|
@ -1,4 +1,4 @@
|
||||||
use emath::Align;
|
use emath::{Align, NumExt as _};
|
||||||
|
|
||||||
use crate::{Layout, Ui, UiBuilder};
|
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.
|
/// 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.
|
/// The left widgets are added left-to-right.
|
||||||
/// Then the right widgets are added, right-to-left.
|
/// 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| {
|
/// # egui::__run_test_ui(|ui| {
|
||||||
|
|
@ -40,6 +45,16 @@ use crate::{Layout, Ui, UiBuilder};
|
||||||
pub struct Sides {
|
pub struct Sides {
|
||||||
height: Option<f32>,
|
height: Option<f32>,
|
||||||
spacing: 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 {
|
impl Sides {
|
||||||
|
|
@ -68,58 +83,175 @@ impl Sides {
|
||||||
self
|
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>(
|
pub fn show<RetL, RetR>(
|
||||||
self,
|
self,
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
add_left: impl FnOnce(&mut Ui) -> RetL,
|
add_left: impl FnOnce(&mut Ui) -> RetL,
|
||||||
add_right: impl FnOnce(&mut Ui) -> RetR,
|
add_right: impl FnOnce(&mut Ui) -> RetR,
|
||||||
) -> (RetL, 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 height = height.unwrap_or_else(|| ui.spacing().interact_size.y);
|
||||||
let spacing = spacing.unwrap_or_else(|| ui.spacing().item_spacing.x);
|
let spacing = spacing.unwrap_or_else(|| ui.spacing().item_spacing.x);
|
||||||
|
|
||||||
let mut top_rect = ui.available_rect_before_wrap();
|
let mut top_rect = ui.available_rect_before_wrap();
|
||||||
top_rect.max.y = top_rect.min.y + height;
|
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() {
|
if ui.is_sizing_pass() {
|
||||||
// Make as small as possible:
|
kind = SidesKind::Extend;
|
||||||
final_rect.max.x = left_rect.min.x + min_width;
|
wrap_mode = None;
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b7ceaa95512c67dcbf1c8ba5a8f33bf4833c2e863d09903fb71b5aa2822cc086
|
||||||
|
size 7889
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442
|
||||||
|
size 8552
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f
|
||||||
|
size 1647
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f
|
||||||
|
size 1276
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:88e1557dffa7295e7e7e37ed175fcec40aab939f9b67137a1ce33811e8ae4722
|
||||||
|
size 7148
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442
|
||||||
|
size 8552
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f
|
||||||
|
size 1647
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f
|
||||||
|
size 1276
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:508209ca303751ef323301b25bb3878410742ea79339b75363d2681b98d2712b
|
||||||
|
size 7068
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442
|
||||||
|
size 8552
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f
|
||||||
|
size 1647
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f
|
||||||
|
size 1276
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a0c9e39c18fc5bb1fc02a86dbf02e3ffca5537dbe8986d5c5b50cb4984c97466
|
||||||
|
size 9085
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442
|
||||||
|
size 8552
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f
|
||||||
|
size 1647
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f
|
||||||
|
size 1276
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a6e9ba0acb573853ef5b3dedb1156d99cdf80338ccb160093960e8aaa41bd5df
|
||||||
|
size 9048
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442
|
||||||
|
size 8552
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f
|
||||||
|
size 1647
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f
|
||||||
|
size 1276
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
use egui::{TextWrapMode, Vec2, containers::Sides};
|
||||||
|
use egui_kittest::{Harness, SnapshotResults};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sides_container_tests() {
|
||||||
|
let mut results = SnapshotResults::new();
|
||||||
|
|
||||||
|
test_variants("default", |sides| sides, &mut results);
|
||||||
|
|
||||||
|
test_variants(
|
||||||
|
"shrink_left",
|
||||||
|
|sides| sides.shrink_left().truncate(),
|
||||||
|
&mut results,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_variants(
|
||||||
|
"shrink_right",
|
||||||
|
|sides| sides.shrink_right().truncate(),
|
||||||
|
&mut results,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_variants(
|
||||||
|
"wrap_left",
|
||||||
|
|sides| sides.shrink_left().wrap_mode(TextWrapMode::Wrap),
|
||||||
|
&mut results,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_variants(
|
||||||
|
"wrap_right",
|
||||||
|
|sides| sides.shrink_right().wrap_mode(TextWrapMode::Wrap),
|
||||||
|
&mut results,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_variants(
|
||||||
|
name: &str,
|
||||||
|
mut create_sides: impl FnMut(Sides) -> Sides,
|
||||||
|
results: &mut SnapshotResults,
|
||||||
|
) {
|
||||||
|
for (variant_name, left_text, right_text, fit_contents) in [
|
||||||
|
("short", "Left", "Right", false),
|
||||||
|
(
|
||||||
|
"long",
|
||||||
|
"Very long left content that should not fit.",
|
||||||
|
"Very long right text that should also not fit.",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
("short_fit_contents", "Left", "Right", true),
|
||||||
|
(
|
||||||
|
"long_fit_contents",
|
||||||
|
"Very long left content that should not fit.",
|
||||||
|
"Very long right text that should also not fit.",
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let mut harness = Harness::builder()
|
||||||
|
.with_size(Vec2::new(400.0, 50.0))
|
||||||
|
.build_ui(|ui| {
|
||||||
|
create_sides(Sides::new()).show(
|
||||||
|
ui,
|
||||||
|
|left| {
|
||||||
|
left.label(left_text);
|
||||||
|
},
|
||||||
|
|right| {
|
||||||
|
right.label(right_text);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if fit_contents {
|
||||||
|
harness.fit_contents();
|
||||||
|
}
|
||||||
|
|
||||||
|
results.add(harness.try_snapshot(&format!("sides/{name}_{variant_name}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue