From 8881e21c1b12fb6829bf3c540bbe76c4759755c8 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:47:46 +0100 Subject: [PATCH] Make Space View containers selectable and editable (#4403) ### What Containers may now be selected and edited. For now, they can be transmuted into another container type, and grid containers have a setting for their column count. - Closes https://github.com/rerun-io/rerun/issues/4162 image ### Checklist * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested [app.rerun.io](https://app.rerun.io/pr/4403) (if applicable) * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG - [PR Build Summary](https://build.rerun.io/pr/4403) - [Docs preview](https://rerun.io/preview/2f30366bc1db4ffc0f5b3a4b3a7f9e8b2dffd7b4/docs) - [Examples preview](https://rerun.io/preview/2f30366bc1db4ffc0f5b3a4b3a7f9e8b2dffd7b4/examples) - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html) --- Cargo.lock | 2 + crates/re_data_ui/src/item.rs | 2 +- .../re_time_panel/src/data_density_graph.rs | 2 +- crates/re_viewer/Cargo.toml | 1 + .../re_viewer/src/ui/selection_history_ui.rs | 18 +++ crates/re_viewer/src/ui/selection_panel.rs | 123 ++++++++++++++++-- crates/re_viewer_context/Cargo.toml | 1 + crates/re_viewer_context/src/item.rs | 10 +- .../re_viewer_context/src/selection_state.rs | 7 +- .../re_viewport/src/space_view_highlights.rs | 4 +- crates/re_viewport/src/viewport_blueprint.rs | 15 +++ .../re_viewport/src/viewport_blueprint_ui.rs | 10 +- 12 files changed, 173 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 002599de4690..03187ffc6817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5053,6 +5053,7 @@ dependencies = [ "egui", "egui-wgpu", "egui_plot", + "egui_tiles", "ehttp", "image", "itertools 0.11.0", @@ -5110,6 +5111,7 @@ dependencies = [ "bytemuck", "egui", "egui-wgpu", + "egui_tiles", "glam", "half 2.3.1", "itertools 0.11.0", diff --git a/crates/re_data_ui/src/item.rs b/crates/re_data_ui/src/item.rs index c1409714fb3d..521532262c02 100644 --- a/crates/re_data_ui/src/item.rs +++ b/crates/re_data_ui/src/item.rs @@ -11,7 +11,7 @@ impl DataUi for Item { query: &re_arrow_store::LatestAtQuery, ) { match self { - Item::SpaceView(_) | Item::DataBlueprintGroup(_, _, _) => { + Item::SpaceView(_) | Item::DataBlueprintGroup(_, _, _) | Item::Container(_) => { // Shouldn't be reachable since SelectionPanel::contents doesn't show data ui for these. // If you add something in here make sure to adjust SelectionPanel::contents accordingly. } diff --git a/crates/re_time_panel/src/data_density_graph.rs b/crates/re_time_panel/src/data_density_graph.rs index a61ec5173471..27535dd2ec71 100644 --- a/crates/re_time_panel/src/data_density_graph.rs +++ b/crates/re_time_panel/src/data_density_graph.rs @@ -547,7 +547,7 @@ fn show_row_ids_tooltip( Item::InstancePath(_, path) => { item_ui::instance_path_button(ctx, ui, None, path); } - Item::SpaceView(_) | Item::DataBlueprintGroup(_, _, _) => { + Item::SpaceView(_) | Item::DataBlueprintGroup(_, _, _) | Item::Container(_) => { // No extra info. This should never happen, but not worth printing a warning over. // Even if it does go here, the ui after will still look ok. } diff --git a/crates/re_viewer/Cargo.toml b/crates/re_viewer/Cargo.toml index d6e88339fb1d..7896af475092 100644 --- a/crates/re_viewer/Cargo.toml +++ b/crates/re_viewer/Cargo.toml @@ -85,6 +85,7 @@ eframe = { workspace = true, default-features = false, features = [ egui_plot.workspace = true egui-wgpu.workspace = true egui.workspace = true +egui_tiles.workspace = true ehttp.workspace = true image = { workspace = true, default-features = false, features = ["png"] } itertools = { workspace = true } diff --git a/crates/re_viewer/src/ui/selection_history_ui.rs b/crates/re_viewer/src/ui/selection_history_ui.rs index 062ea5bf7f1a..b7e00769bc18 100644 --- a/crates/re_viewer/src/ui/selection_history_ui.rs +++ b/crates/re_viewer/src/ui/selection_history_ui.rs @@ -1,4 +1,5 @@ use egui::RichText; +use egui_tiles::Tile; use re_ui::UICommand; use re_viewer_context::{Item, ItemCollection, SelectionHistory}; use re_viewport::ViewportBlueprint; @@ -181,5 +182,22 @@ fn item_to_string(blueprint: &ViewportBlueprint<'_>, item: &Item) -> String { Item::ComponentPath(path) => { format!("{} {}", path.entity_path, path.component_name.short_name(),) } + Item::Container(tile_id) => { + if let Some(tile) = blueprint.tree.tiles.get(*tile_id) { + match tile { + Tile::Pane(sid) => { + // This case shouldn't happen really. + if let Some(space_view) = blueprint.space_view(sid) { + format!("Tile showing {}", space_view.display_name) + } else { + "Tile containing unknown Space View".to_owned() + } + } + Tile::Container(container) => format!("{:?}", container.kind()), + } + } else { + "".to_owned() + } + } } } diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index 21fb6426fbab..104b05d55742 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -1,4 +1,5 @@ use egui::NumExt as _; +use egui_tiles::{GridLayout, Tile}; use re_data_store::{ ColorMapper, Colormap, EditableAutoValue, EntityPath, EntityProperties, VisibleHistory, @@ -116,13 +117,21 @@ impl SelectionPanel { ui.push_id(i, |ui| { what_is_selected_ui(ui, ctx, &mut viewport.blueprint, item); - if let Item::SpaceView(space_view_id) = item { - space_view_top_level_properties( - ui, - ctx, - &mut viewport.blueprint, - space_view_id, - ); + match item { + Item::Container(tile_id) => { + container_top_level_properties(ui, ctx, &mut viewport.blueprint, tile_id); + } + + Item::SpaceView(space_view_id) => { + space_view_top_level_properties( + ui, + ctx, + &mut viewport.blueprint, + space_view_id, + ); + } + + _ => {} } if has_data_section(item) { @@ -151,7 +160,7 @@ fn has_data_section(item: &Item) -> bool { match item { Item::ComponentPath(_) | Item::InstancePath(_, _) => true, // Skip data ui since we don't know yet what to show for these. - Item::SpaceView(_) | Item::DataBlueprintGroup(_, _, _) => false, + Item::SpaceView(_) | Item::DataBlueprintGroup(_, _, _) | Item::Container(_) => false, } } @@ -185,6 +194,17 @@ fn what_is_selected_ui( item: &Item, ) { match item { + Item::Container(tile_id) => { + if let Some(Tile::Container(container)) = viewport.tree.tiles.get(*tile_id) { + item_title_ui( + ctx.re_ui, + ui, + &format!("{:?}", container.kind()), + None, + &format!("{:?} container", container.kind()), + ); + } + } Item::ComponentPath(re_log_types::ComponentPath { entity_path, component_name, @@ -379,9 +399,92 @@ fn space_view_top_level_properties( } } +fn container_top_level_properties( + ui: &mut egui::Ui, + _ctx: &mut ViewerContext<'_>, + viewport: &mut ViewportBlueprint<'_>, + tile_id: &egui_tiles::TileId, +) { + if let Some(Tile::Container(container)) = viewport.tree.tiles.get_mut(*tile_id) { + egui::Grid::new("container_top_level_properties") + .num_columns(2) + .show(ui, |ui| { + ui.label("Kind"); + + let mut container_kind = container.kind(); + egui::ComboBox::from_id_source("container_kind") + .selected_text(format!("{container_kind:?}")) + .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(false); + ui.set_min_width(64.0); + + ui.selectable_value( + &mut container_kind, + egui_tiles::ContainerKind::Tabs, + format!("{:?}", egui_tiles::ContainerKind::Tabs), + ); + ui.selectable_value( + &mut container_kind, + egui_tiles::ContainerKind::Horizontal, + format!("{:?}", egui_tiles::ContainerKind::Horizontal), + ); + ui.selectable_value( + &mut container_kind, + egui_tiles::ContainerKind::Vertical, + format!("{:?}", egui_tiles::ContainerKind::Vertical), + ); + ui.selectable_value( + &mut container_kind, + egui_tiles::ContainerKind::Grid, + format!("{:?}", egui_tiles::ContainerKind::Grid), + ); + }); + + container.set_kind(container_kind); + + ui.end_row(); + + if let egui_tiles::Container::Grid(grid) = container { + ui.label("Columns"); + + fn grid_layout_to_string(layout: &egui_tiles::GridLayout) -> String { + match layout { + GridLayout::Auto => "Auto".to_owned(), + GridLayout::Columns(cols) => cols.to_string(), + } + } + + egui::ComboBox::from_id_source("container_grid_columns") + .selected_text(grid_layout_to_string(&grid.layout)) + .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(false); + ui.set_min_width(64.0); + + ui.selectable_value( + &mut grid.layout, + GridLayout::Auto, + grid_layout_to_string(&GridLayout::Auto), + ); + ui.separator(); + + for columns in 1..=grid.num_children() { + ui.selectable_value( + &mut grid.layout, + GridLayout::Columns(columns), + grid_layout_to_string(&GridLayout::Columns(columns)), + ); + } + }); + + ui.end_row(); + } + }); + } +} + fn has_blueprint_section(item: &Item) -> bool { match item { - Item::ComponentPath(_) => false, + Item::ComponentPath(_) | Item::Container(_) => false, Item::InstancePath(space_view_id, _) => space_view_id.is_some(), _ => true, } @@ -594,7 +697,7 @@ fn blueprint_ui( } } - Item::ComponentPath(_) => {} + Item::ComponentPath(_) | Item::Container(_) => {} } } diff --git a/crates/re_viewer_context/Cargo.toml b/crates/re_viewer_context/Cargo.toml index 20bc77a88e0f..4c744d1e920c 100644 --- a/crates/re_viewer_context/Cargo.toml +++ b/crates/re_viewer_context/Cargo.toml @@ -33,6 +33,7 @@ anyhow.workspace = true bytemuck.workspace = true egui-wgpu.workspace = true egui.workspace = true +egui_tiles.workspace = true glam.workspace = true half.workspace = true itertools.workspace = true diff --git a/crates/re_viewer_context/src/item.rs b/crates/re_viewer_context/src/item.rs index 8e02f2c3c289..cf07d925eee3 100644 --- a/crates/re_viewer_context/src/item.rs +++ b/crates/re_viewer_context/src/item.rs @@ -18,6 +18,7 @@ pub enum Item { SpaceView(SpaceViewId), InstancePath(Option, InstancePath), DataBlueprintGroup(SpaceViewId, DataQueryId, EntityPath), + Container(egui_tiles::TileId), } impl From for Item { @@ -90,6 +91,7 @@ impl std::fmt::Debug for Item { Item::DataBlueprintGroup(sid, qid, entity_path) => { write!(f, "({sid:?}, {qid:?}, {entity_path:?})") } + Item::Container(tile_id) => write!(f, "(tile: {tile_id:?})"), } } } @@ -111,6 +113,7 @@ impl Item { Item::ComponentPath(_) => "Entity Component", Item::SpaceView(_) => "Space View", Item::DataBlueprintGroup(_, _, _) => "Group", + Item::Container(_) => "Container", } } } @@ -199,9 +202,10 @@ pub fn resolve_mono_instance_path_item( *space_view, resolve_mono_instance_path(query, store, instance), ), - Item::ComponentPath(_) | Item::SpaceView(_) | Item::DataBlueprintGroup(_, _, _) => { - item.clone() - } + Item::ComponentPath(_) + | Item::SpaceView(_) + | Item::DataBlueprintGroup(_, _, _) + | Item::Container(_) => item.clone(), } } diff --git a/crates/re_viewer_context/src/selection_state.rs b/crates/re_viewer_context/src/selection_state.rs index 708147174bb4..5502da850cf7 100644 --- a/crates/re_viewer_context/src/selection_state.rs +++ b/crates/re_viewer_context/src/selection_state.rs @@ -215,9 +215,10 @@ impl SelectionState { .hovered_previous_frame .iter() .any(|current| match current { - Item::ComponentPath(_) | Item::SpaceView(_) | Item::DataBlueprintGroup(_, _, _) => { - current == test - } + Item::ComponentPath(_) + | Item::SpaceView(_) + | Item::DataBlueprintGroup(_, _, _) + | Item::Container(_) => current == test, Item::InstancePath(current_space_view_id, current_instance_path) => { if let Item::InstancePath(test_space_view_id, test_instance_path) = test { diff --git a/crates/re_viewport/src/space_view_highlights.rs b/crates/re_viewport/src/space_view_highlights.rs index a0bc834ac946..bd18ee6b42f4 100644 --- a/crates/re_viewport/src/space_view_highlights.rs +++ b/crates/re_viewport/src/space_view_highlights.rs @@ -38,7 +38,7 @@ pub fn highlights_for_space_view( for current_selection in selection_state.current().iter() { match current_selection { - Item::ComponentPath(_) | Item::SpaceView(_) => {} + Item::ComponentPath(_) | Item::SpaceView(_) | Item::Container(_) => {} Item::DataBlueprintGroup(_space_view_id, _query_id, _entity_path) => { // TODO(#4377): Fix DataBlueprintGroup @@ -115,7 +115,7 @@ pub fn highlights_for_space_view( for current_hover in selection_state.hovered().iter() { match current_hover { - Item::ComponentPath(_) | Item::SpaceView(_) => {} + Item::ComponentPath(_) | Item::SpaceView(_) | Item::Container(_) => {} Item::DataBlueprintGroup(_space_view_id, _query_id, _entity_path) => { // TODO(#4377): Fix DataBlueprintGroup diff --git a/crates/re_viewport/src/viewport_blueprint.rs b/crates/re_viewport/src/viewport_blueprint.rs index 4bf2ea4f7a6e..5971796aaaed 100644 --- a/crates/re_viewport/src/viewport_blueprint.rs +++ b/crates/re_viewport/src/viewport_blueprint.rs @@ -165,6 +165,21 @@ impl<'a> ViewportBlueprint<'a> { .space_views .get(space_view_id) .map_or(false, |sv| sv.queries.iter().any(|q| q.id == *query_id)), + Item::Container(tile_id) => { + if Some(*tile_id) == self.tree.root { + // the root tile is always visible + true + } else if let Some(tile) = self.tree.tiles.get(*tile_id) { + if let egui_tiles::Tile::Container(container) = tile { + // single children containers are generally hidden + container.num_children() > 1 + } else { + true + } + } else { + false + } + } } } diff --git a/crates/re_viewport/src/viewport_blueprint_ui.rs b/crates/re_viewport/src/viewport_blueprint_ui.rs index 8a6b87939b87..87190c61a18f 100644 --- a/crates/re_viewport/src/viewport_blueprint_ui.rs +++ b/crates/re_viewport/src/viewport_blueprint_ui.rs @@ -101,14 +101,17 @@ impl ViewportBlueprint<'_> { return self.tile_ui(ctx, ui, child_id); } + let item = Item::Container(tile_id); + let mut visibility_changed = false; let mut visible = self.tree.is_visible(tile_id); let mut remove = false; let default_open = true; - ListItem::new(ctx.re_ui, format!("{:?}", container.kind())) + let response = ListItem::new(ctx.re_ui, format!("{:?}", container.kind())) .subdued(true) + .selected(ctx.selection().contains(&item)) .with_buttons(|re_ui, ui| { let vis_response = visibility_button_ui(re_ui, ui, true, &mut visible); visibility_changed = vis_response.changed(); @@ -122,7 +125,10 @@ impl ViewportBlueprint<'_> { for &child in container.children() { self.tile_ui(ctx, ui, child); } - }); + }) + .item_response; + + item_ui::select_hovered_on_click(ctx, &response, &[item]); if remove { self.mark_user_interaction();