Skip to content

Add TableStore for table/dataframe entries + basic UI #9437

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8092,6 +8092,7 @@ dependencies = [
"egui",
"egui-wgpu",
"egui_plot",
"egui_table",
"ehttp",
"glam",
"image",
Expand Down Expand Up @@ -8125,6 +8126,7 @@ dependencies = [
"re_renderer",
"re_selection_panel",
"re_smart_channel",
"re_sorbet",
"re_time_panel",
"re_tracing",
"re_types",
Expand Down Expand Up @@ -8203,6 +8205,7 @@ dependencies = [
"re_query",
"re_renderer",
"re_smart_channel",
"re_sorbet",
"re_string_interner",
"re_tracing",
"re_types",
Expand Down
6 changes: 3 additions & 3 deletions crates/viewer/re_blueprint_tree/src/blueprint_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@ impl BlueprintTree {
focused_item: &Item,
) -> Option<Item> {
match focused_item {
Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => None,
Item::AppId(_) | Item::TableId(_) | Item::DataSource(_) | Item::StoreId(_) => None,

Item::Container(container_id) => {
self.expand_all_contents_until(
Expand Down Expand Up @@ -1158,11 +1158,11 @@ fn add_new_view_or_container_menu_button(

fn set_blueprint_to_default_menu_buttons(ctx: &ViewerContext<'_>, ui: &mut egui::Ui) {
let default_blueprint_id = ctx
.store_context
.storage_context
.hub
.default_blueprint_id_for_app(&ctx.store_context.app_id);

let default_blueprint = default_blueprint_id.and_then(|id| ctx.store_context.bundle.get(id));
let default_blueprint = default_blueprint_id.and_then(|id| ctx.storage_context.bundle.get(id));

let disabled_reason = match default_blueprint {
None => Some("No default blueprint is set for this app"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ impl ContextMenuAction for CollapseExpandAllAction {
// TODO(ab): in an ideal world, we'd check the fully expended/collapsed state of the item to
// avoid showing a command that wouldn't have an effect but that's lots of added complexity.
match item {
Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) | Item::ComponentPath(_) => {
false
}
Item::AppId(_)
| Item::TableId(_)
| Item::DataSource(_)
| Item::StoreId(_)
| Item::ComponentPath(_) => false,

Item::View(_) | Item::Container(_) | Item::InstancePath(_) => true,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ impl ContextMenuAction for CopyEntityPathToClipboard {
fn supports_item(&self, _ctx: &ContextMenuContext<'_>, item: &Item) -> bool {
match item {
Item::AppId(_)
| Item::TableId(_)
| Item::DataSource(_)
| Item::StoreId(_)
| Item::Container(_)
Expand Down
6 changes: 5 additions & 1 deletion crates/viewer/re_context_menu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use once_cell::sync::OnceCell;

use re_entity_db::InstancePath;
use re_viewer_context::{
ContainerId, Contents, Item, ItemCollection, ItemContext, ViewId, ViewerContext,
ContainerId, Contents, Item, ItemCollection, ItemContext, TableId, ViewId, ViewerContext,
};
use re_viewport_blueprint::{ContainerBlueprint, ViewportBlueprint};

Expand Down Expand Up @@ -358,6 +358,7 @@ trait ContextMenuAction {
for (item, _) in ctx.selection.iter() {
match item {
Item::AppId(app_id) => self.process_app_id(ctx, app_id),
Item::TableId(table_id) => self.process_table_id(ctx, table_id),
Item::DataSource(data_source) => self.process_data_source(ctx, data_source),
Item::StoreId(store_id) => self.process_store_id(ctx, store_id),
Item::ComponentPath(component_path) => {
Expand Down Expand Up @@ -386,6 +387,9 @@ trait ContextMenuAction {
/// Process a single recording.
fn process_store_id(&self, _ctx: &ContextMenuContext<'_>, _store_id: &re_log_types::StoreId) {}

/// Process a table.
fn process_table_id(&self, _ctx: &ContextMenuContext<'_>, _store_id: &TableId) {}

/// Process a single container.
fn process_container(&self, _ctx: &ContextMenuContext<'_>, _container_id: &ContainerId) {}

Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_data_ui/src/app_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl crate::DataUi for ApplicationId {

// Find all recordings with this app id
let recordings: Vec<&EntityDb> = ctx
.store_context
.storage_context
.bundle
.recordings()
.filter(|db| db.app_id() == Some(self))
Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_data_ui/src/data_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl crate::DataUi for re_smart_channel::SmartChannelSource {
let mut blueprints = vec![];

for other in ctx
.store_context
.storage_context
.bundle
.entity_dbs()
.filter(|db| db.data_source.as_ref() == Some(self))
Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_data_ui/src/entity_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ impl crate::DataUi for EntityDb {
}
});

let hub = ctx.store_context.hub;
let hub = ctx.storage_context.hub;
let store_id = Some(self.store_id());

match self.store_kind() {
Expand Down
75 changes: 71 additions & 4 deletions crates/viewer/re_data_ui/src/item_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use re_log_types::{
};
use re_types::components::{Name, Timestamp};
use re_ui::{icons, list_item, SyntaxHighlighting as _, UiExt as _};
use re_viewer_context::{HoverHighlight, Item, UiLayout, ViewId, ViewerContext};
use re_viewer_context::{
HoverHighlight, Item, SystemCommand, SystemCommandSender as _, TableId, UiLayout, ViewId,
ViewerContext,
};

use super::DataUi as _;

Expand Down Expand Up @@ -724,7 +727,7 @@ pub fn store_id_button_ui(
store_id: &re_log_types::StoreId,
ui_layout: UiLayout,
) {
if let Some(entity_db) = ctx.store_context.bundle.get(store_id) {
if let Some(entity_db) = ctx.storage_context.bundle.get(store_id) {
entity_db_button_ui(ctx, ui, entity_db, ui_layout, true);
} else {
ui_layout.label(ui, store_id.to_string());
Expand Down Expand Up @@ -803,7 +806,7 @@ pub fn entity_db_button_ui(
});
if resp.clicked() {
ctx.command_sender()
.send_system(SystemCommand::CloseStore(store_id.clone()));
.send_system(SystemCommand::CloseEntry(store_id.clone().into()));
}
resp
});
Expand Down Expand Up @@ -845,10 +848,74 @@ pub fn entity_db_button_ui(
// for the blueprint.
if store_id.kind == re_log_types::StoreKind::Recording {
ctx.command_sender()
.send_system(SystemCommand::ActivateRecording(store_id.clone()));
.send_system(SystemCommand::ActivateEntry(store_id.clone().into()));
}

ctx.command_sender()
.send_system(SystemCommand::SetSelection(item));
}
}

pub fn table_id_button_ui(
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
table_id: &TableId,
ui_layout: UiLayout,
) {
let item = re_viewer_context::Item::TableId(table_id.clone());

let icon = &icons::VIEW_DATAFRAME;

let mut item_content =
list_item::LabelContent::new(table_id.as_ref()).with_icon_fn(|ui, rect, visuals| {
// Color icon based on whether this is the active recording or not:
// let color = if ctx.store_context.is_active(&store_id) {
let color = if false {
visuals.fg_stroke.color
} else {
ui.visuals().widgets.noninteractive.fg_stroke.color
};
icon.as_image().tint(color).paint_at(ui, rect);
});

if ui_layout.is_selection_panel() {
item_content = item_content.with_buttons(|ui| {
// Close-button:
let resp = ui
.small_icon_button(&icons::REMOVE)
.on_hover_text("Close this table (all data will be lost)");
if resp.clicked() {
ctx.command_sender()
.send_system(SystemCommand::CloseEntry(table_id.clone().into()));
}
resp
});
}

let mut list_item = ui
.list_item()
.selected(ctx.selection().contains_item(&item));

if ctx.hovered().contains_item(&item) {
list_item = list_item.force_hovered(true);
}

let response = list_item::list_item_scope(ui, "entity db button", |ui| {
list_item
.show_hierarchical(ui, item_content)
.on_hover_ui(|ui| {
ui.label(format!("Table: {table_id}"));
})
});

if response.hovered() {
ctx.selection_state().set_hovered(item.clone());
}

if response.clicked() {
ctx.command_sender()
.send_system(SystemCommand::ActivateEntry(table_id.clone().into()));
ctx.command_sender()
.send_system(SystemCommand::SetSelection(item));
}
}
2 changes: 1 addition & 1 deletion crates/viewer/re_data_ui/src/store_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ impl crate::DataUi for re_log_types::StoreId {
query: &re_chunk_store::LatestAtQuery,
db: &re_entity_db::EntityDb,
) {
if let Some(entity_db) = ctx.store_context.bundle.get(self) {
if let Some(entity_db) = ctx.storage_context.bundle.get(self) {
entity_db.data_ui(ctx, ui, ui_layout, query, db);
} else {
ui.label(format!("{} ID {} (not found)", self.kind, self.id));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn item_heading_no_breadcrumbs(
Item::AppId(_)
| Item::DataSource(_)
| Item::StoreId(_)
| Item::TableId(_)
| Item::Container(_)
| Item::View(_) => {
let ItemTitle {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ fn item_bread_crumbs_ui(
item: &Item,
) {
match item {
Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => {
Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) | Item::TableId(_) => {
// These have no bread crumbs, at least not currently.
// I guess one could argue that the `StoreId` should have the `AppId` as its ancestor?
}
Expand Down Expand Up @@ -192,6 +192,7 @@ fn last_part_of_item_heading(
| Item::DataSource { .. }
| Item::Container { .. }
| Item::View { .. }
| Item::TableId { .. }
| Item::StoreId { .. } => true,

Item::InstancePath { .. } | Item::DataResult { .. } | Item::ComponentPath { .. } => false,
Expand Down
11 changes: 9 additions & 2 deletions crates/viewer/re_selection_panel/src/item_title.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use re_ui::{
syntax_highlighting::{InstanceInBrackets as InstanceWithBrackets, SyntaxHighlightedBuilder},
SyntaxHighlighting as _,
};
use re_viewer_context::{contents_name_style, ContainerId, Contents, Item, ViewId, ViewerContext};
use re_viewer_context::{
contents_name_style, ContainerId, Contents, Item, TableId, ViewId, ViewerContext,
};
use re_viewport_blueprint::ViewportBlueprint;

pub fn is_component_static(ctx: &ViewerContext<'_>, component_path: &ComponentPath) -> bool {
Expand Down Expand Up @@ -47,6 +49,7 @@ impl ItemTitle {
}

Item::StoreId(store_id) => Self::from_store_id(ctx, store_id),
Item::TableId(table_id) => Self::from_table_id(ctx, table_id),

Item::InstancePath(instance_path) => {
Self::from_instance_path(ctx, style, instance_path)
Expand All @@ -73,10 +76,14 @@ impl ItemTitle {
}
}

pub fn from_table_id(_ctx: &ViewerContext<'_>, table_id: &TableId) -> Self {
Self::new(table_id.as_ref(), &icons::ENTITY_RESERVED).with_tooltip(table_id.as_ref())
}

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) {
let title = if let Some(entity_db) = ctx.storage_context.bundle.get(store_id) {
match (
entity_db.app_id(),
entity_db.recording_property::<Timestamp>(),
Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_selection_panel/src/selection_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ fn data_section_ui(item: &Item) -> Option<Box<dyn DataUi>> {
Some(Box::new(instance_path.clone()))
}
// Skip data ui since we don't know yet what to show for these.
Item::View(_) | Item::Container(_) => None,
Item::TableId(_) | Item::View(_) | Item::Container(_) => None,
}
}

Expand Down
8 changes: 5 additions & 3 deletions crates/viewer/re_view_spatial/src/ui_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,9 +561,11 @@ impl SpatialView3D {
// Track focused entity if any.
if let Some(focused_item) = ctx.focused_item {
let focused_entity = match focused_item {
Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) | Item::Container(_) => {
None
}
Item::AppId(_)
| Item::DataSource(_)
| Item::StoreId(_)
| Item::Container(_)
| Item::TableId(_) => None,

Item::View(view_id) => {
if view_id == &query.view_id {
Expand Down
4 changes: 4 additions & 0 deletions crates/viewer/re_viewer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ re_viewport.workspace = true
re_analytics = { workspace = true, optional = true }
re_view_map = { workspace = true, optional = true }

# TODO(grtlr): The following deps are for the table. They should go into a separate crate.
re_sorbet.workspace = true
egui_table.workspace = true


# External
ahash.workspace = true
Expand Down
Loading
Loading