From 498e9beb9110c4d5958d02c1ee397539a1664187 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Tue, 14 May 2024 18:02:44 +0200 Subject: [PATCH 1/2] Add support for exact width to `PropertyContent` --- .../re_ui/src/list_item2/property_content.rs | 46 +++++++++++++++++-- crates/re_ui/src/list_item2/scope.rs | 24 +++++++++- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/crates/re_ui/src/list_item2/property_content.rs b/crates/re_ui/src/list_item2/property_content.rs index e91996eb3ff5..1a00082a3f3a 100644 --- a/crates/re_ui/src/list_item2/property_content.rs +++ b/crates/re_ui/src/list_item2/property_content.rs @@ -1,6 +1,6 @@ use egui::{text::TextWrapping, Align, Align2, NumExt as _, Ui}; -use super::{ContentContext, DesiredWidth, ListItemContent}; +use super::{ContentContext, DesiredWidth, LayoutInfoStack, ListItemContent}; use crate::{Icon, ReUi}; /// Closure to draw an icon left of the label. @@ -20,6 +20,8 @@ struct PropertyActionButton<'a> { pub struct PropertyContent<'a> { label: egui::WidgetText, min_desired_width: f32, + exact_width: bool, + icon_fn: Option>>, show_only_when_collapsed: bool, value_fn: Option>>, @@ -37,6 +39,7 @@ impl<'a> PropertyContent<'a> { Self { label: label.into(), min_desired_width: 200.0, + exact_width: false, icon_fn: None, show_only_when_collapsed: true, value_fn: None, @@ -54,6 +57,17 @@ impl<'a> PropertyContent<'a> { self } + /// Allocate the exact width required for the entire content. + /// + /// Note: this is done by tracking the maximum width in the current [`super::list_item_scope`] + /// during the previous frame, so this is effective on the second frame only. If the first frame + /// is actually rendered, this can lead to a flicker. + #[inline] + pub fn exact_width(mut self, exact_width: bool) -> Self { + self.exact_width = exact_width; + self + } + /// Provide an [`Icon`] to be displayed on the left of the label. #[inline] pub fn with_icon(self, icon: &'a Icon) -> Self { @@ -188,6 +202,7 @@ impl ListItemContent for PropertyContent<'_> { show_only_when_collapsed, value_fn, action_buttons, + exact_width: _, } = *self; // │ │ @@ -313,6 +328,11 @@ impl ListItemContent for PropertyContent<'_> { let mut child_ui = ui.child_ui(value_rect, egui::Layout::left_to_right(egui::Align::Center)); value_fn(re_ui, &mut child_ui, visuals); + + context.layout_info.register_property_content_max_width( + child_ui.ctx(), + child_ui.min_rect().right() - context.layout_info.left_x, + ); } } @@ -336,7 +356,27 @@ impl ListItemContent for PropertyContent<'_> { } } - fn desired_width(&self, _re_ui: &ReUi, _ui: &Ui) -> DesiredWidth { - DesiredWidth::AtLeast(self.min_desired_width) + fn desired_width(&self, _re_ui: &ReUi, ui: &Ui) -> DesiredWidth { + let layout_info = LayoutInfoStack::top(ui.ctx()); + if self.exact_width { + if let Some(max_width) = layout_info.property_content_max_width { + let mut desired_width = max_width + layout_info.left_x - ui.max_rect().left(); + + // TODO(ab): ideally there wouldn't be as much code duplication with `Self::ui` + let action_button_dimension = + ReUi::small_icon_size().x + 2.0 * ui.spacing().button_padding.x; + let reserve_action_button_space = + self.action_buttons.is_some() || layout_info.reserve_action_button_space; + if reserve_action_button_space { + desired_width += action_button_dimension + ReUi::text_to_icon_padding(); + } + + DesiredWidth::Exact(desired_width.ceil()) + } else { + DesiredWidth::AtLeast(self.min_desired_width) + } + } else { + DesiredWidth::AtLeast(self.min_desired_width) + } } } diff --git a/crates/re_ui/src/list_item2/scope.rs b/crates/re_ui/src/list_item2/scope.rs index b3aefdd4abc4..1ae92b2f3c19 100644 --- a/crates/re_ui/src/list_item2/scope.rs +++ b/crates/re_ui/src/list_item2/scope.rs @@ -51,6 +51,11 @@ struct LayoutStatistics { /// /// The width is calculated from [`LayoutInfo::left_x`] to the right edge of the item. max_item_width: Option, + + /// `PropertyContent` only — max content width in the current scope. + /// + /// This value is measured from `left_x`. + property_content_max_width: Option, } impl LayoutStatistics { @@ -116,6 +121,11 @@ pub struct LayoutInfo { /// Scope id, used to retrieve the corresponding [`LayoutStatistics`]. scope_id: egui::Id, + + /// `PropertyContent` only — last frame's max content width, to be used in `desired_width()` + /// + /// This value is measured from `left_x`. + pub(crate) property_content_max_width: Option, } impl Default for LayoutInfo { @@ -125,6 +135,7 @@ impl Default for LayoutInfo { left_column_width: None, reserve_action_button_space: true, scope_id: egui::Id::NULL, + property_content_max_width: None, } } } @@ -158,6 +169,16 @@ impl LayoutInfo { stats.max_item_width = stats.max_item_width.map(|v| v.max(width)).or(Some(width)); }); } + + /// `PropertyContent` only — register max content width in the current scope + pub(super) fn register_property_content_max_width(&self, ctx: &egui::Context, width: f32) { + LayoutStatistics::update(ctx, self.scope_id, |stats| { + stats.property_content_max_width = stats + .property_content_max_width + .map(|v| v.max(width)) + .or(Some(width)); + }); + } } /// Stack of [`LayoutInfo`]s. @@ -255,10 +276,11 @@ pub fn list_item_scope( left_column_width, reserve_action_button_space: layout_stats.is_action_button_used, scope_id, + property_content_max_width: layout_stats.property_content_max_width, }; // push, run, pop - LayoutInfoStack::push(ui.ctx(), state); + LayoutInfoStack::push(ui.ctx(), state.clone()); let result = ui .scope(|ui| { ui.spacing_mut().item_spacing.y = 0.0; From c54dab97634b2f411dc369af3e58f0dc060eb6e8 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Wed, 22 May 2024 07:58:26 +0200 Subject: [PATCH 2/2] Remove unnecessary clone --- crates/re_ui/src/list_item2/scope.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/re_ui/src/list_item2/scope.rs b/crates/re_ui/src/list_item2/scope.rs index 1ae92b2f3c19..1754d0ff98cf 100644 --- a/crates/re_ui/src/list_item2/scope.rs +++ b/crates/re_ui/src/list_item2/scope.rs @@ -280,7 +280,7 @@ pub fn list_item_scope( }; // push, run, pop - LayoutInfoStack::push(ui.ctx(), state.clone()); + LayoutInfoStack::push(ui.ctx(), state); let result = ui .scope(|ui| { ui.spacing_mut().item_spacing.y = 0.0;