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
### 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();