diff --git a/crates/re_data_ui/src/editors.rs b/crates/re_data_ui/src/editors.rs index 62b603e3c516..2886043d7e3c 100644 --- a/crates/re_data_ui/src/editors.rs +++ b/crates/re_data_ui/src/editors.rs @@ -260,6 +260,9 @@ fn edit_marker_shape_ui( .width(100.0) .height(320.0) .show_ui(ui, |ui| { + // no spacing between list items + ui.spacing_mut().item_spacing.y = 0.0; + // Hack needed for ListItem to click its highlight bg rect correctly: ui.set_clip_rect( ui.clip_rect() @@ -272,7 +275,7 @@ fn edit_marker_shape_ui( paint_marker(ui, marker.into(), rect, visuals.text_color()); }) .selected(edit_marker == marker); - if list_item.show(ui).clicked() { + if list_item.show_flat(ui).clicked() { edit_marker = marker; } } diff --git a/crates/re_time_panel/src/lib.rs b/crates/re_time_panel/src/lib.rs index 64a4ffd9d262..ae213a976824 100644 --- a/crates/re_time_panel/src/lib.rs +++ b/crates/re_time_panel/src/lib.rs @@ -607,7 +607,7 @@ impl TimePanel { .width_allocation_mode(WidthAllocationMode::Compact) .selected(is_selected) .force_hovered(is_item_hovered) - .show_collapsing( + .show_hierarchical_with_content( ui, CollapseScope::StreamsTree.entity(tree.path.clone()), default_open, @@ -756,7 +756,7 @@ impl TimePanel { == HoverHighlight::Hovered, ) .with_icon(&re_ui::icons::COMPONENT) - .show(ui); + .show_hierarchical(ui); ui.set_clip_rect(clip_rect_save); diff --git a/crates/re_ui/examples/re_ui_example.rs b/crates/re_ui/examples/re_ui_example.rs index 0075c5babb3d..02c7bc0a4edb 100644 --- a/crates/re_ui/examples/re_ui_example.rs +++ b/crates/re_ui/examples/re_ui_example.rs @@ -260,7 +260,8 @@ impl eframe::App for ExampleApp { || re_ui::modal::Modal::new("Modal window").full_span_content(true), |_, ui, _| { for idx in 0..10 { - ListItem::new(&self.re_ui, format!("Item {idx}")).show(ui); + ListItem::new(&self.re_ui, format!("Item {idx}")) + .show_flat(ui); } }, ); @@ -401,7 +402,7 @@ impl eframe::App for ExampleApp { item.with_icon(&re_ui::icons::SPACE_VIEW_TEXT) }; - if item.show(ui).clicked() { + if item.show_flat(ui).clicked() { self.selected_list_item = Some(i); } } @@ -418,15 +419,31 @@ impl eframe::App for ExampleApp { self.re_ui .list_item("Collapsing list item with icon") .with_icon(&re_ui::icons::SPACE_VIEW_2D) - .show_collapsing(ui, "collapsing example", true, |_re_ui, ui| { - self.re_ui.list_item("Sub-item").show(ui); - self.re_ui.list_item("Sub-item").show(ui); - self.re_ui - .list_item("Sub-item with icon") - .with_icon(&re_ui::icons::SPACE_VIEW_TEXT) - .show(ui); - self.re_ui.list_item("Sub-item").show(ui); - }); + .show_hierarchical_with_content( + ui, + "collapsing example", + true, + |_re_ui, ui| { + self.re_ui.list_item("Sub-item").show_hierarchical(ui); + self.re_ui.list_item("Sub-item").show_hierarchical(ui); + self.re_ui + .list_item("Sub-item with icon") + .with_icon(&re_ui::icons::SPACE_VIEW_TEXT) + .show_hierarchical(ui); + self.re_ui + .list_item("Sub-item") + .show_hierarchical_with_content( + ui, + "sub-collapsing", + true, + |_re_ui, ui| { + self.re_ui + .list_item("Sub-sub-item") + .show_hierarchical(ui) + }, + ); + }, + ); }); }); }); @@ -678,7 +695,7 @@ mod drag_and_drop { .list_item(label.as_str()) .selected(self.selected_items.contains(item_id)) .draggable(true) - .show(ui); + .show_flat(ui); // // Handle item selection @@ -1050,7 +1067,7 @@ mod hierarchical_drag_and_drop { .selected(self.selected(item_id)) .draggable(true) .drop_target_style(self.target_container == Some(item_id)) - .show_collapsing(ui, item_id, true, |re_ui, ui| { + .show_hierarchical_with_content(ui, item_id, true, |re_ui, ui| { self.container_children_ui(re_ui, ui, children); }); @@ -1087,7 +1104,7 @@ mod hierarchical_drag_and_drop { .list_item(label) .selected(self.selected(item_id)) .draggable(true) - .show(ui); + .show_hierarchical(ui); self.handle_interaction(ui, item_id, false, &response, None); } diff --git a/crates/re_ui/src/list_item.rs b/crates/re_ui/src/list_item.rs index 67885f357123..d304cac2735b 100644 --- a/crates/re_ui/src/list_item.rs +++ b/crates/re_ui/src/list_item.rs @@ -11,7 +11,7 @@ struct ListItemResponse { collapse_response: Option, } -/// Responses returned by [`ListItem::show_collapsing`]. +/// Responses returned by [`ListItem::show_hierarchical_with_content`]. pub struct ShowCollapsingResponse { /// Response from the item itself. pub item_response: Response, @@ -226,8 +226,8 @@ impl<'a> ListItem<'a> { /// Override the hovered state even if the item is not actually hovered. /// /// Used to highlight items representing things that are hovered elsewhere in the UI. Note that - /// the [`egui::Response`] returned by [`Self::show`] and ]`Self::show_collapsing`] will still - /// reflect the actual hover state. + /// the [`egui::Response`] returned by [`Self::show_flat`], [`Self::show_hierarchical`], and + /// [`Self::show_hierarchical_with_content`] will still reflect the actual hover state. #[inline] pub fn force_hovered(mut self, force_hovered: bool) -> Self { self.force_hovered = force_hovered; @@ -286,14 +286,29 @@ impl<'a> ListItem<'a> { self } - /// Draw the item. - pub fn show(self, ui: &mut Ui) -> Response { + /// Draw the item as part of a flat list. + pub fn show_flat(self, ui: &mut Ui) -> Response { // Note: the purpose of the scope is to minimise interferences on subsequent items' id ui.scope(|ui| self.ui(ui, None)).inner.response } - /// Draw the item as a collapsing header. - pub fn show_collapsing( + /// Draw the item as a leaf node from a hierarchical list. + pub fn show_hierarchical(self, ui: &mut Ui) -> Response { + // Note: the purpose of the scope is to minimise interferences on subsequent items' id + ui.scope(|ui| { + ui.horizontal(|ui| { + ui.add_space(ReUi::small_icon_size().x + ReUi::text_to_icon_padding()); + ui.vertical(|ui| self.ui(ui, None)) + }) + }) + .inner + .inner + .inner + .response + } + + /// Draw the item as a non-leaf node from a hierarchical list. + pub fn show_hierarchical_with_content( mut self, ui: &mut Ui, id: impl Into, diff --git a/crates/re_viewer/src/ui/recordings_panel.rs b/crates/re_viewer/src/ui/recordings_panel.rs index dd67a18ca10b..cfb16cbeeec1 100644 --- a/crates/re_viewer/src/ui/recordings_panel.rs +++ b/crates/re_viewer/src/ui/recordings_panel.rs @@ -92,7 +92,7 @@ fn loading_receivers_ui( } resp }) - .show(ui); + .show_flat(ui); // never more than one level deep if let SmartChannelSource::TcpServer { .. } = source.as_ref() { response.on_hover_text("You can connect to this viewer from a Rerun SDK"); } @@ -129,16 +129,19 @@ fn recording_list_ui(ctx: &ViewerContext<'_>, ui: &mut egui::Ui) -> bool { let entity_db = entity_dbs[0]; recording_ui(ctx, ui, entity_db, Some(app_id), active_recording); } else { - ctx.re_ui.list_item(app_id).active(false).show_collapsing( - ui, - ui.make_persistent_id(app_id), - true, - |_, ui| { - for entity_db in entity_dbs { - recording_ui(ctx, ui, entity_db, None, active_recording); - } - }, - ); + ctx.re_ui + .list_item(app_id) + .active(false) + .show_hierarchical_with_content( + ui, + ui.make_persistent_id(app_id), + true, + |_, ui| { + for entity_db in entity_dbs { + recording_ui(ctx, ui, entity_db, None, active_recording); + } + }, + ); } } @@ -203,15 +206,17 @@ fn recording_ui( list_item = list_item.force_hovered(true); } - let response = list_item.show(ui).on_hover_ui(|ui| { - entity_db.data_ui( - ctx, - ui, - re_viewer_context::UiVerbosity::Full, - &ctx.current_query(), - entity_db.store(), - ); - }); + let response = list_item + .show_flat(ui) // never more than one level deep + .on_hover_ui(|ui| { + entity_db.data_ui( + ctx, + ui, + re_viewer_context::UiVerbosity::Full, + &ctx.current_query(), + entity_db.store(), + ); + }); if response.hovered() { ctx.selection_state().set_hovered(item.clone()); diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index 342cd2218a96..a93857e4c027 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -227,7 +227,7 @@ fn container_children( .weak(true) .italics(true) .active(false) - .show(ui); + .show_flat(ui); } }; @@ -380,7 +380,7 @@ fn what_is_selected_ui( .with_icon(space_view.class(ctx.space_view_class_registry).icon()) .with_height(ReUi::title_bar_height()) .selected(true) - .show(ui) + .show_flat(ui) .on_hover_text(hover_text); } } @@ -485,7 +485,7 @@ fn item_title_ui( list_item = list_item.with_icon(icon); } - list_item.show(ui).on_hover_text(hover) + list_item.show_flat(ui).on_hover_text(hover) } /// Display a list of all the space views an entity appears in. @@ -749,7 +749,7 @@ fn show_list_item_for_container_child( list_item = list_item.force_hovered(true); } - let response = list_item.show(ui); + let response = list_item.show_flat(ui); context_menu_ui_for_item( ctx, diff --git a/crates/re_viewer/src/ui/space_view_space_origin_ui.rs b/crates/re_viewer/src/ui/space_view_space_origin_ui.rs index fd3b55de8b4a..7da6db8d4ac3 100644 --- a/crates/re_viewer/src/ui/space_view_space_origin_ui.rs +++ b/crates/re_viewer/src/ui/space_view_space_origin_ui.rs @@ -192,7 +192,7 @@ fn space_view_space_origin_widget_editing_ui( .syntax_highlighted(ui.style()), ) .force_hovered(*selected_suggestion == Some(idx)) - .show(ui); + .show_flat(ui); if response.hovered() { *selected_suggestion = None; @@ -213,7 +213,7 @@ fn space_view_space_origin_widget_editing_ui( .weak(true) .italics(true) .active(false) - .show(ui); + .show_flat(ui); } }; diff --git a/crates/re_viewport/src/viewport_blueprint_ui.rs b/crates/re_viewport/src/viewport_blueprint_ui.rs index 382e62c8880e..b12fc0c8f2d0 100644 --- a/crates/re_viewport/src/viewport_blueprint_ui.rs +++ b/crates/re_viewport/src/viewport_blueprint_ui.rs @@ -275,7 +275,7 @@ impl Viewport<'_, '_> { .with_icon(crate::icon_for_container_kind( &container_blueprint.container_kind, )) - .show(ui); + .show_flat(ui); for child in &container_blueprint.contents { self.contents_ui(ctx, ui, child, true); @@ -344,7 +344,7 @@ impl Viewport<'_, '_> { remove_response | vis_response }) - .show_collapsing( + .show_hierarchical_with_content( ui, CollapseScope::BlueprintTree.container(*container_id), default_open, @@ -429,7 +429,7 @@ impl Viewport<'_, '_> { response | vis_response }) - .show_collapsing( + .show_hierarchical_with_content( ui, CollapseScope::BlueprintTree.space_view(*space_view_id), default_open, @@ -532,7 +532,7 @@ impl Viewport<'_, '_> { .subdued(true) .italics(true) .with_icon(&re_ui::icons::LINK) - .show(ui) + .show_hierarchical(ui) .on_hover_text( "This subtree corresponds to the Space View's origin, and is displayed above \ the 'Projections' section. Click to select it.", @@ -621,7 +621,7 @@ impl Viewport<'_, '_> { && Self::default_open_for_data_result(node); list_item - .show_collapsing( + .show_hierarchical_with_content( ui, CollapseScope::BlueprintTree.data_result(space_view.id, entity_path.clone()), default_open, @@ -654,7 +654,7 @@ impl Viewport<'_, '_> { ) .item_response } else { - list_item.show(ui) + list_item.show_hierarchical(ui) }; let response = response.on_hover_ui(|ui| {