From dffaeb14d38899d32cbaa21d605548daca564dfe Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 3 Dec 2024 17:09:34 +0100 Subject: [PATCH 01/39] Always show an item, even if it is not found --- .../re_selection_panel/src/item_title.rs | 134 +++++++++--------- .../re_selection_panel/src/selection_panel.rs | 18 ++- 2 files changed, 78 insertions(+), 74 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 9204a4e81006..f2542718b86d 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -17,16 +17,16 @@ impl ItemTitle { viewport: &ViewportBlueprint, style: &egui::Style, item: &Item, - ) -> Option { + ) -> Self { match &item { Item::AppId(app_id) => { let title = app_id.to_string(); - Some(Self::new(title, &icons::APPLICATION)) + Self::new(title, &icons::APPLICATION) } Item::DataSource(data_source) => { let title = data_source.to_string(); - Some(Self::new(title, &icons::DATA_SOURCE)) + Self::new(title, &icons::DATA_SOURCE) } Item::StoreId(store_id) => { @@ -55,7 +55,41 @@ impl ItemTitle { re_log_types::StoreKind::Blueprint => &icons::BLUEPRINT, }; - Some(Self::new(title, icon).with_tooltip(id_str)) + Self::new(title, icon).with_tooltip(id_str) + } + + Item::InstancePath(instance_path) => { + let typ = item.kind(); + let name = instance_path.syntax_highlighted(style); + + Self::new(name, guess_instance_path_icon(ctx, instance_path)) + .with_tooltip(format!("{typ} '{instance_path}'")) + } + + Item::ComponentPath(component_path) => { + let entity_path = &component_path.entity_path; + let component_name = &component_path.component_name; + + let (_query, db) = guess_query_and_db_for_selected_entity(ctx, entity_path); + let is_static = db + .storage_engine() + .store() + .entity_has_static_component(entity_path, component_name); + + Self::new( + component_name.short_name(), + if is_static { + &icons::COMPONENT_STATIC + } else { + &icons::COMPONENT_TEMPORAL + }, + ) + .with_tooltip(format!( + "{} component {} of entity '{}'", + if is_static { "Static" } else { "Temporal" }, + component_name.full_name(), + entity_path + )) } Item::Container(container_id) => { @@ -71,47 +105,21 @@ impl ItemTitle { }; let container_name = container_blueprint.display_name_or_default(); - Some( - Self::new( - container_name.as_ref(), - re_viewer_context::icon_for_container_kind( - &container_blueprint.container_kind, - ), - ) - .with_label_style(contents_name_style(&container_name)) - .with_tooltip(hover_text), + Self::new( + container_name.as_ref(), + re_viewer_context::icon_for_container_kind( + &container_blueprint.container_kind, + ), ) + .with_label_style(contents_name_style(&container_name)) + .with_tooltip(hover_text) } else { - None - } - } - - Item::ComponentPath(component_path) => { - let entity_path = &component_path.entity_path; - let component_name = &component_path.component_name; - - let (_query, db) = guess_query_and_db_for_selected_entity(ctx, entity_path); - let is_static = db - .storage_engine() - .store() - .entity_has_static_component(entity_path, component_name); - - Some( Self::new( - component_name.short_name(), - if is_static { - &icons::COMPONENT_STATIC - } else { - &icons::COMPONENT_TEMPORAL - }, + format!("Unknown container {container_id}"), + &icons::SPACE_VIEW_UNKNOWN, ) - .with_tooltip(format!( - "{} component {} of entity '{}'", - if is_static { "Static" } else { "Temporal" }, - component_name.full_name(), - entity_path - )), - ) + .with_tooltip("Failed to find container in blueprint") + } } Item::SpaceView(view_id) => { @@ -130,44 +138,34 @@ impl ItemTitle { let view_name = view.display_name_or_default(); - Some( - Self::new( - view_name.as_ref(), - view.class(ctx.space_view_class_registry).icon(), - ) - .with_label_style(contents_name_style(&view_name)) - .with_tooltip(hover_text), + Self::new( + view_name.as_ref(), + view.class(ctx.space_view_class_registry).icon(), ) + .with_label_style(contents_name_style(&view_name)) + .with_tooltip(hover_text) } else { - None + Self::new( + format!("Unknown view {view_id}"), + &icons::SPACE_VIEW_UNKNOWN, + ) + .with_tooltip("Failed to find view in blueprint") } } - Item::InstancePath(instance_path) => { - let typ = item.kind(); - let name = instance_path.syntax_highlighted(style); - - Some( - Self::new(name, guess_instance_path_icon(ctx, instance_path)) - .with_tooltip(format!("{typ} '{instance_path}'")), - ) - } - Item::DataResult(view_id, instance_path) => { let name = instance_path.syntax_highlighted(style); + let item_title = Self::new(name, guess_instance_path_icon(ctx, instance_path)); + if let Some(view) = viewport.view(view_id) { let typ = item.kind(); - Some( - Self::new(name, guess_instance_path_icon(ctx, instance_path)).with_tooltip( - format!( - "{typ} '{instance_path}' as shown in view {:?}", - view.display_name - ), - ), - ) + item_title.with_tooltip(format!( + "{typ} '{instance_path}' as shown in view {:?}", + view.display_name + )) } else { - None + item_title } } } diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index f5997b3db199..9b620e17c8ae 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -124,12 +124,7 @@ impl SelectionPanel { }; for (i, item) in selection.iter_items().enumerate() { list_item::list_item_scope(ui, item, |ui| { - if let Some(item_title) = ItemTitle::from_item(ctx, viewport, ui.style(), item) { - item_title.ui(ctx, ui, item); - } else { - re_log::warn_once!("Failed to create item title for {item:?}"); - return; // WEIRD - } + item_heading(ctx, viewport, ui, item); self.item_ui(ctx, viewport, view_states, ui, item, ui_layout); }); @@ -441,6 +436,17 @@ The last rule matching `/world/house` is `+ /world/**`, so it is included. } } +/// We show this above each item section +fn item_heading( + ctx: &ViewerContext<'_>, + viewport: &ViewportBlueprint, + ui: &mut egui::Ui, + item: &Item, +) { + let item_title = ItemTitle::from_item(ctx, viewport, ui.style(), item); + item_title.ui(ctx, ui, item); +} + fn entity_selection_ui( ctx: &ViewerContext<'_>, ui: &mut egui::Ui, From ef61c6909a097fe88184f0d22388ee479d590d02 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 3 Dec 2024 17:20:04 +0100 Subject: [PATCH 02/39] Refactor `ItemTitle` --- .../re_selection_panel/src/item_title.rs | 48 ++++--------------- .../re_selection_panel/src/selection_panel.rs | 37 ++++++++++++-- 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index f2542718b86d..438569b0e4bb 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -1,14 +1,14 @@ use re_data_ui::item_ui::{guess_instance_path_icon, guess_query_and_db_for_selected_entity}; -use re_ui::{icons, list_item, DesignTokens, SyntaxHighlighting as _, UiExt as _}; -use re_viewer_context::{contents_name_style, Item, SystemCommandSender as _, ViewerContext}; +use re_ui::{icons, list_item, SyntaxHighlighting as _}; +use re_viewer_context::{contents_name_style, Item, ViewerContext}; use re_viewport_blueprint::ViewportBlueprint; #[must_use] pub struct ItemTitle { - name: egui::WidgetText, - hover: Option, - icon: &'static re_ui::Icon, - label_style: Option, + pub name: egui::WidgetText, + pub tooltip: Option, + pub icon: &'static re_ui::Icon, + pub label_style: Option, } impl ItemTitle { @@ -174,7 +174,7 @@ impl ItemTitle { fn new(name: impl Into, icon: &'static re_ui::Icon) -> Self { Self { name: name.into(), - hover: None, + tooltip: None, icon, label_style: None, } @@ -182,7 +182,7 @@ impl ItemTitle { #[inline] fn with_tooltip(mut self, hover: impl Into) -> Self { - self.hover = Some(hover.into()); + self.tooltip = Some(hover.into()); self } @@ -191,36 +191,4 @@ impl ItemTitle { self.label_style = Some(label_style); self } - - pub fn ui(self, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, item: &Item) { - let Self { - name, - hover, - icon, - label_style, - } = self; - - let mut content = list_item::LabelContent::new(name).with_icon(icon); - - if let Some(label_style) = label_style { - content = content.label_style(label_style); - } - - let response = ui - .list_item() - .with_height(DesignTokens::title_bar_height()) - .selected(true) - .show_flat(ui, content); - - if response.clicked() { - // If the user has multiple things selected but only wants to have one thing selected, - // this is how they can do it. - ctx.command_sender - .send_system(re_viewer_context::SystemCommand::SetSelection(item.clone())); - } - - if let Some(hover) = hover { - response.on_hover_text(hover); - } - } } diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 9b620e17c8ae..2b88a1ef146e 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -9,12 +9,12 @@ use re_types::blueprint::components::Interactive; use re_ui::{ icons, list_item::{self, PropertyContent}, - ContextExt as _, UiExt, + ContextExt as _, DesignTokens, UiExt, }; use re_viewer_context::{ contents_name_style, icon_for_container_kind, ContainerId, Contents, DataQueryResult, - DataResult, HoverHighlight, Item, SpaceViewId, UiLayout, ViewContext, ViewStates, - ViewerContext, + DataResult, HoverHighlight, Item, SpaceViewId, SystemCommandSender as _, UiLayout, ViewContext, + ViewStates, ViewerContext, }; use re_viewport_blueprint::{ui::show_add_space_view_or_container_modal, ViewportBlueprint}; @@ -444,7 +444,36 @@ fn item_heading( item: &Item, ) { let item_title = ItemTitle::from_item(ctx, viewport, ui.style(), item); - item_title.ui(ctx, ui, item); + + let ItemTitle { + name, + tooltip, + icon, + label_style, + } = item_title; + + let mut content = list_item::LabelContent::new(name).with_icon(icon); + + if let Some(label_style) = label_style { + content = content.label_style(label_style); + } + + let response = ui + .list_item() + .with_height(DesignTokens::title_bar_height()) + .selected(true) + .show_flat(ui, content); + + if response.clicked() { + // If the user has multiple things selected but only wants to have one thing selected, + // this is how they can do it. + ctx.command_sender + .send_system(re_viewer_context::SystemCommand::SetSelection(item.clone())); + } + + if let Some(tooltip) = tooltip { + response.on_hover_text(tooltip); + } } fn entity_selection_ui( From 3fa7e02c16848256ba467885b26b60818c961235 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 09:38:37 +0100 Subject: [PATCH 03/39] Do not use Debug-formatting in user-facing strings --- crates/viewer/re_selection_panel/src/item_title.rs | 4 ++-- crates/viewer/re_viewer_context/src/contents.rs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 438569b0e4bb..2734c76f41d9 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -161,8 +161,8 @@ impl ItemTitle { if let Some(view) = viewport.view(view_id) { let typ = item.kind(); item_title.with_tooltip(format!( - "{typ} '{instance_path}' as shown in view {:?}", - view.display_name + "{typ} '{instance_path}' as shown in view {}", + view.display_name_or_default() )) } else { item_title diff --git a/crates/viewer/re_viewer_context/src/contents.rs b/crates/viewer/re_viewer_context/src/contents.rs index 378afd8228b4..a0c0d8626f42 100644 --- a/crates/viewer/re_viewer_context/src/contents.rs +++ b/crates/viewer/re_viewer_context/src/contents.rs @@ -109,6 +109,12 @@ pub enum ContentsName { Placeholder(String), } +impl std::fmt::Display for ContentsName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_ref()) + } +} + impl AsRef for ContentsName { #[inline] fn as_ref(&self) -> &str { From 8406c686d4df34e2a52f5a2ae1cdbdae7a48bcc0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 09:41:35 +0100 Subject: [PATCH 04/39] Fix: make sure selected list items show up as such --- crates/viewer/re_ui/src/list_item/list_item.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/viewer/re_ui/src/list_item/list_item.rs b/crates/viewer/re_ui/src/list_item/list_item.rs index dc32530862c4..ba84dcdd32f2 100644 --- a/crates/viewer/re_ui/src/list_item/list_item.rs +++ b/crates/viewer/re_ui/src/list_item/list_item.rs @@ -87,6 +87,9 @@ impl ListItem { #[inline] pub fn selected(mut self, selected: bool) -> Self { self.selected = selected; + if selected { + self.interactive = true; // Hack needed, or the item is not shown as selected. TODO(emilk): why? + } self } From 3436f823dea0f54587f5be65faf09c04a5e6003c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 09:42:14 +0100 Subject: [PATCH 05/39] Implement selection heading contents manuallt --- .../re_selection_panel/src/item_heading.rs | 71 +++++++++++++++++++ .../re_selection_panel/src/item_title.rs | 2 +- crates/viewer/re_selection_panel/src/lib.rs | 1 + .../re_selection_panel/src/selection_panel.rs | 53 +++----------- 4 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 crates/viewer/re_selection_panel/src/item_heading.rs diff --git a/crates/viewer/re_selection_panel/src/item_heading.rs b/crates/viewer/re_selection_panel/src/item_heading.rs new file mode 100644 index 000000000000..2af079e9fdff --- /dev/null +++ b/crates/viewer/re_selection_panel/src/item_heading.rs @@ -0,0 +1,71 @@ +//! The heading of each item in the selection panel. +//! +//! It consists of a blue background (selected background color), +//! and within it there are "bread-crumbs" that show the hierarchy of the item. +//! +//! A > B > C > D > item +//! +//! Each bread-crumb is just an icon or a letter. +//! The item is an icon and a name. +//! Each bread-crumb is clickable, as is the last item. + +use re_ui::{list_item, DesignTokens, UiExt as _}; +use re_viewer_context::{Item, SystemCommandSender as _, ViewerContext}; +use re_viewport_blueprint::ViewportBlueprint; + +use crate::ItemTitle; + +/// We show this above each item section +pub fn item_heading( + ctx: &ViewerContext<'_>, + viewport: &ViewportBlueprint, + ui: &mut egui::Ui, + item: &Item, +) { + ui.list_item() + .with_height(DesignTokens::title_bar_height()) + .interactive(false) + .selected(true) + .show_flat( + ui, + list_item::CustomContent::new(|ui, context| { + ui.allocate_new_ui( + egui::UiBuilder::new() + .max_rect(context.rect) + .layout(egui::Layout::left_to_right(egui::Align::Center)), + |ui| { + item_heading_contents(ctx, viewport, ui, item); + }, + ); + }), + ); +} + +fn item_heading_contents( + ctx: &ViewerContext<'_>, + viewport: &ViewportBlueprint, + ui: &mut egui::Ui, + item: &Item, +) { + let item_title = ItemTitle::from_item(ctx, viewport, ui.style(), item); + + let ItemTitle { + name, + tooltip, + icon, + label_style, + } = item_title; + + let response = ui.add(egui::Button::image_and_text(icon.as_image(), name)); + + if response.clicked() { + // If the user has multiple things selected but only wants to have one thing selected, + // this is how they can do it. + ctx.command_sender + .send_system(re_viewer_context::SystemCommand::SetSelection(item.clone())); + } + + if let Some(tooltip) = tooltip { + response.on_hover_text(tooltip); + } +} diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 2734c76f41d9..7e4dae08b710 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -1,5 +1,5 @@ use re_data_ui::item_ui::{guess_instance_path_icon, guess_query_and_db_for_selected_entity}; -use re_ui::{icons, list_item, SyntaxHighlighting as _}; +use re_ui::{icons, SyntaxHighlighting as _}; use re_viewer_context::{contents_name_style, Item, ViewerContext}; use re_viewport_blueprint::ViewportBlueprint; diff --git a/crates/viewer/re_selection_panel/src/lib.rs b/crates/viewer/re_selection_panel/src/lib.rs index 4517a2783bb3..f41e7d1ba57f 100644 --- a/crates/viewer/re_selection_panel/src/lib.rs +++ b/crates/viewer/re_selection_panel/src/lib.rs @@ -1,6 +1,7 @@ //! The UI for the selection panel. mod defaults_ui; +mod item_heading; mod item_title; mod selection_history_ui; mod selection_panel; diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 2b88a1ef146e..edbeb04df581 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -9,22 +9,25 @@ use re_types::blueprint::components::Interactive; use re_ui::{ icons, list_item::{self, PropertyContent}, - ContextExt as _, DesignTokens, UiExt, + ContextExt as _, UiExt, }; use re_viewer_context::{ contents_name_style, icon_for_container_kind, ContainerId, Contents, DataQueryResult, - DataResult, HoverHighlight, Item, SpaceViewId, SystemCommandSender as _, UiLayout, ViewContext, - ViewStates, ViewerContext, + DataResult, HoverHighlight, Item, SpaceViewId, UiLayout, ViewContext, ViewStates, + ViewerContext, }; use re_viewport_blueprint::{ui::show_add_space_view_or_container_modal, ViewportBlueprint}; -use crate::{defaults_ui::view_components_defaults_section_ui, visualizer_ui::visualizer_ui}; +use crate::space_view_entity_picker::SpaceViewEntityPicker; +use crate::{ + defaults_ui::view_components_defaults_section_ui, item_heading::item_heading, + visualizer_ui::visualizer_ui, +}; use crate::{ selection_history_ui::SelectionHistoryUi, visible_time_range_ui::visible_time_range_ui_for_data_result, visible_time_range_ui::visible_time_range_ui_for_view, }; -use crate::{space_view_entity_picker::SpaceViewEntityPicker, ItemTitle}; // --- fn default_selection_panel_width(screen_width: f32) -> f32 { @@ -436,46 +439,6 @@ The last rule matching `/world/house` is `+ /world/**`, so it is included. } } -/// We show this above each item section -fn item_heading( - ctx: &ViewerContext<'_>, - viewport: &ViewportBlueprint, - ui: &mut egui::Ui, - item: &Item, -) { - let item_title = ItemTitle::from_item(ctx, viewport, ui.style(), item); - - let ItemTitle { - name, - tooltip, - icon, - label_style, - } = item_title; - - let mut content = list_item::LabelContent::new(name).with_icon(icon); - - if let Some(label_style) = label_style { - content = content.label_style(label_style); - } - - let response = ui - .list_item() - .with_height(DesignTokens::title_bar_height()) - .selected(true) - .show_flat(ui, content); - - if response.clicked() { - // If the user has multiple things selected but only wants to have one thing selected, - // this is how they can do it. - ctx.command_sender - .send_system(re_viewer_context::SystemCommand::SetSelection(item.clone())); - } - - if let Some(tooltip) = tooltip { - response.on_hover_text(tooltip); - } -} - fn entity_selection_ui( ctx: &ViewerContext<'_>, ui: &mut egui::Ui, From 7e8886abcf07d6eb7f52fcd57c18e7cf53b572f7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 10:29:15 +0100 Subject: [PATCH 06/39] Better titles on hover --- crates/viewer/re_selection_panel/src/item_title.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 7e4dae08b710..11000467ebc3 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -101,7 +101,7 @@ impl ItemTitle { container_blueprint.container_kind, ) } else { - format!("Unnamed {:?} container", container_blueprint.container_kind,) + format!("{:?} container", container_blueprint.container_kind,) }; let container_name = container_blueprint.display_name_or_default(); @@ -128,12 +128,12 @@ impl ItemTitle { let hover_text = if let Some(display_name) = view.display_name.as_ref() { format!( - "Space view {:?} of type {}", + "View {:?} of type {}", display_name, view_class.display_name() ) } else { - format!("Unnamed view of type {}", view_class.display_name()) + format!("View of type {}", view_class.display_name()) }; let view_name = view.display_name_or_default(); From 482d77b453d161bb84390eb65aa7417a70f7d2d8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 10:30:35 +0100 Subject: [PATCH 07/39] Add breadcrumbs for containers and views --- .../re_selection_panel/src/item_heading.rs | 78 +++++++++++++++--- .../data/icons/breadcrumbs_separator.png | Bin 0 -> 223 bytes crates/viewer/re_ui/src/icons.rs | 3 + crates/viewer/re_viewer_context/src/item.rs | 12 ++- 4 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 crates/viewer/re_ui/data/icons/breadcrumbs_separator.png diff --git a/crates/viewer/re_selection_panel/src/item_heading.rs b/crates/viewer/re_selection_panel/src/item_heading.rs index 2af079e9fdff..89d1acdf3eab 100644 --- a/crates/viewer/re_selection_panel/src/item_heading.rs +++ b/crates/viewer/re_selection_panel/src/item_heading.rs @@ -9,12 +9,15 @@ //! The item is an icon and a name. //! Each bread-crumb is clickable, as is the last item. -use re_ui::{list_item, DesignTokens, UiExt as _}; -use re_viewer_context::{Item, SystemCommandSender as _, ViewerContext}; +use re_data_ui::item_ui::cursor_interact_with_selectable; +use re_ui::{icons, list_item, DesignTokens, UiExt as _}; +use re_viewer_context::{Contents, Item, ViewerContext}; use re_viewport_blueprint::ViewportBlueprint; use crate::ItemTitle; +const ICON_SCALE: f32 = 0.5; // Because we save all icons as 2x + /// We show this above each item section pub fn item_heading( ctx: &ViewerContext<'_>, @@ -47,25 +50,78 @@ fn item_heading_contents( ui: &mut egui::Ui, item: &Item, ) { - let item_title = ItemTitle::from_item(ctx, viewport, ui.style(), item); + match item { + Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => { + // TODO(emilk): maybe some of these could have + } + Item::InstancePath(_) => { + // TODO: bread-crumbs of the entity path + } + Item::ComponentPath(component_path) => { + // TODO: bread-crumbs of the entity path + } + Item::Container(container_id) => { + if let Some(parent) = viewport.parent(&Contents::Container(*container_id)) { + viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); + } + } + Item::SpaceView(view_id) => { + if let Some(parent) = viewport.parent(&Contents::SpaceView(*view_id)) { + viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); + } + } + Item::DataResult(view_id, _) => { + viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); + } + } let ItemTitle { name, tooltip, icon, label_style, - } = item_title; + } = ItemTitle::from_item(ctx, viewport, ui.style(), item); + + let mut response = ui.add(egui::Button::image_and_text( + icon.as_image().fit_to_original_size(ICON_SCALE), + name, + )); + if let Some(tooltip) = tooltip { + response = response.on_hover_text(tooltip); + } + cursor_interact_with_selectable(ctx, response, item.clone()); +} - let response = ui.add(egui::Button::image_and_text(icon.as_image(), name)); +fn viewport_breadcrumbs( + ctx: &ViewerContext<'_>, + viewport: &ViewportBlueprint, + ui: &mut egui::Ui, + contents: Contents, +) { + let item = Item::from(contents); - if response.clicked() { - // If the user has multiple things selected but only wants to have one thing selected, - // this is how they can do it. - ctx.command_sender - .send_system(re_viewer_context::SystemCommand::SetSelection(item.clone())); + if let Some(parent) = viewport.parent(&contents) { + viewport_breadcrumbs(ctx, viewport, ui, parent.into()); } + let ItemTitle { + name: _, // ignored: we just show the icon for breadcrumbs + tooltip, + icon, + label_style: _, // no label + } = ItemTitle::from_item(ctx, viewport, ui.style(), &item); + + let mut response = ui.add(egui::Button::image( + icon.as_image().fit_to_original_size(ICON_SCALE), + )); if let Some(tooltip) = tooltip { - response.on_hover_text(tooltip); + response = response.on_hover_text(tooltip); } + cursor_interact_with_selectable(ctx, response, item); + + ui.add( + icons::BREADCRUMBS_SEPARATOR + .as_image() + .fit_to_original_size(ICON_SCALE), + ); } diff --git a/crates/viewer/re_ui/data/icons/breadcrumbs_separator.png b/crates/viewer/re_ui/data/icons/breadcrumbs_separator.png new file mode 100644 index 0000000000000000000000000000000000000000..7f265a572dd6cc6da72482eba5cc495acff13fb2 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^+(0bM!3HGVG{0W}Qk(@Ik;M!QVyYm_=ozH)0Vv2= z9OUlAumdKI;Vst00wkTcK`qY literal 0 HcmV?d00001 diff --git a/crates/viewer/re_ui/src/icons.rs b/crates/viewer/re_ui/src/icons.rs index 7b5c2770f5c7..8ec9dcdab2fc 100644 --- a/crates/viewer/re_ui/src/icons.rs +++ b/crates/viewer/re_ui/src/icons.rs @@ -118,3 +118,6 @@ pub const BLUEPRINT: Icon = icon_from_path!("../data/icons/blueprint.png"); pub const GITHUB: Icon = icon_from_path!("../data/icons/github.png"); pub const VIDEO_ERROR: Icon = icon_from_path!("../data/icons/video_error.png"); + +/// `>` +pub const BREADCRUMBS_SEPARATOR: Icon = icon_from_path!("../data/icons/breadcrumbs_separator.png"); diff --git a/crates/viewer/re_viewer_context/src/item.rs b/crates/viewer/re_viewer_context/src/item.rs index ddda0c537fbb..cc9c7297e3ec 100644 --- a/crates/viewer/re_viewer_context/src/item.rs +++ b/crates/viewer/re_viewer_context/src/item.rs @@ -1,7 +1,7 @@ use re_entity_db::{EntityDb, InstancePath}; use re_log_types::{ComponentPath, DataPath, EntityPath}; -use crate::{ContainerId, SpaceViewId}; +use crate::{ContainerId, Contents, SpaceViewId}; /// One "thing" in the UI. /// @@ -79,6 +79,16 @@ impl From for Item { } } +impl From for Item { + #[inline] + fn from(contents: Contents) -> Self { + match contents { + Contents::Container(container_id) => Self::Container(container_id), + Contents::SpaceView(space_view_id) => Self::SpaceView(space_view_id), + } + } +} + impl std::str::FromStr for Item { type Err = re_log_types::PathParseError; From 18a69838b2c92e461c7022a613d7a44c40a20905 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 10:31:36 +0100 Subject: [PATCH 08/39] Better file naming --- .../{item_heading.rs => item_heading_with_breadcrumbs.rs} | 2 +- crates/viewer/re_selection_panel/src/lib.rs | 2 +- crates/viewer/re_selection_panel/src/selection_panel.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename crates/viewer/re_selection_panel/src/{item_heading.rs => item_heading_with_breadcrumbs.rs} (99%) diff --git a/crates/viewer/re_selection_panel/src/item_heading.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs similarity index 99% rename from crates/viewer/re_selection_panel/src/item_heading.rs rename to crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index 89d1acdf3eab..df4fa7dd8926 100644 --- a/crates/viewer/re_selection_panel/src/item_heading.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -19,7 +19,7 @@ use crate::ItemTitle; const ICON_SCALE: f32 = 0.5; // Because we save all icons as 2x /// We show this above each item section -pub fn item_heading( +pub fn item_heading_with_breadcrumbs( ctx: &ViewerContext<'_>, viewport: &ViewportBlueprint, ui: &mut egui::Ui, diff --git a/crates/viewer/re_selection_panel/src/lib.rs b/crates/viewer/re_selection_panel/src/lib.rs index f41e7d1ba57f..43f33a6fa0eb 100644 --- a/crates/viewer/re_selection_panel/src/lib.rs +++ b/crates/viewer/re_selection_panel/src/lib.rs @@ -1,7 +1,7 @@ //! The UI for the selection panel. mod defaults_ui; -mod item_heading; +mod item_heading_with_breadcrumbs; mod item_title; mod selection_history_ui; mod selection_panel; diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index edbeb04df581..0c384aa9ca9d 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -20,8 +20,8 @@ use re_viewport_blueprint::{ui::show_add_space_view_or_container_modal, Viewport use crate::space_view_entity_picker::SpaceViewEntityPicker; use crate::{ - defaults_ui::view_components_defaults_section_ui, item_heading::item_heading, - visualizer_ui::visualizer_ui, + defaults_ui::view_components_defaults_section_ui, + item_heading_with_breadcrumbs::item_heading_with_breadcrumbs, visualizer_ui::visualizer_ui, }; use crate::{ selection_history_ui::SelectionHistoryUi, @@ -127,7 +127,7 @@ impl SelectionPanel { }; for (i, item) in selection.iter_items().enumerate() { list_item::list_item_scope(ui, item, |ui| { - item_heading(ctx, viewport, ui, item); + item_heading_with_breadcrumbs(ctx, viewport, ui, item); self.item_ui(ctx, viewport, view_states, ui, item, ui_layout); }); From f593c82ac6b22da9737ec1d9e5442e52eec01c9b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 11:27:02 +0100 Subject: [PATCH 09/39] Breadcrumbs for component paths --- .../src/item_heading_with_breadcrumbs.rs | 64 +++++++++++++++--- .../re_selection_panel/src/item_title.rs | 6 +- ...ng => breadcrumbs_separator_blueprint.png} | Bin .../icons/breadcrumbs_separator_entity.png | Bin 0 -> 330 bytes crates/viewer/re_ui/src/icons.rs | 6 +- 5 files changed, 62 insertions(+), 14 deletions(-) rename crates/viewer/re_ui/data/icons/{breadcrumbs_separator.png => breadcrumbs_separator_blueprint.png} (100%) create mode 100644 crates/viewer/re_ui/data/icons/breadcrumbs_separator_entity.png diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index df4fa7dd8926..b90bb73c325d 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -9,7 +9,10 @@ //! The item is an icon and a name. //! Each bread-crumb is clickable, as is the last item. +use re_chunk::EntityPath; use re_data_ui::item_ui::cursor_interact_with_selectable; +use re_entity_db::InstancePath; +use re_log_types::EntityPathPart; use re_ui::{icons, list_item, DesignTokens, UiExt as _}; use re_viewer_context::{Contents, Item, ViewerContext}; use re_viewport_blueprint::ViewportBlueprint; @@ -52,13 +55,13 @@ fn item_heading_contents( ) { match item { Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => { - // TODO(emilk): maybe some of these could have + // TODO(emilk): maybe some of these could have breadcrumbs } - Item::InstancePath(_) => { + Item::InstancePath(instance_path) => { // TODO: bread-crumbs of the entity path } Item::ComponentPath(component_path) => { - // TODO: bread-crumbs of the entity path + entity_path_breadcrumbs(ctx, ui, component_path.entity_path.as_slice()); } Item::Container(container_id) => { if let Some(parent) = viewport.parent(&Contents::Container(*container_id)) { @@ -71,20 +74,27 @@ fn item_heading_contents( } } Item::DataResult(view_id, _) => { - viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); + if let Some(view) = viewport.view(view_id) { + let query_result = ctx.lookup_query_result(*view_id); + let result_tree = &query_result.tree; + let root_node = result_tree.root_node(); + // let origin = + // DataResultNodeOrPath::from_path_lookup(result_tree, &view.space_origin); + viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); + } } } let ItemTitle { - name, - tooltip, icon, + label, label_style, + tooltip, } = ItemTitle::from_item(ctx, viewport, ui.style(), item); let mut response = ui.add(egui::Button::image_and_text( icon.as_image().fit_to_original_size(ICON_SCALE), - name, + label, )); if let Some(tooltip) = tooltip { response = response.on_hover_text(tooltip); @@ -92,6 +102,39 @@ fn item_heading_contents( cursor_interact_with_selectable(ctx, response, item.clone()); } +fn entity_path_breadcrumbs( + ctx: &ViewerContext<'_>, + ui: &mut egui::Ui, + entity_parts: &[EntityPathPart], +) { + // Match on everything plus last + let button = match entity_parts { + [ancestry @ .., last] => { + // Recurse! + entity_path_breadcrumbs(ctx, ui, ancestry); + + let first_char = last.unescaped_str().chars().next().unwrap_or('?'); + egui::Button::new(first_char.to_string()) + } + _ => { + // Root + egui::Button::image(icons::RECORDING.as_image().fit_to_original_size(ICON_SCALE)) + } + }; + + let entity_path = EntityPath::new(entity_parts.to_vec()); + let response = ui.add(button).on_hover_text(entity_path.to_string()); + + let item = Item::from(entity_path); + cursor_interact_with_selectable(ctx, response, item); + + ui.add( + icons::BREADCRUMBS_SEPARATOR_ENTITY + .as_image() + .fit_to_original_size(ICON_SCALE), + ); +} + fn viewport_breadcrumbs( ctx: &ViewerContext<'_>, viewport: &ViewportBlueprint, @@ -101,14 +144,15 @@ fn viewport_breadcrumbs( let item = Item::from(contents); if let Some(parent) = viewport.parent(&contents) { + // Recurse! viewport_breadcrumbs(ctx, viewport, ui, parent.into()); } let ItemTitle { - name: _, // ignored: we just show the icon for breadcrumbs - tooltip, icon, + label: _, // ignored: we just show the icon for breadcrumbs label_style: _, // no label + tooltip, } = ItemTitle::from_item(ctx, viewport, ui.style(), &item); let mut response = ui.add(egui::Button::image( @@ -120,7 +164,7 @@ fn viewport_breadcrumbs( cursor_interact_with_selectable(ctx, response, item); ui.add( - icons::BREADCRUMBS_SEPARATOR + icons::BREADCRUMBS_SEPARATOR_BLUEPRINT .as_image() .fit_to_original_size(ICON_SCALE), ); diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 11000467ebc3..ede501745dcc 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -5,10 +5,10 @@ use re_viewport_blueprint::ViewportBlueprint; #[must_use] pub struct ItemTitle { - pub name: egui::WidgetText, - pub tooltip: Option, pub icon: &'static re_ui::Icon, + pub label: egui::WidgetText, pub label_style: Option, + pub tooltip: Option, } impl ItemTitle { @@ -173,7 +173,7 @@ impl ItemTitle { fn new(name: impl Into, icon: &'static re_ui::Icon) -> Self { Self { - name: name.into(), + label: name.into(), tooltip: None, icon, label_style: None, diff --git a/crates/viewer/re_ui/data/icons/breadcrumbs_separator.png b/crates/viewer/re_ui/data/icons/breadcrumbs_separator_blueprint.png similarity index 100% rename from crates/viewer/re_ui/data/icons/breadcrumbs_separator.png rename to crates/viewer/re_ui/data/icons/breadcrumbs_separator_blueprint.png diff --git a/crates/viewer/re_ui/data/icons/breadcrumbs_separator_entity.png b/crates/viewer/re_ui/data/icons/breadcrumbs_separator_entity.png new file mode 100644 index 0000000000000000000000000000000000000000..446868272a90e5b74355f6d080a14d781d9c69bb GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^96&6>!3HEZNY`WoDb50q$YKTtF;x&|^bAt@02E{_ z4sv&5Sa(k5C6L3C?&#~tz_78O`%fY(kbmFP#WAFUG54b3`6dGawg=y%e%QIYyYJ47 z`J(=#|1W!=nx3EA<4b)#zA-%<>h2dg+@~{r{}5ND+Oy{I+4X^;*{8o{9cvJgG)eDX zap2zXPLZcS|FthT@LxE0V#1+oJKLEg)h~27nXyWgau*#7ZJhl5vfCMsikxYB8UfQ{ zZG1ldyvBUz>-N|`93FpX7qhYUrQ0hXyCU!?>uE{%te_fSnY!Tp2LiTEn0unH`FzD^ z87UdPeY&TKCA4u;-rQiA~~4MVp;h{-5@h ZZ5vb2@`6j<4M49lc)I$ztaD0e0suvLgjoOp literal 0 HcmV?d00001 diff --git a/crates/viewer/re_ui/src/icons.rs b/crates/viewer/re_ui/src/icons.rs index 8ec9dcdab2fc..b08f5c339360 100644 --- a/crates/viewer/re_ui/src/icons.rs +++ b/crates/viewer/re_ui/src/icons.rs @@ -120,4 +120,8 @@ pub const GITHUB: Icon = icon_from_path!("../data/icons/github.png"); pub const VIDEO_ERROR: Icon = icon_from_path!("../data/icons/video_error.png"); /// `>` -pub const BREADCRUMBS_SEPARATOR: Icon = icon_from_path!("../data/icons/breadcrumbs_separator.png"); +pub const BREADCRUMBS_SEPARATOR_BLUEPRINT: Icon = + icon_from_path!("../data/icons/breadcrumbs_separator_blueprint.png"); +/// `/` +pub const BREADCRUMBS_SEPARATOR_ENTITY: Icon = + icon_from_path!("../data/icons/breadcrumbs_separator_entity.png"); From a925b3f4469977d7dc685a1e359f9839cacbe593 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 11:45:47 +0100 Subject: [PATCH 10/39] Add bread crumbs for component paths and instance paths from streams --- .../src/item_heading_with_breadcrumbs.rs | 16 +++++- .../re_selection_panel/src/item_title.rs | 22 +++++++- .../re_selection_panel/src/selection_panel.rs | 55 +++++++++---------- crates/viewer/re_ui/src/lib.rs | 5 +- .../viewer/re_ui/src/syntax_highlighting.rs | 16 ++++-- 5 files changed, 75 insertions(+), 39 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index b90bb73c325d..147e9c673894 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -40,6 +40,7 @@ pub fn item_heading_with_breadcrumbs( .max_rect(context.rect) .layout(egui::Layout::left_to_right(egui::Align::Center)), |ui| { + ui.spacing_mut().item_spacing.x = 4.0; item_heading_contents(ctx, viewport, ui, item); }, ); @@ -58,7 +59,20 @@ fn item_heading_contents( // TODO(emilk): maybe some of these could have breadcrumbs } Item::InstancePath(instance_path) => { - // TODO: bread-crumbs of the entity path + let InstancePath { + entity_path, + instance, + } = instance_path; + + if instance.is_all() { + // Entity path + if let [ancestry @ .., _] = entity_path.as_slice() { + entity_path_breadcrumbs(ctx, ui, ancestry); + } + } else { + // Instance path + entity_path_breadcrumbs(ctx, ui, entity_path.as_slice()); + } } Item::ComponentPath(component_path) => { entity_path_breadcrumbs(ctx, ui, component_path.entity_path.as_slice()); diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index ede501745dcc..48b35024b8be 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -1,5 +1,7 @@ +use re_chunk::EntityPath; use re_data_ui::item_ui::{guess_instance_path_icon, guess_query_and_db_for_selected_entity}; -use re_ui::{icons, SyntaxHighlighting as _}; +use re_entity_db::InstancePath; +use re_ui::{icons, syntax_highlighting::InstanceWithCarets, SyntaxHighlighting as _}; use re_viewer_context::{contents_name_style, Item, ViewerContext}; use re_viewport_blueprint::ViewportBlueprint; @@ -59,8 +61,24 @@ impl ItemTitle { } Item::InstancePath(instance_path) => { + let InstancePath { + entity_path, + instance, + } = instance_path; + let typ = item.kind(); - let name = instance_path.syntax_highlighted(style); + + let name = if instance.is_all() { + // Entity path + if let Some(last) = entity_path.last() { + last.syntax_highlighted(style) + } else { + EntityPath::root().syntax_highlighted(style) + } + } else { + // Instance path + InstanceWithCarets(*instance).syntax_highlighted(style) + }; Self::new(name, guess_instance_path_icon(ctx, instance_path)) .with_tooltip(format!("{typ} '{instance_path}'")) diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 0c384aa9ca9d..02b061f036dc 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -4,7 +4,7 @@ use egui_tiles::ContainerKind; use re_context_menu::{context_menu_ui_for_item, SelectionUpdateBehavior}; use re_data_ui::{item_ui, item_ui::guess_query_and_db_for_selected_entity, DataUi}; use re_entity_db::{EntityPath, InstancePath}; -use re_log_types::EntityPathFilter; +use re_log_types::{ComponentPath, EntityPathFilter}; use re_types::blueprint::components::Interactive; use re_ui::{ icons, @@ -149,8 +149,10 @@ impl SelectionPanel { ) { match item { Item::ComponentPath(component_path) => { - let entity_path = &component_path.entity_path; - let component_name = &component_path.component_name; + let ComponentPath { + entity_path, + component_name, + } = component_path; let (query, db) = guess_query_and_db_for_selected_entity(ctx, entity_path); let is_static = db @@ -158,6 +160,12 @@ impl SelectionPanel { .store() .entity_has_static_component(entity_path, component_name); + ui.list_item_flat_noninteractive(PropertyContent::new("Parent entity").value_fn( + |ui, _| { + item_ui::entity_path_parts_buttons(ctx, &query, db, ui, None, entity_path); + }, + )); + ui.list_item_flat_noninteractive( PropertyContent::new("Component type").value_text(if is_static { "Static" @@ -166,36 +174,25 @@ impl SelectionPanel { }), ); - ui.list_item_flat_noninteractive(PropertyContent::new("Parent entity").value_fn( - |ui, _| { - item_ui::entity_path_button(ctx, &query, db, ui, None, entity_path); - }, - )); - list_existing_data_blueprints(ctx, viewport, ui, &entity_path.clone().into()); } Item::InstancePath(instance_path) => { - let is_instance = !instance_path.instance.is_all(); - let parent = if is_instance { - Some(instance_path.entity_path.clone()) - } else { - instance_path.entity_path.parent() - }; - if let Some(parent) = parent { - if !parent.is_root() { - let (query, db) = - guess_query_and_db_for_selected_entity(ctx, &instance_path.entity_path); - - ui.list_item_flat_noninteractive(PropertyContent::new("Parent").value_fn( - |ui, _| { - item_ui::entity_path_parts_buttons( - ctx, &query, db, ui, None, &parent, - ); - }, - )); - } - } + let (query, db) = + guess_query_and_db_for_selected_entity(ctx, &instance_path.entity_path); + + ui.list_item_flat_noninteractive(PropertyContent::new("Entity path").value_fn( + |ui, _| { + item_ui::entity_path_parts_buttons( + ctx, + &query, + db, + ui, + None, + &instance_path.entity_path, + ); + }, + )); list_existing_data_blueprints(ctx, viewport, ui, instance_path); } diff --git a/crates/viewer/re_ui/src/lib.rs b/crates/viewer/re_ui/src/lib.rs index fd1d14c9ec93..a6e155863e2b 100644 --- a/crates/viewer/re_ui/src/lib.rs +++ b/crates/viewer/re_ui/src/lib.rs @@ -2,16 +2,15 @@ mod command; mod command_palette; -mod design_tokens; -mod syntax_highlighting; - mod context_ext; +mod design_tokens; pub mod drag_and_drop; pub mod icons; pub mod list_item; mod markdown_utils; pub mod modal; mod section_collapsing_header; +pub mod syntax_highlighting; pub mod toasts; mod ui_ext; pub mod zoom_pan_area; diff --git a/crates/viewer/re_ui/src/syntax_highlighting.rs b/crates/viewer/re_ui/src/syntax_highlighting.rs index f2b97d0cd266..678711923642 100644 --- a/crates/viewer/re_ui/src/syntax_highlighting.rs +++ b/crates/viewer/re_ui/src/syntax_highlighting.rs @@ -65,10 +65,18 @@ impl SyntaxHighlighting for EntityPath { impl SyntaxHighlighting for InstancePath { fn syntax_highlight_into(&self, style: &Style, job: &mut LayoutJob) { self.entity_path.syntax_highlight_into(style, job); - if !self.instance.is_all() { - job.append("[", 0.0, faint_text_format(style)); - self.instance.syntax_highlight_into(style, job); - job.append("]", 0.0, faint_text_format(style)); + if self.instance.is_specific() { + InstanceWithCarets(self.instance).syntax_highlight_into(style, job); } } } + +pub struct InstanceWithCarets(pub Instance); + +impl SyntaxHighlighting for InstanceWithCarets { + fn syntax_highlight_into(&self, style: &Style, job: &mut LayoutJob) { + job.append("[", 0.0, faint_text_format(style)); + self.0.syntax_highlight_into(style, job); + job.append("]", 0.0, faint_text_format(style)); + } +} From 5d79a967d597ba791eaccb2c4c31508eaa232675 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 11:50:16 +0100 Subject: [PATCH 11/39] Better parenting --- .../re_selection_panel/src/item_title.rs | 8 +--- .../re_selection_panel/src/selection_panel.rs | 42 +++++++------------ 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 48b35024b8be..812bfca4a0d7 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -145,13 +145,9 @@ impl ItemTitle { let view_class = view.class(ctx.space_view_class_registry); let hover_text = if let Some(display_name) = view.display_name.as_ref() { - format!( - "View {:?} of type {}", - display_name, - view_class.display_name() - ) + format!("{} view {display_name:?}", view_class.display_name(),) } else { - format!("View of type {}", view_class.display_name()) + format!("{} view", view_class.display_name()) }; let view_name = view.display_name_or_default(); diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 02b061f036dc..872b3b7d7b4e 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -210,33 +210,23 @@ impl SelectionPanel { Item::DataResult(view_id, instance_path) => { if let Some(view) = viewport.view(view_id) { - let is_instance = !instance_path.instance.is_all(); - let parent = if is_instance { - Some(instance_path.entity_path.clone()) - } else { - instance_path.entity_path.parent() - }; - if let Some(parent) = parent { - if !parent.is_root() { - ui.list_item_flat_noninteractive( - PropertyContent::new("Parent").value_fn(|ui, _| { - let (query, db) = guess_query_and_db_for_selected_entity( - ctx, - &instance_path.entity_path, - ); - - item_ui::entity_path_parts_buttons( - ctx, - &query, - db, - ui, - Some(*view_id), - &parent, - ); - }), + ui.list_item_flat_noninteractive( + PropertyContent::new("Stream entity").value_fn(|ui, _| { + let (query, db) = guess_query_and_db_for_selected_entity( + ctx, + &instance_path.entity_path, ); - } - } + + item_ui::entity_path_parts_buttons( + ctx, + &query, + db, + ui, + Some(*view_id), + &instance_path.entity_path, + ); + }), + ); ui.list_item_flat_noninteractive(PropertyContent::new("In view").value_fn( |ui, _| { From 5e4c2d33382f827fd9b808f867e664c2af8582dc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 11:59:30 +0100 Subject: [PATCH 12/39] Break up long functions --- .../store/re_entity_db/src/instance_path.rs | 9 + .../re_selection_panel/src/item_title.rs | 279 +++++++++--------- crates/viewer/re_viewer_context/src/item.rs | 8 +- 3 files changed, 157 insertions(+), 139 deletions(-) diff --git a/crates/store/re_entity_db/src/instance_path.rs b/crates/store/re_entity_db/src/instance_path.rs index c11e86676187..96145eb62bb8 100644 --- a/crates/store/re_entity_db/src/instance_path.rs +++ b/crates/store/re_entity_db/src/instance_path.rs @@ -72,6 +72,15 @@ impl InstancePath { instance: self.instance, } } + + /// Human-readable description of the kind + pub fn kind(&self) -> &'static str { + if self.instance.is_specific() { + "Entity instance" + } else { + "Entity" + } + } } impl std::fmt::Display for InstancePath { diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 812bfca4a0d7..7a0f2dc9e768 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -1,8 +1,9 @@ use re_chunk::EntityPath; use re_data_ui::item_ui::{guess_instance_path_icon, guess_query_and_db_for_selected_entity}; use re_entity_db::InstancePath; +use re_log_types::ComponentPath; use re_ui::{icons, syntax_highlighting::InstanceWithCarets, SyntaxHighlighting as _}; -use re_viewer_context::{contents_name_style, Item, ViewerContext}; +use re_viewer_context::{contents_name_style, ContainerId, Item, SpaceViewId, ViewerContext}; use re_viewport_blueprint::ViewportBlueprint; #[must_use] @@ -31,141 +32,17 @@ impl ItemTitle { Self::new(title, &icons::DATA_SOURCE) } - Item::StoreId(store_id) => { - let id_str = format!("{} ID: {}", store_id.kind, store_id); - - let title = if let Some(entity_db) = ctx.store_context.bundle.get(store_id) { - if let Some(info) = entity_db.store_info() { - let time = info - .started - .format_time_custom( - "[hour]:[minute]:[second]", - ctx.app_options.time_zone, - ) - .unwrap_or("".to_owned()); - - format!("{} - {}", info.application_id, time) - } else { - id_str.clone() - } - } else { - id_str.clone() - }; - - let icon = match store_id.kind { - re_log_types::StoreKind::Recording => &icons::RECORDING, - re_log_types::StoreKind::Blueprint => &icons::BLUEPRINT, - }; - - Self::new(title, icon).with_tooltip(id_str) - } + Item::StoreId(store_id) => Self::from_store_id(ctx, store_id), Item::InstancePath(instance_path) => { - let InstancePath { - entity_path, - instance, - } = instance_path; - - let typ = item.kind(); - - let name = if instance.is_all() { - // Entity path - if let Some(last) = entity_path.last() { - last.syntax_highlighted(style) - } else { - EntityPath::root().syntax_highlighted(style) - } - } else { - // Instance path - InstanceWithCarets(*instance).syntax_highlighted(style) - }; - - Self::new(name, guess_instance_path_icon(ctx, instance_path)) - .with_tooltip(format!("{typ} '{instance_path}'")) + Self::from_instance_path(ctx, style, instance_path) } - Item::ComponentPath(component_path) => { - let entity_path = &component_path.entity_path; - let component_name = &component_path.component_name; - - let (_query, db) = guess_query_and_db_for_selected_entity(ctx, entity_path); - let is_static = db - .storage_engine() - .store() - .entity_has_static_component(entity_path, component_name); - - Self::new( - component_name.short_name(), - if is_static { - &icons::COMPONENT_STATIC - } else { - &icons::COMPONENT_TEMPORAL - }, - ) - .with_tooltip(format!( - "{} component {} of entity '{}'", - if is_static { "Static" } else { "Temporal" }, - component_name.full_name(), - entity_path - )) - } + Item::ComponentPath(component_path) => Self::from_component_path(ctx, component_path), - Item::Container(container_id) => { - if let Some(container_blueprint) = viewport.container(container_id) { - let hover_text = - if let Some(display_name) = container_blueprint.display_name.as_ref() { - format!( - "{:?} container {display_name:?}", - container_blueprint.container_kind, - ) - } else { - format!("{:?} container", container_blueprint.container_kind,) - }; - - let container_name = container_blueprint.display_name_or_default(); - Self::new( - container_name.as_ref(), - re_viewer_context::icon_for_container_kind( - &container_blueprint.container_kind, - ), - ) - .with_label_style(contents_name_style(&container_name)) - .with_tooltip(hover_text) - } else { - Self::new( - format!("Unknown container {container_id}"), - &icons::SPACE_VIEW_UNKNOWN, - ) - .with_tooltip("Failed to find container in blueprint") - } - } + Item::Container(container_id) => Self::from_container_id(viewport, container_id), - Item::SpaceView(view_id) => { - if let Some(view) = viewport.view(view_id) { - let view_class = view.class(ctx.space_view_class_registry); - - let hover_text = if let Some(display_name) = view.display_name.as_ref() { - format!("{} view {display_name:?}", view_class.display_name(),) - } else { - format!("{} view", view_class.display_name()) - }; - - let view_name = view.display_name_or_default(); - - Self::new( - view_name.as_ref(), - view.class(ctx.space_view_class_registry).icon(), - ) - .with_label_style(contents_name_style(&view_name)) - .with_tooltip(hover_text) - } else { - Self::new( - format!("Unknown view {view_id}"), - &icons::SPACE_VIEW_UNKNOWN, - ) - .with_tooltip("Failed to find view in blueprint") - } - } + Item::SpaceView(view_id) => Self::from_view_id(ctx, viewport, view_id), Item::DataResult(view_id, instance_path) => { let name = instance_path.syntax_highlighted(style); @@ -173,9 +50,9 @@ impl ItemTitle { let item_title = Self::new(name, guess_instance_path_icon(ctx, instance_path)); if let Some(view) = viewport.view(view_id) { - let typ = item.kind(); + let kind = item.kind(); item_title.with_tooltip(format!( - "{typ} '{instance_path}' as shown in view {}", + "{kind} '{instance_path}' as shown in view {}", view.display_name_or_default() )) } else { @@ -185,6 +62,144 @@ impl ItemTitle { } } + pub fn from_store_id(ctx: &ViewerContext<'_>, store_id: &re_log_types::StoreId) -> Self { + let id_str = format!("{} ID: {}", store_id.kind, store_id); + + let title = if let Some(entity_db) = ctx.store_context.bundle.get(store_id) { + if let Some(info) = entity_db.store_info() { + let time = info + .started + .format_time_custom("[hour]:[minute]:[second]", ctx.app_options.time_zone) + .unwrap_or("".to_owned()); + + format!("{} - {}", info.application_id, time) + } else { + id_str.clone() + } + } else { + id_str.clone() + }; + + let icon = match store_id.kind { + re_log_types::StoreKind::Recording => &icons::RECORDING, + re_log_types::StoreKind::Blueprint => &icons::BLUEPRINT, + }; + + Self::new(title, icon).with_tooltip(id_str) + } + + pub fn from_instance_path( + ctx: &ViewerContext<'_>, + style: &egui::Style, + instance_path: &InstancePath, + ) -> Self { + let InstancePath { + entity_path, + instance, + } = instance_path; + + let kind = instance_path.kind(); + + let name = if instance.is_all() { + // Entity path + if let Some(last) = entity_path.last() { + last.syntax_highlighted(style) + } else { + EntityPath::root().syntax_highlighted(style) + } + } else { + // Instance path + InstanceWithCarets(*instance).syntax_highlighted(style) + }; + + Self::new(name, guess_instance_path_icon(ctx, instance_path)) + .with_tooltip(format!("{kind} '{instance_path}'")) + } + + pub fn from_component_path(ctx: &ViewerContext<'_>, component_path: &ComponentPath) -> Self { + let entity_path = &component_path.entity_path; + let component_name = &component_path.component_name; + + let (_query, db) = guess_query_and_db_for_selected_entity(ctx, entity_path); + let is_static = db + .storage_engine() + .store() + .entity_has_static_component(entity_path, component_name); + + Self::new( + component_name.short_name(), + if is_static { + &icons::COMPONENT_STATIC + } else { + &icons::COMPONENT_TEMPORAL + }, + ) + .with_tooltip(format!( + "{} component {} of entity '{}'", + if is_static { "Static" } else { "Temporal" }, + component_name.full_name(), + entity_path + )) + } + + pub fn from_container_id(viewport: &ViewportBlueprint, container_id: &ContainerId) -> Self { + if let Some(container_blueprint) = viewport.container(container_id) { + let hover_text = if let Some(display_name) = container_blueprint.display_name.as_ref() { + format!( + "{:?} container {display_name:?}", + container_blueprint.container_kind, + ) + } else { + format!("{:?} container", container_blueprint.container_kind,) + }; + + let container_name = container_blueprint.display_name_or_default(); + Self::new( + container_name.as_ref(), + re_viewer_context::icon_for_container_kind(&container_blueprint.container_kind), + ) + .with_label_style(contents_name_style(&container_name)) + .with_tooltip(hover_text) + } else { + Self::new( + format!("Unknown container {container_id}"), + &icons::SPACE_VIEW_UNKNOWN, + ) + .with_tooltip("Failed to find container in blueprint") + } + } + + fn from_view_id( + ctx: &ViewerContext<'_>, + viewport: &ViewportBlueprint, + view_id: &SpaceViewId, + ) -> Self { + if let Some(view) = viewport.view(view_id) { + let view_class = view.class(ctx.space_view_class_registry); + + let hover_text = if let Some(display_name) = view.display_name.as_ref() { + format!("{} view {display_name:?}", view_class.display_name(),) + } else { + format!("{} view", view_class.display_name()) + }; + + let view_name = view.display_name_or_default(); + + Self::new( + view_name.as_ref(), + view.class(ctx.space_view_class_registry).icon(), + ) + .with_label_style(contents_name_style(&view_name)) + .with_tooltip(hover_text) + } else { + Self::new( + format!("Unknown view {view_id}"), + &icons::SPACE_VIEW_UNKNOWN, + ) + .with_tooltip("Failed to find view in blueprint") + } + } + fn new(name: impl Into, icon: &'static re_ui::Icon) -> Self { Self { label: name.into(), diff --git a/crates/viewer/re_viewer_context/src/item.rs b/crates/viewer/re_viewer_context/src/item.rs index cc9c7297e3ec..3713bab4e8ab 100644 --- a/crates/viewer/re_viewer_context/src/item.rs +++ b/crates/viewer/re_viewer_context/src/item.rs @@ -143,13 +143,7 @@ impl Item { re_log_types::StoreKind::Recording => "Recording ID", re_log_types::StoreKind::Blueprint => "Blueprint ID", }, - Self::InstancePath(instance_path) => { - if instance_path.instance.is_specific() { - "Entity instance" - } else { - "Entity" - } - } + Self::InstancePath(instance_path) => instance_path.kind(), Self::ComponentPath(_) => "Entity component", Self::SpaceView(_) => "View", Self::Container(_) => "Container", From 509e058e133739ca0c6dc84caa339d23d636c558 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 12:06:56 +0100 Subject: [PATCH 13/39] Fix warnings --- .../src/item_heading_with_breadcrumbs.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index 147e9c673894..5b9e9aadc697 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -88,21 +88,22 @@ fn item_heading_contents( } } Item::DataResult(view_id, _) => { - if let Some(view) = viewport.view(view_id) { - let query_result = ctx.lookup_query_result(*view_id); - let result_tree = &query_result.tree; - let root_node = result_tree.root_node(); - // let origin = - // DataResultNodeOrPath::from_path_lookup(result_tree, &view.space_origin); - viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); - } + viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); + + // TODO(#4491): breadcrumbs for data results entity paths and projections + // if let Some(view) = viewport.view(view_id) { + // let query_result = ctx.lookup_query_result(*view_id); + // let result_tree = &query_result.tree; + // let root_node = result_tree.root_node(); + // let origin = + // DataResultNodeOrPath::from_path_lookup(result_tree, &view.space_origin); } } let ItemTitle { icon, label, - label_style, + label_style: _, // Intentionally ignored tooltip, } = ItemTitle::from_item(ctx, viewport, ui.style(), item); From 093f8f11fa8cb68409e90a23bf66a9a8230b0b9f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 12:11:51 +0100 Subject: [PATCH 14/39] More niceties --- crates/viewer/re_data_ui/src/item_ui.rs | 42 +++++++++++-------- .../re_selection_panel/src/selection_panel.rs | 20 +-------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/crates/viewer/re_data_ui/src/item_ui.rs b/crates/viewer/re_data_ui/src/item_ui.rs index 024f57baaf63..e1309873f61f 100644 --- a/crates/viewer/re_data_ui/src/item_ui.rs +++ b/crates/viewer/re_data_ui/src/item_ui.rs @@ -62,30 +62,36 @@ pub fn entity_path_parts_buttons( space_view_id: Option, entity_path: &EntityPath, ) -> egui::Response { - let with_icon = false; // too much noise with icons in a path + let with_individual_icons = false; // too much noise with icons in a path ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 2.0; - // Show one single icon up-front instead: - let instance_path = InstancePath::entity_all(entity_path.clone()); - ui.add(instance_path_icon(&query.timeline(), db, &instance_path).as_image()); - - let mut accumulated = Vec::new(); - for part in entity_path.iter() { - accumulated.push(part.clone()); + if !with_individual_icons { + // Show one single icon up-front instead: + let instance_path = InstancePath::entity_all(entity_path.clone()); + ui.add(instance_path_icon(&query.timeline(), db, &instance_path).as_image()); + } + if entity_path.is_root() { ui.strong("/"); - instance_path_button_to_ex( - ctx, - query, - db, - ui, - space_view_id, - &InstancePath::entity_all(accumulated.clone().into()), - part.syntax_highlighted(ui.style()), - with_icon, - ); + } else { + let mut accumulated = Vec::new(); + for part in entity_path.iter() { + accumulated.push(part.clone()); + + ui.strong("/"); + instance_path_button_to_ex( + ctx, + query, + db, + ui, + space_view_id, + &InstancePath::entity_all(accumulated.clone().into()), + part.syntax_highlighted(ui.style()), + with_individual_icons, + ); + } } }) .response diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 872b3b7d7b4e..e323dba2076e 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -222,7 +222,7 @@ impl SelectionPanel { &query, db, ui, - Some(*view_id), + None, &instance_path.entity_path, ); }), @@ -236,24 +236,6 @@ impl SelectionPanel { } if instance_path.is_all() { - ui.list_item_flat_noninteractive(PropertyContent::new("Entity").value_fn( - |ui, _| { - let (query, db) = guess_query_and_db_for_selected_entity( - ctx, - &instance_path.entity_path, - ); - - item_ui::entity_path_button( - ctx, - &query, - db, - ui, - None, - &instance_path.entity_path, - ); - }, - )); - let entity_path = &instance_path.entity_path; let query_result = ctx.lookup_query_result(*view_id); let data_result = query_result From 671906b1d0961691915a2c92119a65bdb4809b43 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 15:26:31 +0100 Subject: [PATCH 15/39] Dimmer breadcrumb colors --- Cargo.lock | 22 +-- Cargo.toml | 14 +- crates/viewer/re_data_ui/src/item_ui.rs | 9 +- .../src/item_heading_with_breadcrumbs.rs | 130 ++++++++++-------- .../icons/breadcrumbs_separator_blueprint.png | Bin 223 -> 168 bytes .../icons/breadcrumbs_separator_entity.png | Bin 330 -> 343 bytes 6 files changed, 101 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b071cb0aacb1..cfae455849b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1923,7 +1923,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecolor" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "bytemuck", "color-hex", @@ -1940,7 +1940,7 @@ checksum = "18aade80d5e09429040243ce1143ddc08a92d7a22820ac512610410a4dd5214f" [[package]] name = "eframe" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "ahash", "bytemuck", @@ -1979,7 +1979,7 @@ dependencies = [ [[package]] name = "egui" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "accesskit", "ahash", @@ -1996,7 +1996,7 @@ dependencies = [ [[package]] name = "egui-wgpu" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "ahash", "bytemuck", @@ -2015,7 +2015,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "accesskit_winit", "ahash", @@ -2057,7 +2057,7 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "ahash", "egui", @@ -2074,7 +2074,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "ahash", "bytemuck", @@ -2092,7 +2092,7 @@ dependencies = [ [[package]] name = "egui_kittest" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "dify", "egui", @@ -2161,7 +2161,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "bytemuck", "serde", @@ -2277,7 +2277,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" dependencies = [ "ab_glyph", "ahash", @@ -2296,7 +2296,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" version = "0.29.1" -source = "git+https://github.com/emilk/egui.git?rev=c5ac7d301a90afbaec843ee04fb43d8a0956cc90#c5ac7d301a90afbaec843ee04fb43d8a0956cc90" +source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550" [[package]] name = "equivalent" diff --git a/Cargo.toml b/Cargo.toml index 3a8b3275fd06..618d697e49cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -559,13 +559,13 @@ significant_drop_tightening = "allow" # An update of parking_lot made this trigg # As a last resport, patch with a commit to our own repository. # ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk. -ecolor = { git = "https://github.com/emilk/egui.git", rev = "c5ac7d301a90afbaec843ee04fb43d8a0956cc90" } # egui master 2024-12-03 -eframe = { git = "https://github.com/emilk/egui.git", rev = "c5ac7d301a90afbaec843ee04fb43d8a0956cc90" } # egui master 2024-12-03 -egui = { git = "https://github.com/emilk/egui.git", rev = "c5ac7d301a90afbaec843ee04fb43d8a0956cc90" } # egui master 2024-12-03 -egui_extras = { git = "https://github.com/emilk/egui.git", rev = "c5ac7d301a90afbaec843ee04fb43d8a0956cc90" } # egui master 2024-12-03 -egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "c5ac7d301a90afbaec843ee04fb43d8a0956cc90" } # egui master 2024-12-03 -egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "c5ac7d301a90afbaec843ee04fb43d8a0956cc90" } # egui master 2024-12-03 -emath = { git = "https://github.com/emilk/egui.git", rev = "c5ac7d301a90afbaec843ee04fb43d8a0956cc90" } # egui master 2024-12-03 +ecolor = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04 +eframe = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04 +egui = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04 +egui_extras = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04 +egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04 +egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04 +emath = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04 # Useful while developing: # ecolor = { path = "../../egui/crates/ecolor" } diff --git a/crates/viewer/re_data_ui/src/item_ui.rs b/crates/viewer/re_data_ui/src/item_ui.rs index e1309873f61f..0bafd68873a2 100644 --- a/crates/viewer/re_data_ui/src/item_ui.rs +++ b/crates/viewer/re_data_ui/src/item_ui.rs @@ -65,7 +65,14 @@ pub fn entity_path_parts_buttons( let with_individual_icons = false; // too much noise with icons in a path ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 2.0; + { + ui.spacing_mut().item_spacing.x = 2.0; + + // The last part points to the selected entity, but that's ugly, so remove the highlight: + let visuals = ui.visuals_mut(); + visuals.selection.bg_fill = egui::Color32::TRANSPARENT; + visuals.selection.stroke = visuals.widgets.inactive.fg_stroke; + } if !with_individual_icons { // Show one single icon up-front instead: diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index 5b9e9aadc697..da7ecc2d763a 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -54,51 +54,69 @@ fn item_heading_contents( ui: &mut egui::Ui, item: &Item, ) { - match item { - Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => { - // TODO(emilk): maybe some of these could have breadcrumbs + { + // No background rectangles, even for hovered items + let visuals = ui.visuals_mut(); + visuals.widgets.active.bg_fill = egui::Color32::TRANSPARENT; + visuals.widgets.active.weak_bg_fill = egui::Color32::TRANSPARENT; + visuals.widgets.hovered.bg_fill = egui::Color32::TRANSPARENT; + visuals.widgets.hovered.weak_bg_fill = egui::Color32::TRANSPARENT; + } + + ui.scope(|ui| { + // Breadcrumbs + { + // Dimmer colors for breadcrumbs + let visuals = ui.visuals_mut(); + visuals.widgets.inactive.fg_stroke.color = visuals.text_color(); } - Item::InstancePath(instance_path) => { - let InstancePath { - entity_path, - instance, - } = instance_path; - - if instance.is_all() { - // Entity path - if let [ancestry @ .., _] = entity_path.as_slice() { - entity_path_breadcrumbs(ctx, ui, ancestry); + + match item { + Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => { + // TODO(emilk): maybe some of these could have breadcrumbs + } + Item::InstancePath(instance_path) => { + let InstancePath { + entity_path, + instance, + } = instance_path; + + if instance.is_all() { + // Entity path + if let [ancestry @ .., _] = entity_path.as_slice() { + entity_path_breadcrumbs(ctx, ui, ancestry); + } + } else { + // Instance path + entity_path_breadcrumbs(ctx, ui, entity_path.as_slice()); } - } else { - // Instance path - entity_path_breadcrumbs(ctx, ui, entity_path.as_slice()); } - } - Item::ComponentPath(component_path) => { - entity_path_breadcrumbs(ctx, ui, component_path.entity_path.as_slice()); - } - Item::Container(container_id) => { - if let Some(parent) = viewport.parent(&Contents::Container(*container_id)) { - viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); + Item::ComponentPath(component_path) => { + entity_path_breadcrumbs(ctx, ui, component_path.entity_path.as_slice()); } - } - Item::SpaceView(view_id) => { - if let Some(parent) = viewport.parent(&Contents::SpaceView(*view_id)) { - viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); + Item::Container(container_id) => { + if let Some(parent) = viewport.parent(&Contents::Container(*container_id)) { + viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); + } + } + Item::SpaceView(view_id) => { + if let Some(parent) = viewport.parent(&Contents::SpaceView(*view_id)) { + viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); + } + } + Item::DataResult(view_id, _) => { + viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); + + // TODO(#4491): breadcrumbs for data results entity paths and projections + // if let Some(view) = viewport.view(view_id) { + // let query_result = ctx.lookup_query_result(*view_id); + // let result_tree = &query_result.tree; + // let root_node = result_tree.root_node(); + // let origin = + // DataResultNodeOrPath::from_path_lookup(result_tree, &view.space_origin); } } - Item::DataResult(view_id, _) => { - viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); - - // TODO(#4491): breadcrumbs for data results entity paths and projections - // if let Some(view) = viewport.view(view_id) { - // let query_result = ctx.lookup_query_result(*view_id); - // let result_tree = &query_result.tree; - // let root_node = result_tree.root_node(); - // let origin = - // DataResultNodeOrPath::from_path_lookup(result_tree, &view.space_origin); - } - } + }); let ItemTitle { icon, @@ -107,10 +125,10 @@ fn item_heading_contents( tooltip, } = ItemTitle::from_item(ctx, viewport, ui.style(), item); - let mut response = ui.add(egui::Button::image_and_text( - icon.as_image().fit_to_original_size(ICON_SCALE), - label, - )); + let mut response = ui.add( + egui::Button::image_and_text(icon.as_image().fit_to_original_size(ICON_SCALE), label) + .image_tint_follows_text_color(true), + ); if let Some(tooltip) = tooltip { response = response.on_hover_text(tooltip); } @@ -129,11 +147,12 @@ fn entity_path_breadcrumbs( entity_path_breadcrumbs(ctx, ui, ancestry); let first_char = last.unescaped_str().chars().next().unwrap_or('?'); - egui::Button::new(first_char.to_string()) + egui::Button::new(first_char.to_string()).image_tint_follows_text_color(true) } _ => { // Root egui::Button::image(icons::RECORDING.as_image().fit_to_original_size(ICON_SCALE)) + .image_tint_follows_text_color(true) } }; @@ -143,11 +162,7 @@ fn entity_path_breadcrumbs( let item = Item::from(entity_path); cursor_interact_with_selectable(ctx, response, item); - ui.add( - icons::BREADCRUMBS_SEPARATOR_ENTITY - .as_image() - .fit_to_original_size(ICON_SCALE), - ); + separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR_ENTITY); } fn viewport_breadcrumbs( @@ -170,17 +185,22 @@ fn viewport_breadcrumbs( tooltip, } = ItemTitle::from_item(ctx, viewport, ui.style(), &item); - let mut response = ui.add(egui::Button::image( - icon.as_image().fit_to_original_size(ICON_SCALE), - )); + let mut response = ui.add( + egui::Button::image(icon.as_image().fit_to_original_size(ICON_SCALE)) + .image_tint_follows_text_color(true), + ); if let Some(tooltip) = tooltip { response = response.on_hover_text(tooltip); } cursor_interact_with_selectable(ctx, response, item); + separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR_BLUEPRINT); +} + +fn separator_icon_ui(ui: &mut egui::Ui, icon: re_ui::Icon) { ui.add( - icons::BREADCRUMBS_SEPARATOR_BLUEPRINT - .as_image() - .fit_to_original_size(ICON_SCALE), + icon.as_image() + .fit_to_original_size(ICON_SCALE) + .tint(ui.visuals().text_color()), ); } diff --git a/crates/viewer/re_ui/data/icons/breadcrumbs_separator_blueprint.png b/crates/viewer/re_ui/data/icons/breadcrumbs_separator_blueprint.png index 7f265a572dd6cc6da72482eba5cc495acff13fb2..fac47a6b5458c82dd05dd1550592d088942a43bd 100644 GIT binary patch delta 89 zcmV-f0H*)n0jL3xR#H7lL_t(|0qv484geqs1JMu558&rjBdek8G$AZ^ohnidfOXDT vSn)y&-a)fcqB%S&giWwO&U|B4`_ms`28{{ z!08ZCo2zH8sQaD;l`n{RPk2ed`{7~<2E!<*dXok9Uq``$qrk*o;9@H%a4;A>l#j$~ z@F}ehtB=Z2wpJ@(1AHlN#015yANkvXXu0mjfd+$HS diff --git a/crates/viewer/re_ui/data/icons/breadcrumbs_separator_entity.png b/crates/viewer/re_ui/data/icons/breadcrumbs_separator_entity.png index 446868272a90e5b74355f6d080a14d781d9c69bb..862b0db9faf26810bb4358ce646c37b4e776d727 100644 GIT binary patch delta 266 zcmV+l0rmdM0@nhNR)6eCL_t(|0d0{n4#F@DMePIxBiw*8fY@MRr8j^Z05^milnJQ} zl&KOzN^d|{K#fV-P};hdk~qoBcWfiHSQtNcfMTnGj-m|!lv^|Vh$6w}-Bqc<=S5HJ zcY8Qw3fVjJof|AXFJ*uxbO7D#%m4&j^KS>-2Zay;1H!wSn`LB1vYdE4gx25}X<$%`T5;9|38z-Td>~ionhG-D5-Z>B zqs7ci7(l2uPNgPa0?qQ)0NY*yuqi^P|F2*PG~3$*IvQmHI@^Pk?6tKo?*2`vkltCf Q00000NkvXXt^-0~f(8|I@&Et; delta 252 zcmV1?{ z(vFNxT#N`ON6`pJmjd_jUV0^rtmDnEQB`Zp_G-p}6d5#Ui>Scf`-v3g`TmEX!2c6% zkzm5sxrYK7C(wvOHU$`U3v$L)fs^;sM$8C!Y?dx4P?lXhOm*@3)&t!2w_W@QNd230 z1_h31KO@G}5aMd(bc>o&d`%pFQ@_AawUC|2j)Tv5^Bfu+F1{_w^ft~#1uP-OH}l6} znH1k2Zx6`z%6R1^PEaT?yxt+mvKJz9vqP!>mh}d<0#c`N(u;rq0000 Date: Wed, 4 Dec 2024 15:56:31 +0100 Subject: [PATCH 16/39] Move `DataResultNodeOrPath` into its own file --- .../re_blueprint_tree/src/blueprint_tree.rs | 32 +----------------- .../src/data_result_node_or_path.rs | 33 +++++++++++++++++++ crates/viewer/re_viewer_context/src/lib.rs | 2 ++ 3 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 crates/viewer/re_viewer_context/src/data_result_node_or_path.rs diff --git a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs index c9a2a6798e59..435a7bb4fcb5 100644 --- a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs +++ b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs @@ -9,7 +9,7 @@ use re_log_types::EntityPath; use re_types::blueprint::components::Visible; use re_ui::{drag_and_drop::DropTarget, list_item, ContextExt as _, DesignTokens, UiExt as _}; use re_viewer_context::{ - contents_name_style, icon_for_container_kind, CollapseScope, Contents, DataResultTree, + contents_name_style, icon_for_container_kind, CollapseScope, Contents, DataResultNodeOrPath, SystemCommandSender, }; use re_viewer_context::{ @@ -17,36 +17,6 @@ use re_viewer_context::{ }; use re_viewport_blueprint::ui::show_add_space_view_or_container_modal; use re_viewport_blueprint::{SpaceViewBlueprint, ViewportBlueprint}; - -enum DataResultNodeOrPath<'a> { - Path(&'a EntityPath), - DataResultNode(&'a DataResultNode), -} - -impl<'a> DataResultNodeOrPath<'a> { - fn from_path_lookup(result_tree: &'a DataResultTree, path: &'a EntityPath) -> Self { - result_tree - .lookup_node_by_path(path) - .map_or(DataResultNodeOrPath::Path(path), |node| { - DataResultNodeOrPath::DataResultNode(node) - }) - } - - fn path(&self) -> &'a EntityPath { - match self { - DataResultNodeOrPath::Path(path) => path, - DataResultNodeOrPath::DataResultNode(node) => &node.data_result.entity_path, - } - } - - fn data_result_node(&self) -> Option<&'a DataResultNode> { - match self { - DataResultNodeOrPath::Path(_) => None, - DataResultNodeOrPath::DataResultNode(node) => Some(node), - } - } -} - /// Holds the state of the blueprint tree UI. #[derive(Default)] pub struct BlueprintTree { diff --git a/crates/viewer/re_viewer_context/src/data_result_node_or_path.rs b/crates/viewer/re_viewer_context/src/data_result_node_or_path.rs new file mode 100644 index 000000000000..c771de78c255 --- /dev/null +++ b/crates/viewer/re_viewer_context/src/data_result_node_or_path.rs @@ -0,0 +1,33 @@ +use re_chunk::EntityPath; + +use crate::{DataResultNode, DataResultTree}; + +// TODO(@Wumpf): document this +pub enum DataResultNodeOrPath<'a> { + Path(&'a EntityPath), + DataResultNode(&'a DataResultNode), +} + +impl<'a> DataResultNodeOrPath<'a> { + pub fn from_path_lookup(result_tree: &'a DataResultTree, path: &'a EntityPath) -> Self { + result_tree + .lookup_node_by_path(path) + .map_or(DataResultNodeOrPath::Path(path), |node| { + DataResultNodeOrPath::DataResultNode(node) + }) + } + + pub fn path(&self) -> &'a EntityPath { + match self { + DataResultNodeOrPath::Path(path) => path, + DataResultNodeOrPath::DataResultNode(node) => &node.data_result.entity_path, + } + } + + pub fn data_result_node(&self) -> Option<&'a DataResultNode> { + match self { + DataResultNodeOrPath::Path(_) => None, + DataResultNodeOrPath::DataResultNode(node) => Some(node), + } + } +} diff --git a/crates/viewer/re_viewer_context/src/lib.rs b/crates/viewer/re_viewer_context/src/lib.rs index 221a532e4fd4..b9a014444bdb 100644 --- a/crates/viewer/re_viewer_context/src/lib.rs +++ b/crates/viewer/re_viewer_context/src/lib.rs @@ -12,6 +12,7 @@ mod command_sender; mod component_fallbacks; mod component_ui_registry; mod contents; +mod data_result_node_or_path; mod file_dialog; mod image_info; mod item; @@ -51,6 +52,7 @@ pub use self::{ }, component_ui_registry::{ComponentUiRegistry, ComponentUiTypes, UiLayout}, contents::{blueprint_id_to_tile_id, Contents, ContentsName}, + data_result_node_or_path::DataResultNodeOrPath, file_dialog::santitize_file_name, image_info::{ColormapWithRange, ImageInfo}, item::Item, From 238d35320d448f6c30aafeb8949cad99435a4ea7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 17:36:26 +0100 Subject: [PATCH 17/39] MOAR CRUMBS --- .../src/item_heading_with_breadcrumbs.rs | 47 +++++++++++++------ .../re_selection_panel/src/item_title.rs | 5 +- .../re_selection_panel/src/selection_panel.rs | 37 ++++++++------- 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index da7ecc2d763a..1201bc88a9ff 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -14,7 +14,7 @@ use re_data_ui::item_ui::cursor_interact_with_selectable; use re_entity_db::InstancePath; use re_log_types::EntityPathPart; use re_ui::{icons, list_item, DesignTokens, UiExt as _}; -use re_viewer_context::{Contents, Item, ViewerContext}; +use re_viewer_context::{Contents, Item, SpaceViewId, ViewerContext}; use re_viewport_blueprint::ViewportBlueprint; use crate::ItemTitle; @@ -84,15 +84,15 @@ fn item_heading_contents( if instance.is_all() { // Entity path if let [ancestry @ .., _] = entity_path.as_slice() { - entity_path_breadcrumbs(ctx, ui, ancestry); + entity_path_breadcrumbs(ctx, ui, None, ancestry); } } else { // Instance path - entity_path_breadcrumbs(ctx, ui, entity_path.as_slice()); + entity_path_breadcrumbs(ctx, ui, None, entity_path.as_slice()); } } Item::ComponentPath(component_path) => { - entity_path_breadcrumbs(ctx, ui, component_path.entity_path.as_slice()); + entity_path_breadcrumbs(ctx, ui, None, component_path.entity_path.as_slice()); } Item::Container(container_id) => { if let Some(parent) = viewport.parent(&Contents::Container(*container_id)) { @@ -104,16 +104,30 @@ fn item_heading_contents( viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); } } - Item::DataResult(view_id, _) => { + Item::DataResult(view_id, instance_path) => { viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); - // TODO(#4491): breadcrumbs for data results entity paths and projections - // if let Some(view) = viewport.view(view_id) { - // let query_result = ctx.lookup_query_result(*view_id); - // let result_tree = &query_result.tree; - // let root_node = result_tree.root_node(); - // let origin = - // DataResultNodeOrPath::from_path_lookup(result_tree, &view.space_origin); + let InstancePath { + entity_path, + instance, + } = instance_path; + + if let Some(view) = viewport.view(view_id) { + if entity_path.starts_with(&view.space_origin) { + let relative = &entity_path.as_slice()[view.space_origin.len()..]; + + if instance.is_all() { + // we will show last part in full, later + if let [all_but_last @ .., _] = relative { + entity_path_breadcrumbs(ctx, ui, Some(*view_id), all_but_last); + } + } else { + entity_path_breadcrumbs(ctx, ui, Some(*view_id), relative); + } + } else { + ui.label("TODO"); // TODO + } + } } } }); @@ -138,13 +152,14 @@ fn item_heading_contents( fn entity_path_breadcrumbs( ctx: &ViewerContext<'_>, ui: &mut egui::Ui, + view_id: Option, entity_parts: &[EntityPathPart], ) { // Match on everything plus last let button = match entity_parts { [ancestry @ .., last] => { // Recurse! - entity_path_breadcrumbs(ctx, ui, ancestry); + entity_path_breadcrumbs(ctx, ui, view_id, ancestry); let first_char = last.unescaped_str().chars().next().unwrap_or('?'); egui::Button::new(first_char.to_string()).image_tint_follows_text_color(true) @@ -159,7 +174,11 @@ fn entity_path_breadcrumbs( let entity_path = EntityPath::new(entity_parts.to_vec()); let response = ui.add(button).on_hover_text(entity_path.to_string()); - let item = Item::from(entity_path); + let item = if let Some(view_id) = view_id { + Item::DataResult(view_id, entity_path.into()) + } else { + Item::from(entity_path) + }; cursor_interact_with_selectable(ctx, response, item); separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR_ENTITY); diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 7a0f2dc9e768..11cac47e4e0c 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -45,10 +45,7 @@ impl ItemTitle { Item::SpaceView(view_id) => Self::from_view_id(ctx, viewport, view_id), Item::DataResult(view_id, instance_path) => { - let name = instance_path.syntax_highlighted(style); - - let item_title = Self::new(name, guess_instance_path_icon(ctx, instance_path)); - + let item_title = Self::from_instance_path(ctx, style, instance_path); if let Some(view) = viewport.view(view_id) { let kind = item.kind(); item_title.with_tooltip(format!( diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index e323dba2076e..5bdba2072e02 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -209,25 +209,30 @@ impl SelectionPanel { } Item::DataResult(view_id, instance_path) => { - if let Some(view) = viewport.view(view_id) { - ui.list_item_flat_noninteractive( - PropertyContent::new("Stream entity").value_fn(|ui, _| { - let (query, db) = guess_query_and_db_for_selected_entity( - ctx, - &instance_path.entity_path, - ); + ui.list_item_flat_noninteractive(PropertyContent::new("Stream entity").value_fn( + |ui, _| { + let (query, db) = + guess_query_and_db_for_selected_entity(ctx, &instance_path.entity_path); - item_ui::entity_path_parts_buttons( - ctx, - &query, - db, - ui, - None, - &instance_path.entity_path, - ); - }), + item_ui::entity_path_parts_buttons( + ctx, + &query, + db, + ui, + None, + &instance_path.entity_path, + ); + }, + )); + + if instance_path.instance.is_specific() { + ui.list_item_flat_noninteractive( + PropertyContent::new("Instance") + .value_text(instance_path.instance.to_string()), ); + } + if let Some(view) = viewport.view(view_id) { ui.list_item_flat_noninteractive(PropertyContent::new("In view").value_fn( |ui, _| { space_view_button(ctx, ui, view); From ad865bff38d2c5ea1f090f40460f3d66183a903b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 4 Dec 2024 17:59:51 +0100 Subject: [PATCH 18/39] Fix origin-relative space view paths --- .../src/item_heading_with_breadcrumbs.rs | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index 1201bc88a9ff..b20a977c69d4 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -28,6 +28,8 @@ pub fn item_heading_with_breadcrumbs( ui: &mut egui::Ui, item: &Item, ) { + re_tracing::profile_function!(); + ui.list_item() .with_height(DesignTokens::title_bar_height()) .interactive(false) @@ -84,15 +86,15 @@ fn item_heading_contents( if instance.is_all() { // Entity path if let [ancestry @ .., _] = entity_path.as_slice() { - entity_path_breadcrumbs(ctx, ui, None, ancestry); + entity_path_breadcrumbs(ctx, ui, None, None, ancestry); } } else { // Instance path - entity_path_breadcrumbs(ctx, ui, None, entity_path.as_slice()); + entity_path_breadcrumbs(ctx, ui, None, None, entity_path.as_slice()); } } Item::ComponentPath(component_path) => { - entity_path_breadcrumbs(ctx, ui, None, component_path.entity_path.as_slice()); + entity_path_breadcrumbs(ctx, ui, None, None, component_path.entity_path.as_slice()); } Item::Container(container_id) => { if let Some(parent) = viewport.parent(&Contents::Container(*container_id)) { @@ -119,10 +121,22 @@ fn item_heading_contents( if instance.is_all() { // we will show last part in full, later if let [all_but_last @ .., _] = relative { - entity_path_breadcrumbs(ctx, ui, Some(*view_id), all_but_last); + entity_path_breadcrumbs( + ctx, + ui, + Some(*view_id), + Some(&view.space_origin), + all_but_last, + ); } } else { - entity_path_breadcrumbs(ctx, ui, Some(*view_id), relative); + entity_path_breadcrumbs( + ctx, + ui, + Some(*view_id), + Some(&view.space_origin), + relative, + ); } } else { ui.label("TODO"); // TODO @@ -152,14 +166,18 @@ fn item_heading_contents( fn entity_path_breadcrumbs( ctx: &ViewerContext<'_>, ui: &mut egui::Ui, + // If we are in a view view_id: Option, + // Everything is relative to this + origin: Option<&EntityPath>, + // Show crumbs for all of these entity_parts: &[EntityPathPart], ) { // Match on everything plus last let button = match entity_parts { [ancestry @ .., last] => { // Recurse! - entity_path_breadcrumbs(ctx, ui, view_id, ancestry); + entity_path_breadcrumbs(ctx, ui, view_id, origin, ancestry); let first_char = last.unescaped_str().chars().next().unwrap_or('?'); egui::Button::new(first_char.to_string()).image_tint_follows_text_color(true) @@ -171,13 +189,17 @@ fn entity_path_breadcrumbs( } }; - let entity_path = EntityPath::new(entity_parts.to_vec()); - let response = ui.add(button).on_hover_text(entity_path.to_string()); + let full_entity_path = if let Some(origin) = origin { + origin.join(&EntityPath::new(entity_parts.to_vec())) + } else { + EntityPath::new(entity_parts.to_vec()) + }; + let response = ui.add(button).on_hover_text(full_entity_path.to_string()); let item = if let Some(view_id) = view_id { - Item::DataResult(view_id, entity_path.into()) + Item::DataResult(view_id, full_entity_path.into()) } else { - Item::from(entity_path) + Item::from(full_entity_path) }; cursor_interact_with_selectable(ctx, response, item); From c56d4b1a44986692a0e0033a347b6de6de80964a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 08:03:43 +0100 Subject: [PATCH 19/39] Handle projections --- .../src/item_heading_with_breadcrumbs.rs | 87 ++++++++++++++----- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index b20a977c69d4..24ea809ebc87 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -13,7 +13,7 @@ use re_chunk::EntityPath; use re_data_ui::item_ui::cursor_interact_with_selectable; use re_entity_db::InstancePath; use re_log_types::EntityPathPart; -use re_ui::{icons, list_item, DesignTokens, UiExt as _}; +use re_ui::{icons, list_item, DesignTokens, SyntaxHighlighting, UiExt as _}; use re_viewer_context::{Contents, Item, SpaceViewId, ViewerContext}; use re_viewport_blueprint::ViewportBlueprint; @@ -86,15 +86,22 @@ fn item_heading_contents( if instance.is_all() { // Entity path if let [ancestry @ .., _] = entity_path.as_slice() { - entity_path_breadcrumbs(ctx, ui, None, None, ancestry); + entity_path_breadcrumbs(ctx, ui, None, None, ancestry, true); } } else { // Instance path - entity_path_breadcrumbs(ctx, ui, None, None, entity_path.as_slice()); + entity_path_breadcrumbs(ctx, ui, None, None, entity_path.as_slice(), true); } } Item::ComponentPath(component_path) => { - entity_path_breadcrumbs(ctx, ui, None, None, component_path.entity_path.as_slice()); + entity_path_breadcrumbs( + ctx, + ui, + None, + None, + component_path.entity_path.as_slice(), + true, + ); } Item::Container(container_id) => { if let Some(parent) = viewport.parent(&Contents::Container(*container_id)) { @@ -119,7 +126,7 @@ fn item_heading_contents( let relative = &entity_path.as_slice()[view.space_origin.len()..]; if instance.is_all() { - // we will show last part in full, later + // we will show last part in full later if let [all_but_last @ .., _] = relative { entity_path_breadcrumbs( ctx, @@ -127,19 +134,50 @@ fn item_heading_contents( Some(*view_id), Some(&view.space_origin), all_but_last, + true, ); } } else { + // we show the instance number later entity_path_breadcrumbs( ctx, ui, Some(*view_id), Some(&view.space_origin), relative, + true, ); } } else { - ui.label("TODO"); // TODO + // Projections + let common_ancestor = instance_path + .entity_path + .common_ancestor(&view.space_origin); + let relative = &entity_path.as_slice()[common_ancestor.len()..]; + + if instance.is_all() { + // we will show last part in full later + if let [all_but_last @ .., _] = relative { + entity_path_breadcrumbs( + ctx, + ui, + Some(*view_id), + Some(&common_ancestor), + all_but_last, + false, + ); + } + } else { + // we show the instance number later + entity_path_breadcrumbs( + ctx, + ui, + Some(*view_id), + Some(&common_ancestor), + relative, + false, + ); + } } } } @@ -172,29 +210,36 @@ fn entity_path_breadcrumbs( origin: Option<&EntityPath>, // Show crumbs for all of these entity_parts: &[EntityPathPart], + include_root: bool, ) { - // Match on everything plus last - let button = match entity_parts { - [ancestry @ .., last] => { - // Recurse! - entity_path_breadcrumbs(ctx, ui, view_id, origin, ancestry); - - let first_char = last.unescaped_str().chars().next().unwrap_or('?'); - egui::Button::new(first_char.to_string()).image_tint_follows_text_color(true) - } - _ => { - // Root - egui::Button::image(icons::RECORDING.as_image().fit_to_original_size(ICON_SCALE)) - .image_tint_follows_text_color(true) + if let [ancestry @ .., _] = entity_parts { + // Recurse! + + if !ancestry.is_empty() || include_root { + entity_path_breadcrumbs(ctx, ui, view_id, origin, ancestry, include_root); } - }; + } let full_entity_path = if let Some(origin) = origin { origin.join(&EntityPath::new(entity_parts.to_vec())) } else { EntityPath::new(entity_parts.to_vec()) }; - let response = ui.add(button).on_hover_text(full_entity_path.to_string()); + + let button = if let Some(last) = full_entity_path.last() { + let first_char = last.unescaped_str().chars().next().unwrap_or('?'); + egui::Button::new(first_char.to_string()) + } else { + // Root + // TODO: different icon, at least if we are in a space view + egui::Button::image(icons::RECORDING.as_image().fit_to_original_size(ICON_SCALE)) + .image_tint_follows_text_color(true) + }; + + let response = ui.add(button); + let response = response.on_hover_ui(|ui| { + ui.label(full_entity_path.syntax_highlighted(ui.style())); + }); let item = if let Some(view_id) = view_id { Item::DataResult(view_id, full_entity_path.into()) From 5c21dfa09ab03009d06db881d226c905c6991410 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 08:42:27 +0100 Subject: [PATCH 20/39] Cleanup --- .../src/item_heading_with_breadcrumbs.rs | 237 +++++++++--------- .../re_selection_panel/src/item_title.rs | 6 +- .../viewer/re_ui/src/syntax_highlighting.rs | 7 +- 3 files changed, 127 insertions(+), 123 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index 24ea809ebc87..4148469e69e5 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -8,9 +8,12 @@ //! Each bread-crumb is just an icon or a letter. //! The item is an icon and a name. //! Each bread-crumb is clickable, as is the last item. +//! +//! The bread crumbs hierarchy should be identical to the hierarchy in the +//! either the blueprint tree panel, or the streams/time panel. use re_chunk::EntityPath; -use re_data_ui::item_ui::cursor_interact_with_selectable; +use re_data_ui::item_ui::{cursor_interact_with_selectable, guess_instance_path_icon}; use re_entity_db::InstancePath; use re_log_types::EntityPathPart; use re_ui::{icons, list_item, DesignTokens, SyntaxHighlighting, UiExt as _}; @@ -43,147 +46,141 @@ pub fn item_heading_with_breadcrumbs( .layout(egui::Layout::left_to_right(egui::Align::Center)), |ui| { ui.spacing_mut().item_spacing.x = 4.0; - item_heading_contents(ctx, viewport, ui, item); + + { + // No background rectangles, even for hovered items + let visuals = ui.visuals_mut(); + visuals.widgets.active.bg_fill = egui::Color32::TRANSPARENT; + visuals.widgets.active.weak_bg_fill = egui::Color32::TRANSPARENT; + visuals.widgets.hovered.bg_fill = egui::Color32::TRANSPARENT; + visuals.widgets.hovered.weak_bg_fill = egui::Color32::TRANSPARENT; + } + + // First the c/r/u/m/b/s/ + ui.scope(|ui| { + // Dimmer colors for breadcrumbs + let visuals = ui.visuals_mut(); + visuals.widgets.inactive.fg_stroke.color = visuals.text_color(); + item_bread_crumbs_ui(ctx, viewport, ui, item); + }); + + // Then the full name of the main item: + last_part_of_item_heading(ctx, viewport, ui, item); }, ); }), ); } -fn item_heading_contents( +fn item_bread_crumbs_ui( ctx: &ViewerContext<'_>, viewport: &ViewportBlueprint, ui: &mut egui::Ui, item: &Item, ) { - { - // No background rectangles, even for hovered items - let visuals = ui.visuals_mut(); - visuals.widgets.active.bg_fill = egui::Color32::TRANSPARENT; - visuals.widgets.active.weak_bg_fill = egui::Color32::TRANSPARENT; - visuals.widgets.hovered.bg_fill = egui::Color32::TRANSPARENT; - visuals.widgets.hovered.weak_bg_fill = egui::Color32::TRANSPARENT; - } - - ui.scope(|ui| { - // Breadcrumbs - { - // Dimmer colors for breadcrumbs - let visuals = ui.visuals_mut(); - visuals.widgets.inactive.fg_stroke.color = visuals.text_color(); + match item { + Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => { + // TODO(emilk): maybe some of these could have breadcrumbs } + Item::InstancePath(instance_path) => { + let InstancePath { + entity_path, + instance, + } = instance_path; - match item { - Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => { - // TODO(emilk): maybe some of these could have breadcrumbs - } - Item::InstancePath(instance_path) => { - let InstancePath { - entity_path, - instance, - } = instance_path; - - if instance.is_all() { - // Entity path - if let [ancestry @ .., _] = entity_path.as_slice() { - entity_path_breadcrumbs(ctx, ui, None, None, ancestry, true); - } - } else { - // Instance path - entity_path_breadcrumbs(ctx, ui, None, None, entity_path.as_slice(), true); + if instance.is_all() { + // Entity path. Exclude the last part from the breadcrumbs, + // as we will show it in full later on. + if let [all_but_last @ .., _] = entity_path.as_slice() { + entity_path_breadcrumbs(ctx, ui, None, &EntityPath::root(), all_but_last, true); } - } - Item::ComponentPath(component_path) => { + } else { + // Instance path. + // Show the full entity path, and save the `[instance_nr]` for later. entity_path_breadcrumbs( ctx, ui, None, - None, - component_path.entity_path.as_slice(), + &EntityPath::root(), + entity_path.as_slice(), true, ); } - Item::Container(container_id) => { - if let Some(parent) = viewport.parent(&Contents::Container(*container_id)) { - viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); - } + } + Item::ComponentPath(component_path) => { + entity_path_breadcrumbs( + ctx, + ui, + None, + &EntityPath::root(), + component_path.entity_path.as_slice(), + true, + ); + } + Item::Container(container_id) => { + if let Some(parent) = viewport.parent(&Contents::Container(*container_id)) { + viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); } - Item::SpaceView(view_id) => { - if let Some(parent) = viewport.parent(&Contents::SpaceView(*view_id)) { - viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); - } + } + Item::SpaceView(view_id) => { + if let Some(parent) = viewport.parent(&Contents::SpaceView(*view_id)) { + viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent)); } - Item::DataResult(view_id, instance_path) => { - viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); + } + Item::DataResult(view_id, instance_path) => { + viewport_breadcrumbs(ctx, viewport, ui, Contents::SpaceView(*view_id)); - let InstancePath { - entity_path, - instance, - } = instance_path; + let InstancePath { + entity_path, + instance, + } = instance_path; - if let Some(view) = viewport.view(view_id) { - if entity_path.starts_with(&view.space_origin) { - let relative = &entity_path.as_slice()[view.space_origin.len()..]; + if let Some(view) = viewport.view(view_id) { + let common_ancestor = instance_path + .entity_path + .common_ancestor(&view.space_origin); - if instance.is_all() { - // we will show last part in full later - if let [all_but_last @ .., _] = relative { - entity_path_breadcrumbs( - ctx, - ui, - Some(*view_id), - Some(&view.space_origin), - all_but_last, - true, - ); - } - } else { - // we show the instance number later - entity_path_breadcrumbs( - ctx, - ui, - Some(*view_id), - Some(&view.space_origin), - relative, - true, - ); - } - } else { - // Projections - let common_ancestor = instance_path - .entity_path - .common_ancestor(&view.space_origin); - let relative = &entity_path.as_slice()[common_ancestor.len()..]; + let relative = &entity_path.as_slice()[common_ancestor.len()..]; - if instance.is_all() { - // we will show last part in full later - if let [all_but_last @ .., _] = relative { - entity_path_breadcrumbs( - ctx, - ui, - Some(*view_id), - Some(&common_ancestor), - all_but_last, - false, - ); - } - } else { - // we show the instance number later - entity_path_breadcrumbs( - ctx, - ui, - Some(*view_id), - Some(&common_ancestor), - relative, - false, - ); - } + let is_projection = !entity_path.starts_with(&view.space_origin); + + if instance.is_all() { + // Entity path. Exclude the last part from the breadcrumbs, + // as we will show it in full later on. + if let [all_but_last @ .., _] = relative { + entity_path_breadcrumbs( + ctx, + ui, + Some(*view_id), + &common_ancestor, + all_but_last, + !is_projection, + ); } + } else { + // Instance path. + // Show the full entity path, and save the `[instance_nr]` for later. + entity_path_breadcrumbs( + ctx, + ui, + Some(*view_id), + &common_ancestor, + relative, + !is_projection, + ); } } } - }); + } +} +fn last_part_of_item_heading( + ctx: &ViewerContext<'_>, + viewport: &ViewportBlueprint, + ui: &mut egui::Ui, + item: &Item, +) { + // Show the actual item, after all the bread crumbs: let ItemTitle { icon, label, @@ -207,7 +204,7 @@ fn entity_path_breadcrumbs( // If we are in a view view_id: Option, // Everything is relative to this - origin: Option<&EntityPath>, + origin: &EntityPath, // Show crumbs for all of these entity_parts: &[EntityPathPart], include_root: bool, @@ -220,19 +217,23 @@ fn entity_path_breadcrumbs( } } - let full_entity_path = if let Some(origin) = origin { - origin.join(&EntityPath::new(entity_parts.to_vec())) - } else { - EntityPath::new(entity_parts.to_vec()) - }; + let full_entity_path = origin.join(&EntityPath::new(entity_parts.to_vec())); let button = if let Some(last) = full_entity_path.last() { let first_char = last.unescaped_str().chars().next().unwrap_or('?'); egui::Button::new(first_char.to_string()) } else { // Root - // TODO: different icon, at least if we are in a space view - egui::Button::image(icons::RECORDING.as_image().fit_to_original_size(ICON_SCALE)) + let icon = if view_id.is_some() { + // Inside a space view, we show the root with an icon + // that matches the one in the blueprint tree panel. + guess_instance_path_icon(ctx, &InstancePath::from(full_entity_path.clone())) + } else { + // For a streams hierarchy, we show the root using a different icon, + // just to make it clear that this is a different kind of hierarchy. + &icons::RECORDING // streams hierarchy + }; + egui::Button::image(icon.as_image().fit_to_original_size(ICON_SCALE)) .image_tint_follows_text_color(true) }; diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 11cac47e4e0c..411bd04bef62 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -2,7 +2,9 @@ use re_chunk::EntityPath; use re_data_ui::item_ui::{guess_instance_path_icon, guess_query_and_db_for_selected_entity}; use re_entity_db::InstancePath; use re_log_types::ComponentPath; -use re_ui::{icons, syntax_highlighting::InstanceWithCarets, SyntaxHighlighting as _}; +use re_ui::{ + icons, syntax_highlighting::InstanceInBrackets as InstanceWithBrackets, SyntaxHighlighting as _, +}; use re_viewer_context::{contents_name_style, ContainerId, Item, SpaceViewId, ViewerContext}; use re_viewport_blueprint::ViewportBlueprint; @@ -106,7 +108,7 @@ impl ItemTitle { } } else { // Instance path - InstanceWithCarets(*instance).syntax_highlighted(style) + InstanceWithBrackets(*instance).syntax_highlighted(style) }; Self::new(name, guess_instance_path_icon(ctx, instance_path)) diff --git a/crates/viewer/re_ui/src/syntax_highlighting.rs b/crates/viewer/re_ui/src/syntax_highlighting.rs index 678711923642..18ea524244d2 100644 --- a/crates/viewer/re_ui/src/syntax_highlighting.rs +++ b/crates/viewer/re_ui/src/syntax_highlighting.rs @@ -66,14 +66,15 @@ impl SyntaxHighlighting for InstancePath { fn syntax_highlight_into(&self, style: &Style, job: &mut LayoutJob) { self.entity_path.syntax_highlight_into(style, job); if self.instance.is_specific() { - InstanceWithCarets(self.instance).syntax_highlight_into(style, job); + InstanceInBrackets(self.instance).syntax_highlight_into(style, job); } } } -pub struct InstanceWithCarets(pub Instance); +/// Formats an instance number enclosed in square brackets: `[123]` +pub struct InstanceInBrackets(pub Instance); -impl SyntaxHighlighting for InstanceWithCarets { +impl SyntaxHighlighting for InstanceInBrackets { fn syntax_highlight_into(&self, style: &Style, job: &mut LayoutJob) { job.append("[", 0.0, faint_text_format(style)); self.0.syntax_highlight_into(style, job); From a73f0128bbe05139c6c0f2d8086ebf8c0e9a3f9d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 08:50:27 +0100 Subject: [PATCH 21/39] Cleanup --- .../src/item_heading_with_breadcrumbs.rs | 89 ++++++++++--------- .../re_selection_panel/src/item_title.rs | 15 +++- 2 files changed, 61 insertions(+), 43 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index 4148469e69e5..d784d91c725f 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -72,6 +72,7 @@ pub fn item_heading_with_breadcrumbs( ); } +// Show the bread crumbs leading to (but not including) the final item. fn item_bread_crumbs_ui( ctx: &ViewerContext<'_>, viewport: &ViewportBlueprint, @@ -80,7 +81,8 @@ fn item_bread_crumbs_ui( ) { match item { Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => { - // TODO(emilk): maybe some of these could have breadcrumbs + // These have no bread crumbs, at least not currently. + // I guess one could argue that the `StoreId` should have the `AppId` as its ancestor? } Item::InstancePath(instance_path) => { let InstancePath { @@ -174,13 +176,13 @@ fn item_bread_crumbs_ui( } } +// Show the actual item, after all the bread crumbs: fn last_part_of_item_heading( ctx: &ViewerContext<'_>, viewport: &ViewportBlueprint, ui: &mut egui::Ui, item: &Item, ) { - // Show the actual item, after all the bread crumbs: let ItemTitle { icon, label, @@ -198,6 +200,49 @@ fn last_part_of_item_heading( cursor_interact_with_selectable(ctx, response, item.clone()); } +/// The breadcrumbs of containers and views in the viewport. +fn viewport_breadcrumbs( + ctx: &ViewerContext<'_>, + viewport: &ViewportBlueprint, + ui: &mut egui::Ui, + contents: Contents, +) { + let item = Item::from(contents); + + if let Some(parent) = viewport.parent(&contents) { + // Recurse! + viewport_breadcrumbs(ctx, viewport, ui, parent.into()); + } + + let ItemTitle { + icon, + label: _, // ignored: we just show the icon for breadcrumbs + label_style: _, // no label + tooltip, + } = ItemTitle::from_contents(ctx, viewport, &contents); + + let mut response = ui.add( + egui::Button::image(icon.as_image().fit_to_original_size(ICON_SCALE)) + .image_tint_follows_text_color(true), + ); + if let Some(tooltip) = tooltip { + response = response.on_hover_text(tooltip); + } + cursor_interact_with_selectable(ctx, response, item); + + separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR_BLUEPRINT); +} + +fn separator_icon_ui(ui: &mut egui::Ui, icon: re_ui::Icon) { + ui.add( + icon.as_image() + .fit_to_original_size(ICON_SCALE) + .tint(ui.visuals().text_color()), + ); +} + +/// The breadcrumbs of an entity path, +/// that may or may not be part of a view. fn entity_path_breadcrumbs( ctx: &ViewerContext<'_>, ui: &mut egui::Ui, @@ -251,43 +296,3 @@ fn entity_path_breadcrumbs( separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR_ENTITY); } - -fn viewport_breadcrumbs( - ctx: &ViewerContext<'_>, - viewport: &ViewportBlueprint, - ui: &mut egui::Ui, - contents: Contents, -) { - let item = Item::from(contents); - - if let Some(parent) = viewport.parent(&contents) { - // Recurse! - viewport_breadcrumbs(ctx, viewport, ui, parent.into()); - } - - let ItemTitle { - icon, - label: _, // ignored: we just show the icon for breadcrumbs - label_style: _, // no label - tooltip, - } = ItemTitle::from_item(ctx, viewport, ui.style(), &item); - - let mut response = ui.add( - egui::Button::image(icon.as_image().fit_to_original_size(ICON_SCALE)) - .image_tint_follows_text_color(true), - ); - if let Some(tooltip) = tooltip { - response = response.on_hover_text(tooltip); - } - cursor_interact_with_selectable(ctx, response, item); - - separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR_BLUEPRINT); -} - -fn separator_icon_ui(ui: &mut egui::Ui, icon: re_ui::Icon) { - ui.add( - icon.as_image() - .fit_to_original_size(ICON_SCALE) - .tint(ui.visuals().text_color()), - ); -} diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 411bd04bef62..34b8cd5d55ec 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -5,7 +5,9 @@ use re_log_types::ComponentPath; use re_ui::{ icons, syntax_highlighting::InstanceInBrackets as InstanceWithBrackets, SyntaxHighlighting as _, }; -use re_viewer_context::{contents_name_style, ContainerId, Item, SpaceViewId, ViewerContext}; +use re_viewer_context::{ + contents_name_style, ContainerId, Contents, Item, SpaceViewId, ViewerContext, +}; use re_viewport_blueprint::ViewportBlueprint; #[must_use] @@ -141,6 +143,17 @@ impl ItemTitle { )) } + pub fn from_contents( + ctx: &ViewerContext<'_>, + viewport: &ViewportBlueprint, + contents: &Contents, + ) -> Self { + match contents { + Contents::Container(container_id) => Self::from_container_id(viewport, container_id), + Contents::SpaceView(view_id) => Self::from_view_id(ctx, viewport, view_id), + } + } + pub fn from_container_id(viewport: &ViewportBlueprint, container_id: &ContainerId) -> Self { if let Some(container_blueprint) = viewport.container(container_id) { let hover_text = if let Some(display_name) = container_blueprint.display_name.as_ref() { From ddd3ff408ad8e0a8bd882521420ecefb67b6674c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 08:56:43 +0100 Subject: [PATCH 22/39] fix spacing and add a TODO --- .../re_selection_panel/src/item_heading_with_breadcrumbs.rs | 2 ++ crates/viewer/re_ui/src/icons.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index d784d91c725f..fa0283a22fea 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -145,6 +145,8 @@ fn item_bread_crumbs_ui( let relative = &entity_path.as_slice()[common_ancestor.len()..]; let is_projection = !entity_path.starts_with(&view.space_origin); + // TODO: the projection breadcrumbs are wrong for nuscenes + // (but correct for arkit!) if instance.is_all() { // Entity path. Exclude the last part from the breadcrumbs, diff --git a/crates/viewer/re_ui/src/icons.rs b/crates/viewer/re_ui/src/icons.rs index b08f5c339360..f1e18ef24fd3 100644 --- a/crates/viewer/re_ui/src/icons.rs +++ b/crates/viewer/re_ui/src/icons.rs @@ -122,6 +122,7 @@ pub const VIDEO_ERROR: Icon = icon_from_path!("../data/icons/video_error.png"); /// `>` pub const BREADCRUMBS_SEPARATOR_BLUEPRINT: Icon = icon_from_path!("../data/icons/breadcrumbs_separator_blueprint.png"); + /// `/` pub const BREADCRUMBS_SEPARATOR_ENTITY: Icon = icon_from_path!("../data/icons/breadcrumbs_separator_entity.png"); From 6fd2d5af5be4fd5c870db16f8828c2fa18a4a0c2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 11:13:13 +0100 Subject: [PATCH 23/39] Remove icon from final item --- .../src/item_heading_with_breadcrumbs.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index fa0283a22fea..0c0fd01d4542 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -186,16 +186,13 @@ fn last_part_of_item_heading( item: &Item, ) { let ItemTitle { - icon, + icon: _, // no icon! label, label_style: _, // Intentionally ignored tooltip, } = ItemTitle::from_item(ctx, viewport, ui.style(), item); - let mut response = ui.add( - egui::Button::image_and_text(icon.as_image().fit_to_original_size(ICON_SCALE), label) - .image_tint_follows_text_color(true), - ); + let mut response = ui.add(egui::Button::new(label)); if let Some(tooltip) = tooltip { response = response.on_hover_text(tooltip); } From 86c1ed884ceb3fee21babf04dc41ca578e3a7a6e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 11:16:42 +0100 Subject: [PATCH 24/39] Same separator icon for all crumbs --- .../src/item_heading_with_breadcrumbs.rs | 4 ++-- crates/viewer/re_ui/src/icons.rs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index 0c0fd01d4542..3f206c740a0f 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -229,7 +229,7 @@ fn viewport_breadcrumbs( } cursor_interact_with_selectable(ctx, response, item); - separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR_BLUEPRINT); + separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR); } fn separator_icon_ui(ui: &mut egui::Ui, icon: re_ui::Icon) { @@ -293,5 +293,5 @@ fn entity_path_breadcrumbs( }; cursor_interact_with_selectable(ctx, response, item); - separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR_ENTITY); + separator_icon_ui(ui, icons::BREADCRUMBS_SEPARATOR); } diff --git a/crates/viewer/re_ui/src/icons.rs b/crates/viewer/re_ui/src/icons.rs index f1e18ef24fd3..0e433f0a8502 100644 --- a/crates/viewer/re_ui/src/icons.rs +++ b/crates/viewer/re_ui/src/icons.rs @@ -120,9 +120,10 @@ pub const GITHUB: Icon = icon_from_path!("../data/icons/github.png"); pub const VIDEO_ERROR: Icon = icon_from_path!("../data/icons/video_error.png"); /// `>` -pub const BREADCRUMBS_SEPARATOR_BLUEPRINT: Icon = +pub const BREADCRUMBS_SEPARATOR: Icon = icon_from_path!("../data/icons/breadcrumbs_separator_blueprint.png"); -/// `/` -pub const BREADCRUMBS_SEPARATOR_ENTITY: Icon = - icon_from_path!("../data/icons/breadcrumbs_separator_entity.png"); +// TODO: remove icon? +// /// `/` +// pub const BREADCRUMBS_SEPARATOR_ENTITY: Icon = +// icon_from_path!("../data/icons/breadcrumbs_separator_entity.png"); From 4744d657b003bdf06988ca2179193de2ee186145 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 11:18:02 +0100 Subject: [PATCH 25/39] Remove "in view" from data results (you can see it from the crumbs) --- crates/viewer/re_selection_panel/src/selection_panel.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 5bdba2072e02..ba8262afcfaf 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -232,14 +232,6 @@ impl SelectionPanel { ); } - if let Some(view) = viewport.view(view_id) { - ui.list_item_flat_noninteractive(PropertyContent::new("In view").value_fn( - |ui, _| { - space_view_button(ctx, ui, view); - }, - )); - } - if instance_path.is_all() { let entity_path = &instance_path.entity_path; let query_result = ctx.lookup_query_result(*view_id); From 75bf9e64cfe5237ff1a3e626edd8c1fc8c342989 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 11:32:36 +0100 Subject: [PATCH 26/39] Color tweaks --- .../re_selection_panel/src/item_heading_with_breadcrumbs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index 3f206c740a0f..5fe248722f81 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -60,7 +60,7 @@ pub fn item_heading_with_breadcrumbs( ui.scope(|ui| { // Dimmer colors for breadcrumbs let visuals = ui.visuals_mut(); - visuals.widgets.inactive.fg_stroke.color = visuals.text_color(); + visuals.widgets.inactive.fg_stroke.color = egui::hex_color!("#6A8CD0"); item_bread_crumbs_ui(ctx, viewport, ui, item); }); @@ -236,7 +236,7 @@ fn separator_icon_ui(ui: &mut egui::Ui, icon: re_ui::Icon) { ui.add( icon.as_image() .fit_to_original_size(ICON_SCALE) - .tint(ui.visuals().text_color()), + .tint(ui.visuals().text_color().gamma_multiply(0.65)), ); } From d53baa2bbe8f7d19ec01cc02cf9f052447edd930 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 11:44:11 +0100 Subject: [PATCH 27/39] Nicer tooltip --- .../re_selection_panel/src/item_title.rs | 21 ++++++++++++------- .../viewer/re_ui/src/syntax_highlighting.rs | 6 ++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index 34b8cd5d55ec..c0cb6d43cd8c 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -1,3 +1,5 @@ +use egui::WidgetText; + use re_chunk::EntityPath; use re_data_ui::item_ui::{guess_instance_path_icon, guess_query_and_db_for_selected_entity}; use re_entity_db::InstancePath; @@ -15,7 +17,7 @@ pub struct ItemTitle { pub icon: &'static re_ui::Icon, pub label: egui::WidgetText, pub label_style: Option, - pub tooltip: Option, + pub tooltip: Option, } impl ItemTitle { @@ -51,11 +53,14 @@ impl ItemTitle { Item::DataResult(view_id, instance_path) => { let item_title = Self::from_instance_path(ctx, style, instance_path); if let Some(view) = viewport.view(view_id) { - let kind = item.kind(); - item_title.with_tooltip(format!( - "{kind} '{instance_path}' as shown in view {}", - view.display_name_or_default() - )) + item_title.with_tooltip({ + let mut layout_job = egui::text::LayoutJob::default(); + let style = ctx.egui_ctx.style(); + instance_path.syntax_highlight_into(&style, &mut layout_job); + format!(" in view '{}'", view.display_name_or_default()) + .syntax_highlight_into(&style, &mut layout_job); + layout_job + }) } else { item_title } @@ -222,8 +227,8 @@ impl ItemTitle { } #[inline] - fn with_tooltip(mut self, hover: impl Into) -> Self { - self.tooltip = Some(hover.into()); + fn with_tooltip(mut self, tooltip: impl Into) -> Self { + self.tooltip = Some(tooltip.into()); self } diff --git a/crates/viewer/re_ui/src/syntax_highlighting.rs b/crates/viewer/re_ui/src/syntax_highlighting.rs index 18ea524244d2..16a5fa32ef3e 100644 --- a/crates/viewer/re_ui/src/syntax_highlighting.rs +++ b/crates/viewer/re_ui/src/syntax_highlighting.rs @@ -33,6 +33,12 @@ fn faint_text_format(style: &Style) -> TextFormat { } } +impl SyntaxHighlighting for String { + fn syntax_highlight_into(&self, style: &Style, job: &mut LayoutJob) { + job.append(self, 0.0, text_format(style)); + } +} + impl SyntaxHighlighting for EntityPathPart { fn syntax_highlight_into(&self, style: &Style, job: &mut LayoutJob) { job.append(&self.ui_string(), 0.0, text_format(style)); From 79bcc4e5bece0562fb26d4e971d49df1f4ebd382 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Dec 2024 11:58:55 +0100 Subject: [PATCH 28/39] Better tooltips --- .../re_selection_panel/src/item_title.rs | 21 ++++---- .../viewer/re_ui/src/syntax_highlighting.rs | 51 +++++++++++++++++++ 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index c0cb6d43cd8c..4ab4750200e1 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -5,7 +5,9 @@ use re_data_ui::item_ui::{guess_instance_path_icon, guess_query_and_db_for_selec use re_entity_db::InstancePath; use re_log_types::ComponentPath; use re_ui::{ - icons, syntax_highlighting::InstanceInBrackets as InstanceWithBrackets, SyntaxHighlighting as _, + icons, + syntax_highlighting::{InstanceInBrackets as InstanceWithBrackets, SyntaxHighlightedBuilder}, + SyntaxHighlighting as _, }; use re_viewer_context::{ contents_name_style, ContainerId, Contents, Item, SpaceViewId, ViewerContext, @@ -53,14 +55,11 @@ impl ItemTitle { Item::DataResult(view_id, instance_path) => { let item_title = Self::from_instance_path(ctx, style, instance_path); if let Some(view) = viewport.view(view_id) { - item_title.with_tooltip({ - let mut layout_job = egui::text::LayoutJob::default(); - let style = ctx.egui_ctx.style(); - instance_path.syntax_highlight_into(&style, &mut layout_job); - format!(" in view '{}'", view.display_name_or_default()) - .syntax_highlight_into(&style, &mut layout_job); - layout_job - }) + item_title.with_tooltip( + SyntaxHighlightedBuilder::new(ctx.egui_ctx.style()) + .append(instance_path) + .append(&format!(" in view '{}'", view.display_name_or_default())), + ) } else { item_title } @@ -104,8 +103,6 @@ impl ItemTitle { instance, } = instance_path; - let kind = instance_path.kind(); - let name = if instance.is_all() { // Entity path if let Some(last) = entity_path.last() { @@ -119,7 +116,7 @@ impl ItemTitle { }; Self::new(name, guess_instance_path_icon(ctx, instance_path)) - .with_tooltip(format!("{kind} '{instance_path}'")) + .with_tooltip(instance_path.syntax_highlighted(style)) } pub fn from_component_path(ctx: &ViewerContext<'_>, component_path: &ComponentPath) -> Self { diff --git a/crates/viewer/re_ui/src/syntax_highlighting.rs b/crates/viewer/re_ui/src/syntax_highlighting.rs index 16a5fa32ef3e..af3d9d76e85b 100644 --- a/crates/viewer/re_ui/src/syntax_highlighting.rs +++ b/crates/viewer/re_ui/src/syntax_highlighting.rs @@ -1,8 +1,11 @@ +use std::sync::Arc; + use re_entity_db::InstancePath; use re_log_types::{EntityPath, EntityPathPart, Instance}; use egui::{text::LayoutJob, Color32, Style, TextFormat}; +// ---------------------------------------------------------------------------- pub trait SyntaxHighlighting { fn syntax_highlighted(&self, style: &Style) -> LayoutJob { let mut job = LayoutJob::default(); @@ -13,6 +16,54 @@ pub trait SyntaxHighlighting { fn syntax_highlight_into(&self, style: &Style, job: &mut LayoutJob); } +// ---------------------------------------------------------------------------- + +/// Easily build syntax-highlighted text. +pub struct SyntaxHighlightedBuilder { + pub style: Arc