Skip to content

Commit 77dbc99

Browse files
abey79emilk
andauthored
Add a tooltip to column headers with the content of the descriptor (#9947)
### What Title. TODO: - [x] validate design - [x] provide more explanation on stuff? - [x] happy with the button to copy? (note the info message feedback) cc @gavrelina <img width="287" alt="image" src="https://github.com/user-attachments/assets/5b0102f6-1b3d-408d-a2c1-c30029659b40" /> <img width="373" alt="image" src="https://github.com/user-attachments/assets/5be65caf-7868-4d97-b166-ed938af5b1d1" /> <img width="264" alt="image" src="https://github.com/user-attachments/assets/d6accc46-f98e-43a7-bde7-488f09c1c94b" /> --------- Co-authored-by: Emil Ernerfeldt <[email protected]>
1 parent 2e51730 commit 77dbc99

File tree

6 files changed

+147
-44
lines changed

6 files changed

+147
-44
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6921,6 +6921,7 @@ dependencies = [
69216921
"re_arrow_util",
69226922
"re_chunk_store",
69236923
"re_dataframe",
6924+
"re_log",
69246925
"re_log_types",
69256926
"re_sorbet",
69266927
"re_tracing",

crates/store/re_sorbet/src/index_column_descriptor.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ pub struct UnsupportedTimeType {
1414
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1515
pub struct IndexColumnDescriptor {
1616
/// The timeline this column is associated with.
17-
timeline: Timeline,
17+
pub timeline: Timeline,
1818

1919
/// The Arrow datatype of the column.
20-
datatype: ArrowDatatype,
20+
pub datatype: ArrowDatatype,
2121

2222
/// Are the indices in this column sorted?
2323
///
2424
/// `false` means either "unsorted" or "unknown".
25-
is_sorted: bool,
25+
pub is_sorted: bool,
2626
}
2727

2828
impl PartialOrd for IndexColumnDescriptor {

crates/viewer/re_dataframe_ui/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ all-features = true
2222
re_arrow_util.workspace = true
2323
re_chunk_store.workspace = true
2424
re_dataframe.workspace = true
25+
re_log.workspace = true
2526
re_log_types.workspace = true
2627
re_sorbet.workspace = true
2728
re_tracing.workspace = true

crates/viewer/re_dataframe_ui/src/datafusion_table_widget.rs

Lines changed: 141 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -393,48 +393,57 @@ impl egui_table::TableDelegate for DataFusionTableDelegate<'_> {
393393
});
394394

395395
header_ui(ui, |ui| {
396-
egui::Sides::new().show(
397-
ui,
398-
|ui| {
399-
ui.label(egui::RichText::new(name).strong().monospace());
400-
401-
if let Some(dir_icon) = current_sort_direction.map(SortDirection::icon) {
402-
ui.add_space(-5.0);
403-
ui.small_icon(
404-
dir_icon,
405-
Some(
406-
ui.design_tokens()
407-
.color(re_ui::ColorToken::blue(re_ui::Scale::S450)),
408-
),
409-
);
410-
}
411-
},
412-
|ui| {
413-
egui::containers::menu::MenuButton::from_button(
414-
ui.small_icon_button_widget(&re_ui::icons::MORE),
415-
)
416-
.ui(ui, |ui| {
417-
for sort_direction in SortDirection::iter() {
418-
let already_sorted =
419-
Some(&sort_direction) == current_sort_direction;
420-
421-
if ui
422-
.add_enabled_ui(!already_sorted, |ui| {
423-
sort_direction.menu_button(ui)
424-
})
425-
.inner
426-
.clicked()
427-
{
428-
self.new_blueprint.sort_by = Some(SortBy {
429-
column: column_name.to_owned(),
430-
direction: sort_direction,
431-
});
432-
ui.close();
433-
}
396+
egui::Sides::new()
397+
.show(
398+
ui,
399+
|ui| {
400+
let response = ui.label(egui::RichText::new(name).strong().monospace());
401+
402+
if let Some(dir_icon) = current_sort_direction.map(SortDirection::icon)
403+
{
404+
ui.add_space(-5.0);
405+
ui.small_icon(
406+
dir_icon,
407+
Some(
408+
ui.design_tokens()
409+
.color(re_ui::ColorToken::blue(re_ui::Scale::S450)),
410+
),
411+
);
434412
}
435-
});
436-
},
437-
);
413+
414+
response
415+
},
416+
|ui| {
417+
egui::containers::menu::MenuButton::from_button(
418+
ui.small_icon_button_widget(&re_ui::icons::MORE),
419+
)
420+
.ui(ui, |ui| {
421+
for sort_direction in SortDirection::iter() {
422+
let already_sorted =
423+
Some(&sort_direction) == current_sort_direction;
424+
425+
if ui
426+
.add_enabled_ui(!already_sorted, |ui| {
427+
sort_direction.menu_button(ui)
428+
})
429+
.inner
430+
.clicked()
431+
{
432+
self.new_blueprint.sort_by = Some(SortBy {
433+
column: column_name.to_owned(),
434+
direction: sort_direction,
435+
});
436+
ui.close();
437+
}
438+
}
439+
});
440+
},
441+
)
442+
.0
443+
})
444+
.inner
445+
.on_hover_ui(|ui| {
446+
column_descriptor_ui(ui, desc);
438447
});
439448
}
440449
}
@@ -481,3 +490,94 @@ impl egui_table::TableDelegate for DataFusionTableDelegate<'_> {
481490
re_ui::DesignTokens::table_line_height() + CELL_MARGIN.sum().y
482491
}
483492
}
493+
494+
fn column_descriptor_ui(ui: &mut egui::Ui, column: &ColumnDescriptorRef<'_>) {
495+
match *column {
496+
ColumnDescriptorRef::RowId(desc) => {
497+
let re_sorbet::RowIdColumnDescriptor { is_sorted } = desc;
498+
499+
header_property_ui(ui, "Type", "row id");
500+
header_property_ui(ui, "Sorted", sorted_text(*is_sorted));
501+
}
502+
ColumnDescriptorRef::Time(desc) => {
503+
let re_sorbet::IndexColumnDescriptor {
504+
timeline,
505+
datatype,
506+
is_sorted,
507+
} = desc;
508+
509+
header_property_ui(ui, "Type", "index");
510+
header_property_ui(ui, "Timeline", timeline.name());
511+
header_property_ui(ui, "Sorted", sorted_text(*is_sorted));
512+
datatype_ui(ui, &column.display_name(), datatype);
513+
}
514+
ColumnDescriptorRef::Component(desc) => {
515+
let re_sorbet::ComponentColumnDescriptor {
516+
store_datatype,
517+
component_name,
518+
entity_path,
519+
archetype_name,
520+
archetype_field_name,
521+
is_static,
522+
is_indicator,
523+
is_tombstone,
524+
is_semantically_empty,
525+
} = desc;
526+
527+
header_property_ui(ui, "Type", "component");
528+
header_property_ui(ui, "Component", component_name.full_name());
529+
header_property_ui(ui, "Entity path", entity_path.to_string());
530+
datatype_ui(ui, &column.display_name(), store_datatype);
531+
header_property_ui(
532+
ui,
533+
"Archetype",
534+
archetype_name.map(|a| a.full_name()).unwrap_or("-"),
535+
);
536+
//TODO(#9978): update this if we rename this descriptor field.
537+
header_property_ui(
538+
ui,
539+
"Archetype field",
540+
archetype_field_name.map(|a| a.as_str()).unwrap_or("-"),
541+
);
542+
header_property_ui(ui, "Static", is_static.to_string());
543+
header_property_ui(ui, "Indicator", is_indicator.to_string());
544+
header_property_ui(ui, "Tombstone", is_tombstone.to_string());
545+
header_property_ui(ui, "Empty", is_semantically_empty.to_string());
546+
}
547+
}
548+
}
549+
550+
fn sorted_text(sorted: bool) -> &'static str {
551+
if sorted { "true" } else { "unknown" }
552+
}
553+
554+
fn header_property_ui(ui: &mut egui::Ui, label: &str, value: impl AsRef<str>) {
555+
egui::Sides::new().show(ui, |ui| ui.strong(label), |ui| ui.monospace(value.as_ref()));
556+
}
557+
558+
fn datatype_ui(ui: &mut egui::Ui, column_name: &str, datatype: &arrow::datatypes::DataType) {
559+
egui::Sides::new().show(
560+
ui,
561+
|ui| ui.strong("Datatype"),
562+
|ui| {
563+
// We don't want the copy button to stand out next to the other properties. The copy
564+
// icon already indicates that it's a button.
565+
ui.visuals_mut().widgets.inactive.fg_stroke =
566+
ui.visuals_mut().widgets.noninteractive.fg_stroke;
567+
568+
if ui
569+
.add(
570+
egui::Button::image_and_text(
571+
re_ui::icons::COPY.as_image(),
572+
egui::RichText::new(re_arrow_util::format_data_type(datatype)).monospace(),
573+
)
574+
.image_tint_follows_text_color(true),
575+
)
576+
.clicked()
577+
{
578+
ui.ctx().copy_text(format!("{datatype:#?}"));
579+
re_log::info!("Copied full datatype of column `{column_name}` to clipboard");
580+
}
581+
},
582+
);
583+
}
378 Bytes
Loading

crates/viewer/re_ui/src/icons.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,4 @@ pub const SHIFT: Icon = icon_from_path!("../data/icons/shift.png");
173173
pub const CONTROL: Icon = icon_from_path!("../data/icons/control.png");
174174
pub const COMMAND: Icon = icon_from_path!("../data/icons/command.png");
175175
pub const OPTION: Icon = icon_from_path!("../data/icons/option.png");
176+
pub const COPY: Icon = icon_from_path!("../data/icons/copy.png");

0 commit comments

Comments
 (0)