From 805811f2a571ef534df8dbb0af19f170d5c15b35 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 5 Jun 2024 16:42:56 +0200 Subject: [PATCH] Use `UiStack` to figure out the default wrap-mode for ListItem labels --- crates/re_time_panel/src/lib.rs | 6 +- .../examples/re_ui_example/right_panel.rs | 7 +- crates/re_ui/src/lib.rs | 4 +- crates/re_ui/src/list_item/label_content.rs | 75 +++++++++++++------ 4 files changed, 60 insertions(+), 32 deletions(-) diff --git a/crates/re_time_panel/src/lib.rs b/crates/re_time_panel/src/lib.rs index 36da28e88205..3cfbad0153b0 100644 --- a/crates/re_time_panel/src/lib.rs +++ b/crates/re_time_panel/src/lib.rs @@ -635,7 +635,7 @@ impl TimePanel { ctx, &InstancePath::from(tree.path.clone()), )) - .exact_width(true), + .truncate(false), |_, ui| { self.show_children( ctx, @@ -780,7 +780,7 @@ impl TimePanel { } else { &re_ui::icons::COMPONENT_TEMPORAL }) - .exact_width(true), + .truncate(false), ); context_menu_ui_for_item( @@ -826,7 +826,7 @@ impl TimePanel { ) }, )) - .exact_width(true) + .truncate(false) .with_icon(if is_static { &re_ui::icons::COMPONENT_STATIC } else { diff --git a/crates/re_ui/examples/re_ui_example/right_panel.rs b/crates/re_ui/examples/re_ui_example/right_panel.rs index e9bf12da0dc8..84415eba474e 100644 --- a/crates/re_ui/examples/re_ui_example/right_panel.rs +++ b/crates/re_ui/examples/re_ui_example/right_panel.rs @@ -94,16 +94,13 @@ impl RightPanel { format!("Some item {i}") }; - // Note: we use `exact_width(true)` here to force the item to allocate + // Note: we use `truncate(false)` here to force the item to allocate // as much as needed for the label, which in turn will trigger the // scroll area. if re_ui .list_item() .selected(Some(i) == self.selected_list_item) - .show_flat( - ui, - list_item::LabelContent::new(&label).exact_width(true), - ) + .show_flat(ui, list_item::LabelContent::new(&label).truncate(false)) .clicked() { self.selected_list_item = Some(i); diff --git a/crates/re_ui/src/lib.rs b/crates/re_ui/src/lib.rs index 1135814f3509..e8b6b59e8c6f 100644 --- a/crates/re_ui/src/lib.rs +++ b/crates/re_ui/src/lib.rs @@ -1327,9 +1327,9 @@ pub trait UiExt { if node.has_visible_frame() || node.is_panel_ui() || node.is_root_ui() - || node.kind == Some(egui::UiKind::TableCell) + || node.kind() == Some(egui::UiKind::TableCell) { - return (node.max_rect + node.frame.inner_margin).x_range(); + return (node.max_rect + node.frame().inner_margin).x_range(); } } diff --git a/crates/re_ui/src/list_item/label_content.rs b/crates/re_ui/src/list_item/label_content.rs index 0dc6e4df8ec5..afbe2a4e9f01 100644 --- a/crates/re_ui/src/list_item/label_content.rs +++ b/crates/re_ui/src/list_item/label_content.rs @@ -18,23 +18,26 @@ pub struct LabelContent<'a> { buttons_fn: Option egui::Response + 'a>>, always_show_buttons: bool, + text_wrap_mode: Option, min_desired_width: Option, - exact_width: bool, } impl<'a> LabelContent<'a> { pub fn new(text: impl Into) -> Self { Self { text: text.into(), + subdued: false, weak: false, italics: false, + label_style: Default::default(), icon_fn: None, buttons_fn: None, always_show_buttons: false, + + text_wrap_mode: None, min_desired_width: None, - exact_width: false, } } @@ -79,16 +82,13 @@ impl<'a> LabelContent<'a> { self } - /// Allocate the exact width required for the label. - /// - /// By default, [`LabelContent`] uses the available width. By setting `exact_width` to true, - /// the exact width required by the label (and the icon if any) is allocated instead. See - /// [`super::DesiredWidth::Exact`]. - /// - /// Note that if [`Self::min_desired_width`] is set, it is used as a minimum value. - #[inline] - pub fn exact_width(mut self, exact_width: bool) -> Self { - self.exact_width = exact_width; + /// Should we truncate text if it is too long? + pub fn truncate(mut self, truncate: bool) -> Self { + self.text_wrap_mode = Some(if truncate { + egui::TextWrapMode::Truncate + } else { + egui::TextWrapMode::Extend + }); self } @@ -148,10 +148,39 @@ impl<'a> LabelContent<'a> { self.always_show_buttons = always_show_buttons; self } + + fn get_text_wrap_mode(&self, ui: &egui::Ui) -> egui::TextWrapMode { + if let Some(text_wrap_mode) = self.text_wrap_mode { + text_wrap_mode + } else { + let mut is_in_side_panel = false; + for frame in ui.stack().iter() { + if let Some(kind) = frame.kind() { + if kind.is_area() { + // Our popups (tooltips etc) aren't resizable, so show all of the text + return egui::TextWrapMode::Extend; + } + if matches!(kind, egui::UiKind::LeftPanel | egui::UiKind::RightPanel) { + is_in_side_panel = true; + } + } + } + + if is_in_side_panel { + // Our side-panels are resizable, so truncate the text if we don't fit. + egui::TextWrapMode::Truncate + } else { + // Safe fallback + egui::TextWrapMode::Extend + } + } + } } impl ListItemContent for LabelContent<'_> { fn ui(self: Box, re_ui: &ReUi, ui: &mut Ui, context: &ContentContext<'_>) { + let text_wrap_mode = self.get_text_wrap_mode(ui); + let Self { mut text, subdued, @@ -161,8 +190,8 @@ impl ListItemContent for LabelContent<'_> { icon_fn, buttons_fn, always_show_buttons, + text_wrap_mode: _, min_desired_width: _, - exact_width: _, } = *self; let icon_rect = egui::Rect::from_center_size( @@ -235,7 +264,7 @@ impl ListItemContent for LabelContent<'_> { let mut layout_job = text.into_layout_job(ui.style(), egui::FontSelection::Default, Align::LEFT); - layout_job.wrap = TextWrapping::truncate_at_width(text_rect.width()); + layout_job.wrap = TextWrapping::from_wrap_mode_and_width(text_wrap_mode, text_rect.width()); let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); @@ -256,6 +285,8 @@ impl ListItemContent for LabelContent<'_> { } fn desired_width(&self, _re_ui: &ReUi, ui: &Ui) -> DesiredWidth { + let text_wrap_mode = self.get_text_wrap_mode(ui); + let measured_width = { //TODO(ab): ideally there wouldn't be as much code duplication with `Self::ui` let mut text = self.text.clone(); @@ -279,16 +310,16 @@ impl ListItemContent for LabelContent<'_> { desired_width.ceil() }; - // TODO(emilk): use the egui `UiStack` to check if we're somewhere resizable, - // and only then turn on text truncation. - // For instance: in a tooltip we must not truncate the text, because - // the user can't resize the tooltip to read the full text. - // But if we're in a panel, the user can resize the panel to read the full text. - let min_desired_width = self.min_desired_width.unwrap_or(measured_width); - - if self.exact_width { + if text_wrap_mode == egui::TextWrapMode::Extend { + let min_desired_width = self.min_desired_width.unwrap_or(0.0); DesiredWidth::Exact(measured_width.at_least(min_desired_width)) } else { + // If the user set an explicit min-width, use it. + // Otherwise, show at least `default_min_width`, unless the text is even short. + let default_min_width = 64.0; + let min_desired_width = self + .min_desired_width + .unwrap_or_else(|| measured_width.min(default_min_width)); DesiredWidth::AtLeast(min_desired_width) } }