From e08c88a41bce56fb933982c9eca1ddfc08a64675 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 20 May 2024 18:34:12 +0200 Subject: [PATCH] Moved `[data_]label_for_ui_layout` and `table_for_ui_layout` to `UiLayout` methods --- Cargo.lock | 1 + crates/re_data_ui/src/annotation_context.rs | 18 ++-- crates/re_data_ui/src/component.rs | 5 +- .../re_data_ui/src/component_ui_registry.rs | 10 +- crates/re_data_ui/src/data.rs | 33 ++++--- crates/re_data_ui/src/image.rs | 6 +- crates/re_data_ui/src/instance_path.rs | 11 +-- crates/re_data_ui/src/lib.rs | 95 ------------------- crates/re_data_ui/src/pinhole.rs | 12 +-- crates/re_data_ui/src/rotation3d.rs | 6 +- crates/re_data_ui/src/transform3d.rs | 8 +- crates/re_viewer_context/Cargo.toml | 1 + .../src/component_ui_registry.rs | 88 +++++++++++++++++ 13 files changed, 144 insertions(+), 150 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 957b7f4116a50..638dc08dbb9d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5058,6 +5058,7 @@ dependencies = [ "bytemuck", "egui", "egui-wgpu", + "egui_extras", "egui_tiles", "glam", "half 2.3.1", diff --git a/crates/re_data_ui/src/annotation_context.rs b/crates/re_data_ui/src/annotation_context.rs index 07c126d4e074f..88776eaa12116 100644 --- a/crates/re_data_ui/src/annotation_context.rs +++ b/crates/re_data_ui/src/annotation_context.rs @@ -7,7 +7,7 @@ use re_types::datatypes::{ }; use re_viewer_context::{auto_color, UiLayout, ViewerContext}; -use super::{data_label_for_ui_layout, label_for_ui_layout, table_for_ui_layout, DataUi}; +use super::DataUi; impl crate::EntityDataUi for re_types::components::ClassId { fn entity_data_ui( @@ -32,7 +32,7 @@ impl crate::EntityDataUi for re_types::components::ClassId { text.push(' '); text.push_str(label.as_str()); } - label_for_ui_layout(ui, ui_layout, text); + ui_layout.label(ui, text); }); let id = self.0; @@ -54,7 +54,7 @@ impl crate::EntityDataUi for re_types::components::ClassId { } } } else { - label_for_ui_layout(ui, ui_layout, format!("{}", self.0)); + ui_layout.label(ui, format!("{}", self.0)); } } } @@ -79,10 +79,10 @@ impl crate::EntityDataUi for re_types::components::KeypointId { text.push_str(label.as_str()); } - data_label_for_ui_layout(ui, ui_layout, text); + ui_layout.data_label(ui, text); }); } else { - data_label_for_ui_layout(ui, ui_layout, format!("{}", self.0)); + ui_layout.data_label(ui, format!("{}", self.0)); } } } @@ -124,7 +124,7 @@ impl DataUi for AnnotationContext { } else { format!("{} classes", self.0.len()) }; - label_for_ui_layout(ui, ui_layout, text); + ui_layout.label(ui, text); } UiLayout::SelectionPanelLimitHeight | UiLayout::SelectionPanelFull => { ui.vertical(|ui| { @@ -204,7 +204,8 @@ fn class_description_ui( ui.push_id(format!("keypoints_connections_{}", id.0), |ui| { use egui_extras::Column; - let table = table_for_ui_layout(ui_layout, ui) + let table = ui_layout + .table(ui) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::auto().clip(true).at_least(40.0)) .column(Column::auto().clip(true).at_least(40.0)); @@ -272,7 +273,8 @@ fn annotation_info_table_ui( use egui_extras::Column; - let table = table_for_ui_layout(ui_layout, ui) + let table = ui_layout + .table(ui) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::auto()) // id .column(Column::auto().clip(true).at_least(40.0)) // label diff --git a/crates/re_data_ui/src/component.rs b/crates/re_data_ui/src/component.rs index 5ffd16debc6af..170fc1c95a44c 100644 --- a/crates/re_data_ui/src/component.rs +++ b/crates/re_data_ui/src/component.rs @@ -8,7 +8,7 @@ use re_types::ComponentName; use re_ui::SyntaxHighlighting as _; use re_viewer_context::{UiLayout, ViewerContext}; -use super::{table_for_ui_layout, DataUi}; +use super::DataUi; use crate::item_ui; /// All the values of a specific [`re_log_types::ComponentPath`]. @@ -139,7 +139,8 @@ impl DataUi for EntityLatestAtResults { } else if one_line { ui.label(format!("{} values", re_format::format_uint(num_instances))); } else { - table_for_ui_layout(ui_layout, ui) + ui_layout + .table(ui) .resizable(false) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(egui_extras::Column::auto()) diff --git a/crates/re_data_ui/src/component_ui_registry.rs b/crates/re_data_ui/src/component_ui_registry.rs index 052981f3de5a3..e2d386d8cea4e 100644 --- a/crates/re_data_ui/src/component_ui_registry.rs +++ b/crates/re_data_ui/src/component_ui_registry.rs @@ -6,7 +6,7 @@ use re_viewer_context::{ComponentUiRegistry, UiLayout, ViewerContext}; use crate::editors::register_editors; -use super::{data_label_for_ui_layout, EntityDataUi}; +use super::EntityDataUi; pub fn create_component_ui_registry() -> ComponentUiRegistry { re_tracing::profile_function!(); @@ -90,14 +90,14 @@ fn arrow_ui(ui: &mut egui::Ui, ui_layout: UiLayout, array: &dyn arrow2::array::A if let Some(utf8) = array.as_any().downcast_ref::>() { if utf8.len() == 1 { let string = utf8.value(0); - data_label_for_ui_layout(ui, ui_layout, string); + ui_layout.data_label(ui, string); return; } } if let Some(utf8) = array.as_any().downcast_ref::>() { if utf8.len() == 1 { let string = utf8.value(0); - data_label_for_ui_layout(ui, ui_layout, string); + ui_layout.data_label(ui, string); return; } } @@ -108,7 +108,7 @@ fn arrow_ui(ui: &mut egui::Ui, ui_layout: UiLayout, array: &dyn arrow2::array::A let mut string = String::new(); let display = arrow2::array::get_display(array, "null"); if display(&mut string, 0).is_ok() { - data_label_for_ui_layout(ui, ui_layout, &string); + ui_layout.data_label(ui, &string); return; } } @@ -121,7 +121,7 @@ fn arrow_ui(ui: &mut egui::Ui, ui_layout: UiLayout, array: &dyn arrow2::array::A if data_type_formatted.len() < 20 { // e.g. "4.2 KiB of Float32" - data_label_for_ui_layout(ui, ui_layout, &format!("{bytes} of {data_type_formatted}")); + ui_layout.data_label(ui, &format!("{bytes} of {data_type_formatted}")); } else { // Huge datatype, probably a union horror show ui.label(format!("{bytes} of data")); diff --git a/crates/re_data_ui/src/data.rs b/crates/re_data_ui/src/data.rs index 04ccc68f13e17..1ce0d738d19c3 100644 --- a/crates/re_data_ui/src/data.rs +++ b/crates/re_data_ui/src/data.rs @@ -6,7 +6,7 @@ use re_format::format_f32; use re_types::components::{Color, LineStrip2D, LineStrip3D, Range1D, Range2D, ViewCoordinates}; use re_viewer_context::{UiLayout, ViewerContext}; -use super::{data_label_for_ui_layout, label_for_ui_layout, table_for_ui_layout, DataUi}; +use super::DataUi; /// Default number of ui points to show a number. const DEFAULT_NUMBER_WIDTH: f32 = 52.0; @@ -64,13 +64,14 @@ impl DataUi for ViewCoordinates { ) { match ui_layout { UiLayout::List => { - label_for_ui_layout(ui, ui_layout, self.describe_short()) + ui_layout + .label(ui, self.describe_short()) .on_hover_text(self.describe()); } UiLayout::SelectionPanelFull | UiLayout::SelectionPanelLimitHeight | UiLayout::Tooltip => { - label_for_ui_layout(ui, ui_layout, self.describe()); + ui_layout.label(ui, self.describe()); } } } @@ -113,7 +114,7 @@ impl DataUi for re_types::datatypes::Vec2D { _query: &re_data_store::LatestAtQuery, _db: &re_entity_db::EntityDb, ) { - data_label_for_ui_layout(ui, ui_layout, self.to_string()); + ui_layout.data_label(ui, self.to_string()); } } @@ -126,7 +127,7 @@ impl DataUi for re_types::datatypes::Vec3D { _query: &re_data_store::LatestAtQuery, _db: &re_entity_db::EntityDb, ) { - data_label_for_ui_layout(ui, ui_layout, self.to_string()); + ui_layout.data_label(ui, self.to_string()); } } @@ -139,7 +140,7 @@ impl DataUi for re_types::datatypes::Vec4D { _query: &re_data_store::LatestAtQuery, _db: &re_entity_db::EntityDb, ) { - data_label_for_ui_layout(ui, ui_layout, self.to_string()); + ui_layout.data_label(ui, self.to_string()); } } @@ -152,7 +153,7 @@ impl DataUi for re_types::datatypes::UVec2D { _query: &re_data_store::LatestAtQuery, _db: &re_entity_db::EntityDb, ) { - data_label_for_ui_layout(ui, ui_layout, self.to_string()); + ui_layout.data_label(ui, self.to_string()); } } @@ -165,7 +166,7 @@ impl DataUi for re_types::datatypes::UVec3D { _query: &re_data_store::LatestAtQuery, _db: &re_entity_db::EntityDb, ) { - data_label_for_ui_layout(ui, ui_layout, self.to_string()); + ui_layout.data_label(ui, self.to_string()); } } @@ -178,7 +179,7 @@ impl DataUi for re_types::datatypes::UVec4D { _query: &re_data_store::LatestAtQuery, _db: &re_entity_db::EntityDb, ) { - data_label_for_ui_layout(ui, ui_layout, self.to_string()); + ui_layout.data_label(ui, self.to_string()); } } @@ -191,7 +192,7 @@ impl DataUi for Range1D { _query: &LatestAtQuery, _db: &EntityDb, ) { - data_label_for_ui_layout(ui, ui_layout, self.to_string()); + ui_layout.data_label(ui, self.to_string()); } } @@ -204,7 +205,7 @@ impl DataUi for Range2D { _query: &LatestAtQuery, _db: &EntityDb, ) { - data_label_for_ui_layout(ui, ui_layout, self.to_string()); + ui_layout.data_label(ui, self.to_string()); } } @@ -219,11 +220,12 @@ impl DataUi for LineStrip2D { ) { match ui_layout { UiLayout::List | UiLayout::Tooltip => { - label_for_ui_layout(ui, ui_layout, format!("{} positions", self.0.len())); + ui_layout.label(ui, format!("{} positions", self.0.len())); } UiLayout::SelectionPanelLimitHeight | UiLayout::SelectionPanelFull => { use egui_extras::Column; - table_for_ui_layout(ui_layout, ui) + ui_layout + .table(ui) .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .columns(Column::initial(DEFAULT_NUMBER_WIDTH).clip(true), 2) @@ -266,11 +268,12 @@ impl DataUi for LineStrip3D { ) { match ui_layout { UiLayout::List | UiLayout::Tooltip => { - label_for_ui_layout(ui, ui_layout, format!("{} positions", self.0.len())); + ui_layout.label(ui, format!("{} positions", self.0.len())); } UiLayout::SelectionPanelFull | UiLayout::SelectionPanelLimitHeight => { use egui_extras::Column; - table_for_ui_layout(ui_layout, ui) + ui_layout + .table(ui) .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .columns(Column::initial(DEFAULT_NUMBER_WIDTH).clip(true), 3) diff --git a/crates/re_data_ui/src/image.rs b/crates/re_data_ui/src/image.rs index 006898e88f7aa..775210403e20d 100644 --- a/crates/re_data_ui/src/image.rs +++ b/crates/re_data_ui/src/image.rs @@ -12,7 +12,7 @@ use re_viewer_context::{ ViewerContext, }; -use crate::{image_meaning_for_entity, label_for_ui_layout}; +use crate::image_meaning_for_entity; use super::EntityDataUi; @@ -85,7 +85,7 @@ impl EntityDataUi for re_types::components::TensorData { ); } Err(err) => { - label_for_ui_layout(ui, ui_layout, ctx.re_ui.error_text(err.to_string())); + ui_layout.label(ui, ctx.re_ui.error_text(err.to_string())); } } } @@ -183,7 +183,7 @@ pub fn tensor_ui( original_tensor.dtype(), format_tensor_shape_single_line(&shape) ); - label_for_ui_layout(ui, ui_layout, text).on_hover_ui(|ui| { + ui_layout.label(ui, text).on_hover_ui(|ui| { tensor_summary_ui( ctx.re_ui, ui, diff --git a/crates/re_data_ui/src/instance_path.rs b/crates/re_data_ui/src/instance_path.rs index 640be0e4391c5..adf1c241f46d0 100644 --- a/crates/re_data_ui/src/instance_path.rs +++ b/crates/re_data_ui/src/instance_path.rs @@ -2,7 +2,7 @@ use re_entity_db::InstancePath; use re_log_types::ComponentPath; use re_viewer_context::{HoverHighlight, Item, UiLayout, ViewerContext}; -use super::{label_for_ui_layout, DataUi}; +use super::DataUi; impl DataUi for InstancePath { fn data_ui( @@ -24,18 +24,16 @@ impl DataUi for InstancePath { else { if ctx.recording().is_known_entity(entity_path) { // This is fine - e.g. we're looking at `/world` and the user has only logged to `/world/car`. - label_for_ui_layout( + ui_layout.label( ui, - ui_layout, format!( "No components logged on timeline {:?}", query.timeline().name() ), ); } else { - label_for_ui_layout( + ui_layout.label( ui, - ui_layout, ctx.re_ui .error_text(format!("Unknown entity: {entity_path:?}")), ); @@ -50,9 +48,8 @@ impl DataUi for InstancePath { .count(); if ui_layout == UiLayout::List { - label_for_ui_layout( + ui_layout.label( ui, - ui_layout, format!( "{} component{} (including {} indicator component{})", components.len(), diff --git a/crates/re_data_ui/src/lib.rs b/crates/re_data_ui/src/lib.rs index 7e628df3c4e2c..58bd1b98502f2 100644 --- a/crates/re_data_ui/src/lib.rs +++ b/crates/re_data_ui/src/lib.rs @@ -177,98 +177,3 @@ pub fn annotations( annotation_map.load(ctx, query, std::iter::once(entity_path)); annotation_map.find(entity_path) } - -// --------------------------------------------------------------------------- - -/// Build an egui table and configure it for the given UI context. -/// -/// Note that the caller is responsible for strictly limiting the number of displayed rows for -/// [`UiLayout::List`] and [`UiLayout::Tooltip`], as the table will not scroll. -pub fn table_for_ui_layout( - ui_layout: UiLayout, - ui: &mut egui::Ui, -) -> egui_extras::TableBuilder<'_> { - let table = egui_extras::TableBuilder::new(ui); - match ui_layout { - UiLayout::List | UiLayout::Tooltip => { - // Be as small as possible in the hover tooltips. No scrolling related configuration, as - // the content itself must be limited (scrolling is not possible in tooltips). - table.auto_shrink([true, true]) - } - UiLayout::SelectionPanelLimitHeight => { - // Don't take too much vertical space to leave room for other selected items. - table - .auto_shrink([false, true]) - .vscroll(true) - .max_scroll_height(100.0) - } - UiLayout::SelectionPanelFull => { - // We're alone in the selection panel. Let the outer ScrollArea do the work. - table.auto_shrink([false, true]).vscroll(false) - } - } -} - -/// Show a label while respecting the given UI layout. -/// -/// Important: for label only, data should use [`crate::data_label_for_ui_layout`] instead. -// TODO(#6315): must be merged with `data_label_for_ui_layout` and have an improved API -pub fn label_for_ui_layout( - ui: &mut egui::Ui, - ui_layout: UiLayout, - text: impl Into, -) -> egui::Response { - let mut label = egui::Label::new(text); - - match ui_layout { - UiLayout::List => label = label.truncate(true), - UiLayout::Tooltip | UiLayout::SelectionPanelLimitHeight | UiLayout::SelectionPanelFull => { - label = label.wrap(true); - } - } - - ui.add(label) -} - -/// Show data while respecting the given UI layout. -/// -/// Import: for data only, labels should use [`crate::label_for_ui_layout`] instead. -// TODO(#6315): must be merged with `label_for_ui_layout` and have an improved API -pub fn data_label_for_ui_layout(ui: &mut egui::Ui, ui_layout: UiLayout, string: impl AsRef) { - let string = string.as_ref(); - let font_id = egui::TextStyle::Monospace.resolve(ui.style()); - let color = ui.visuals().text_color(); - let wrap_width = ui.available_width(); - let mut layout_job = - egui::text::LayoutJob::simple(string.to_owned(), font_id, color, wrap_width); - - let mut needs_scroll_area = false; - - match ui_layout { - UiLayout::List => { - // Elide - layout_job.wrap.max_rows = 1; - layout_job.wrap.break_anywhere = true; - } - UiLayout::Tooltip => { - layout_job.wrap.max_rows = 3; - } - UiLayout::SelectionPanelLimitHeight => { - let num_newlines = string.chars().filter(|&c| c == '\n').count(); - needs_scroll_area = 10 < num_newlines || 300 < string.len(); - } - UiLayout::SelectionPanelFull => { - needs_scroll_area = false; - } - } - - let galley = ui.fonts(|f| f.layout_job(layout_job)); // We control the text layout; not the label - - if needs_scroll_area { - egui::ScrollArea::vertical().show(ui, |ui| { - ui.label(galley); - }); - } else { - ui.label(galley); - } -} diff --git a/crates/re_data_ui/src/pinhole.rs b/crates/re_data_ui/src/pinhole.rs index 3003c3d0897bd..edc923455eb2b 100644 --- a/crates/re_data_ui/src/pinhole.rs +++ b/crates/re_data_ui/src/pinhole.rs @@ -1,7 +1,7 @@ use re_types::components::{PinholeProjection, Resolution}; use re_viewer_context::{UiLayout, ViewerContext}; -use crate::{data_label_for_ui_layout, label_for_ui_layout, DataUi}; +use crate::DataUi; impl DataUi for PinholeProjection { fn data_ui( @@ -24,13 +24,9 @@ impl DataUi for PinholeProjection { fl.to_string() }; - label_for_ui_layout( - ui, - ui_layout, - format!("Focal length: {fl}, principal point: {pp}"), - ) + ui_layout.label(ui, format!("Focal length: {fl}, principal point: {pp}")) } else { - label_for_ui_layout(ui, ui_layout, "3×3 projection matrix") + ui_layout.label(ui, "3×3 projection matrix") } .on_hover_ui(|ui| self.data_ui(ctx, ui, UiLayout::Tooltip, query, db)); } @@ -51,6 +47,6 @@ impl DataUi for Resolution { _db: &re_entity_db::EntityDb, ) { let [x, y] = self.0 .0; - data_label_for_ui_layout(ui, ui_layout, format!("{x}×{y}")); + ui_layout.data_label(ui, format!("{x}×{y}")); } } diff --git a/crates/re_data_ui/src/rotation3d.rs b/crates/re_data_ui/src/rotation3d.rs index d80f160aaf0f3..fe9602f9c1daa 100644 --- a/crates/re_data_ui/src/rotation3d.rs +++ b/crates/re_data_ui/src/rotation3d.rs @@ -4,7 +4,7 @@ use re_types::{ }; use re_viewer_context::{UiLayout, ViewerContext}; -use crate::{data_label_for_ui_layout, label_for_ui_layout, DataUi}; +use crate::DataUi; impl DataUi for components::Rotation3D { fn data_ui( @@ -31,13 +31,13 @@ impl DataUi for datatypes::Rotation3D { match self { datatypes::Rotation3D::Quaternion(q) => { // TODO(andreas): Better formatting for quaternions. - data_label_for_ui_layout(ui, ui_layout, format!("{q:?}")); + ui_layout.data_label(ui, format!("{q:?}")); } datatypes::Rotation3D::AxisAngle(RotationAxisAngle { axis, angle }) => { match ui_layout { UiLayout::List => { // TODO(#6315): should be mixed label/data formatting - label_for_ui_layout(ui, ui_layout, format!("angle: {angle}, axis: {axis}")); + ui_layout.label(ui, format!("angle: {angle}, axis: {axis}")); } _ => { egui::Grid::new("axis_angle").num_columns(2).show(ui, |ui| { diff --git a/crates/re_data_ui/src/transform3d.rs b/crates/re_data_ui/src/transform3d.rs index c0e97485186ae..d2b093cc354f6 100644 --- a/crates/re_data_ui/src/transform3d.rs +++ b/crates/re_data_ui/src/transform3d.rs @@ -1,7 +1,7 @@ use re_types::datatypes::{Scale3D, Transform3D, TranslationAndMat3x3, TranslationRotationScale3D}; use re_viewer_context::{UiLayout, ViewerContext}; -use crate::{label_for_ui_layout, DataUi}; +use crate::DataUi; impl DataUi for re_types::components::Transform3D { #[allow(clippy::only_used_in_recursion)] @@ -16,7 +16,7 @@ impl DataUi for re_types::components::Transform3D { match ui_layout { UiLayout::List => { // TODO(andreas): Preview some information instead of just a label with hover ui. - label_for_ui_layout(ui, ui_layout, "3D transform").on_hover_ui(|ui| { + ui_layout.label(ui, "3D transform").on_hover_ui(|ui| { self.data_ui(ctx, ui, UiLayout::Tooltip, query, db); }); } @@ -35,9 +35,9 @@ impl DataUi for re_types::components::Transform3D { }; ui.vertical(|ui| { - label_for_ui_layout(ui, ui_layout, "3D transform"); + ui_layout.label(ui, "3D transform"); ui.indent("transform_repr", |ui| { - label_for_ui_layout(ui, ui_layout, dir_string); + ui_layout.label(ui, dir_string); self.0.data_ui(ctx, ui, ui_layout, query, db); }); }); diff --git a/crates/re_viewer_context/Cargo.toml b/crates/re_viewer_context/Cargo.toml index f90f05f6eb631..f55bbef8c1fca 100644 --- a/crates/re_viewer_context/Cargo.toml +++ b/crates/re_viewer_context/Cargo.toml @@ -38,6 +38,7 @@ bit-vec.workspace = true bytemuck.workspace = true egui-wgpu.workspace = true egui.workspace = true +egui_extras.workspace = true egui_tiles.workspace = true glam = { workspace = true, features = ["serde"] } half.workspace = true diff --git a/crates/re_viewer_context/src/component_ui_registry.rs b/crates/re_viewer_context/src/component_ui_registry.rs index 4ef9d9ea912ed..d9fc71f17084c 100644 --- a/crates/re_viewer_context/src/component_ui_registry.rs +++ b/crates/re_viewer_context/src/component_ui_registry.rs @@ -38,6 +38,94 @@ pub enum UiLayout { SelectionPanelFull, } +impl UiLayout { + /// Build an egui table and configure it for the given UI layout. + /// + /// Note that the caller is responsible for strictly limiting the number of displayed rows for + /// [`Self::List`] and [`Self::Tooltip`], as the table will not scroll. + pub fn table(self, ui: &mut egui::Ui) -> egui_extras::TableBuilder<'_> { + let table = egui_extras::TableBuilder::new(ui); + match self { + Self::List | Self::Tooltip => { + // Be as small as possible in the hover tooltips. No scrolling related configuration, as + // the content itself must be limited (scrolling is not possible in tooltips). + table.auto_shrink([true, true]) + } + Self::SelectionPanelLimitHeight => { + // Don't take too much vertical space to leave room for other selected items. + table + .auto_shrink([false, true]) + .vscroll(true) + .max_scroll_height(100.0) + } + Self::SelectionPanelFull => { + // We're alone in the selection panel. Let the outer ScrollArea do the work. + table.auto_shrink([false, true]).vscroll(false) + } + } + } + + /// Show a label while respecting the given UI layout. + /// + /// Important: for label only, data should use [`crate::data_label_for_ui_layout`] instead. + // TODO(#6315): must be merged with `Self::data_label` and have an improved API + pub fn label(self, ui: &mut egui::Ui, text: impl Into) -> egui::Response { + let mut label = egui::Label::new(text); + + match self { + Self::List => label = label.truncate(true), + Self::Tooltip | Self::SelectionPanelLimitHeight | Self::SelectionPanelFull => { + label = label.wrap(true); + } + } + + ui.add(label) + } + + /// Show data while respecting the given UI layout. + /// + /// Import: for data only, labels should use [`crate::label_for_ui_layout`] instead. + // TODO(#6315): must be merged with `Self::label` and have an improved API + pub fn data_label(self, ui: &mut egui::Ui, string: impl AsRef) { + let string = string.as_ref(); + let font_id = egui::TextStyle::Monospace.resolve(ui.style()); + let color = ui.visuals().text_color(); + let wrap_width = ui.available_width(); + let mut layout_job = + egui::text::LayoutJob::simple(string.to_owned(), font_id, color, wrap_width); + + let mut needs_scroll_area = false; + + match self { + Self::List => { + // Elide + layout_job.wrap.max_rows = 1; + layout_job.wrap.break_anywhere = true; + } + Self::Tooltip => { + layout_job.wrap.max_rows = 3; + } + Self::SelectionPanelLimitHeight => { + let num_newlines = string.chars().filter(|&c| c == '\n').count(); + needs_scroll_area = 10 < num_newlines || 300 < string.len(); + } + Self::SelectionPanelFull => { + needs_scroll_area = false; + } + } + + let galley = ui.fonts(|f| f.layout_job(layout_job)); // We control the text layout; not the label + + if needs_scroll_area { + egui::ScrollArea::vertical().show(ui, |ui| { + ui.label(galley); + }); + } else { + ui.label(galley); + } + } +} + type ComponentUiCallback = Box< dyn Fn( &ViewerContext<'_>,