Skip to content

Commit 0b0adea

Browse files
emilkWumpf
andauthored
Show first line of the docs when hovering a component name (#6609)
### What * Part of #6556 Now that we can override all kinds of components in the UI, we should also take care to explain them to the user. Previously all the user had to go on was the name. With this PR they get the first line of the docstring for the component, plus a link to the full docs on our website. This required adding a new (generated) reflection API. ![image](https://github.com/rerun-io/rerun/assets/1148717/358b1517-a36f-4b7c-8dd0-fc42f29a2e78) Best reviewed commit-by-commit. ### Related * lampsitter/egui_commonmark#54 ### Later PR * #6616 ### Checklist * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested the web demo (if applicable): * Using examples from latest `main` build: [rerun.io/viewer](https://rerun.io/viewer/pr/6609?manifest_url=https://app.rerun.io/version/main/examples_manifest.json) * Using full set of examples from `nightly` build: [rerun.io/viewer](https://rerun.io/viewer/pr/6609?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json) * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG * [x] If applicable, add a new check to the [release checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)! - [PR Build Summary](https://build.rerun.io/pr/6609) - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html) To run all checks from `main`, comment on the PR with `@rerun-bot full-check`. --------- Co-authored-by: Andreas Reich <[email protected]>
1 parent b5da55a commit 0b0adea

33 files changed

Lines changed: 1106 additions & 764 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5141,6 +5141,7 @@ dependencies = [
51415141
"criterion",
51425142
"document-features",
51435143
"itertools 0.13.0",
5144+
"nohash-hasher",
51445145
"once_cell",
51455146
"re_arrow2",
51465147
"re_case",
@@ -5283,6 +5284,7 @@ dependencies = [
52835284
"re_string_interner",
52845285
"re_tracing",
52855286
"re_types",
5287+
"re_types_core",
52865288
"re_ui",
52875289
"serde",
52885290
"slotmap",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use re_types_core::ComponentName;
2+
use re_ui::UiExt as _;
3+
use re_viewer_context::{UiLayout, ViewerContext};
4+
5+
use super::DataUi;
6+
7+
impl DataUi for ComponentName {
8+
fn data_ui(
9+
&self,
10+
ctx: &ViewerContext<'_>,
11+
ui: &mut egui::Ui,
12+
ui_layout: UiLayout,
13+
_query: &re_data_store::LatestAtQuery,
14+
_db: &re_entity_db::EntityDb,
15+
) {
16+
if ui_layout == UiLayout::List {
17+
ui.label(self.full_name());
18+
} else {
19+
ui.scope(|ui| {
20+
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
21+
ui.label(format!("Full name: {}", self.full_name()));
22+
23+
// Only show the first line of the docs:
24+
if let Some(markdown) = ctx
25+
.reflection
26+
.components
27+
.get(self)
28+
.and_then(|info| info.docstring_md.lines().next())
29+
{
30+
ui.markdown_ui(egui::Id::new(self), markdown);
31+
}
32+
33+
if let Some(url) = self.doc_url() {
34+
ui.re_hyperlink("Full documentation", url);
35+
}
36+
});
37+
}
38+
}
39+
}

crates/re_data_ui/src/instance_path.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl DataUi for InstancePath {
4242
return;
4343
};
4444

45-
let components = crate::component_list_for_ui(&components);
45+
let components = crate::sorted_component_list_for_ui(&components);
4646
let indicator_count = components
4747
.iter()
4848
.filter(|c| c.is_indicator_component())
@@ -147,6 +147,10 @@ impl DataUi for InstancePath {
147147
list_item.show_flat(ui, content)
148148
};
149149

150+
let response = response.on_hover_ui(|ui| {
151+
component_name.data_ui_recording(ctx, ui, UiLayout::Tooltip);
152+
});
153+
150154
if interactive {
151155
ctx.select_hovered_on_click(&response, item);
152156
}

crates/re_data_ui/src/item_ui.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -588,13 +588,7 @@ pub fn app_id_button_ui(
588588
);
589589

590590
let response = response.on_hover_ui(|ui| {
591-
app_id.data_ui(
592-
ctx,
593-
ui,
594-
re_viewer_context::UiLayout::Tooltip,
595-
&ctx.current_query(), // unused
596-
ctx.recording(), // unused
597-
);
591+
app_id.data_ui_recording(ctx, ui, re_viewer_context::UiLayout::Tooltip);
598592
});
599593

600594
cursor_interact_with_selectable(ctx, response, item)
@@ -615,13 +609,7 @@ pub fn data_source_button_ui(
615609
);
616610

617611
let response = response.on_hover_ui(|ui| {
618-
data_source.data_ui(
619-
ctx,
620-
ui,
621-
re_viewer_context::UiLayout::Tooltip,
622-
&ctx.current_query(),
623-
ctx.recording(), // unused
624-
);
612+
data_source.data_ui_recording(ctx, ui, re_viewer_context::UiLayout::Tooltip);
625613
});
626614

627615
cursor_interact_with_selectable(ctx, response, item)

crates/re_data_ui/src/lib.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod app_id;
1313
mod blueprint_data;
1414
mod blueprint_types;
1515
mod component;
16+
mod component_name;
1617
mod component_path;
1718
mod component_ui_registry;
1819
mod data;
@@ -40,13 +41,14 @@ pub use component_ui_registry::{add_to_registry, create_component_ui_registry};
4041
pub use image_meaning::image_meaning_for_entity;
4142

4243
/// Sort components for display in the UI.
43-
pub fn component_list_for_ui<'a>(
44+
pub fn sorted_component_list_for_ui<'a>(
4445
iter: impl IntoIterator<Item = &'a ComponentName> + 'a,
4546
) -> Vec<ComponentName> {
4647
let mut components: Vec<ComponentName> = iter.into_iter().copied().collect();
4748

48-
// Put indicator components first:
49-
components.sort_by_key(|c| (!c.is_indicator_component(), c.full_name()));
49+
// Put indicator components first.
50+
// We then sort by the short name, as that is what is shown in the UI.
51+
components.sort_by_key(|c| (!c.is_indicator_component(), c.short_name()));
5052

5153
components
5254
}
@@ -62,6 +64,11 @@ pub trait DataUi {
6264
query: &re_data_store::LatestAtQuery,
6365
db: &re_entity_db::EntityDb,
6466
);
67+
68+
/// Called [`Self::data_ui`] using the default query and recording.
69+
fn data_ui_recording(&self, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) {
70+
self.data_ui(ctx, ui, ui_layout, &ctx.current_query(), ctx.recording());
71+
}
6572
}
6673

6774
/// Similar to [`DataUi`], but for data that is related to an entity (e.g. a component).

crates/re_selection_panel/src/defaults_ui.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use std::collections::{BTreeMap, BTreeSet};
22

3-
use itertools::Itertools;
4-
53
use re_data_store::LatestAtQuery;
4+
use re_data_ui::{sorted_component_list_for_ui, DataUi as _};
65
use re_log_types::{DataCell, DataRow, EntityPath, RowId};
76
use re_types_core::ComponentName;
87
use re_ui::UiExt as _;
@@ -59,7 +58,7 @@ pub fn defaults_ui(ctx: &ViewContext<'_>, space_view: &SpaceViewBlueprint, ui: &
5958
&space_view.defaults_path,
6059
);
6160

62-
let sorted_overrides = active_defaults.iter().sorted();
61+
let sorted_overrides = sorted_component_list_for_ui(active_defaults.iter());
6362

6463
let query_context = QueryContext {
6564
viewer_ctx: ctx.viewer_ctx,
@@ -73,7 +72,7 @@ pub fn defaults_ui(ctx: &ViewContext<'_>, space_view: &SpaceViewBlueprint, ui: &
7372
re_ui::list_item::list_item_scope(ui, "defaults", |ui| {
7473
ui.spacing_mut().item_spacing.y = 0.0;
7574
for component_name in sorted_overrides {
76-
let Some(visualizer_identifier) = component_to_vis.get(component_name) else {
75+
let Some(visualizer_identifier) = component_to_vis.get(&component_name) else {
7776
continue;
7877
};
7978
let Ok(visualizer) = ctx
@@ -94,10 +93,10 @@ pub fn defaults_ui(ctx: &ViewContext<'_>, space_view: &SpaceViewBlueprint, ui: &
9493
db.store(),
9594
query,
9695
&space_view.defaults_path,
97-
[*component_name],
96+
[component_name],
9897
)
9998
.components
100-
.get(component_name)
99+
.get(&component_name)
101100
.cloned(); /* arc */
102101

103102
if let Some(component_data) = component_data {
@@ -107,7 +106,7 @@ pub fn defaults_ui(ctx: &ViewContext<'_>, space_view: &SpaceViewBlueprint, ui: &
107106
ui,
108107
db,
109108
&space_view.defaults_path,
110-
*component_name,
109+
component_name,
111110
&component_data,
112111
visualizer.as_fallback_provider(),
113112
);
@@ -122,12 +121,18 @@ pub fn defaults_ui(ctx: &ViewContext<'_>, space_view: &SpaceViewBlueprint, ui: &
122121
.action_button(&re_ui::icons::CLOSE, || {
123122
ctx.save_empty_blueprint_component_by_name(
124123
&space_view.defaults_path,
125-
*component_name,
124+
component_name,
126125
);
127126
})
128127
.value_fn(|ui, _| value_fn(ui)),
129128
)
130-
.on_hover_text(component_name.full_name());
129+
.on_hover_ui(|ui| {
130+
component_name.data_ui_recording(
131+
ctx.viewer_ctx,
132+
ui,
133+
re_viewer_context::UiLayout::Tooltip,
134+
);
135+
});
131136
}
132137
}
133138
});

crates/re_selection_panel/src/visualizer_ui.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use itertools::Itertools;
22

3-
use re_data_ui::DataUi;
3+
use re_data_ui::{sorted_component_list_for_ui, DataUi};
44
use re_entity_db::EntityDb;
55
use re_log_types::EntityPath;
66
use re_space_view::latest_at_with_blueprint_resolved_data;
@@ -123,7 +123,7 @@ fn visualizer_components(
123123
);
124124

125125
// TODO(andreas): Should we show required components in a special way?
126-
for &component in query_info.queried.iter() {
126+
for component in sorted_component_list_for_ui(query_info.queried.iter()) {
127127
if component.is_indicator_component() {
128128
continue;
129129
}
@@ -249,7 +249,9 @@ fn visualizer_components(
249249
ui,
250250
list_item::PropertyContent::new(component.short_name()).value_fn(value_fn),
251251
)
252-
.on_hover_text(component.full_name());
252+
.on_hover_ui(|ui| {
253+
component.data_ui_recording(ctx.viewer_ctx, ui, UiLayout::Tooltip);
254+
});
253255
}
254256
}
255257

crates/re_space_view_spatial/src/ui.rs

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -637,13 +637,7 @@ pub fn picking(
637637
Some(query.space_view_id),
638638
&instance_path,
639639
);
640-
instance_path.data_ui(
641-
ctx,
642-
ui,
643-
UiLayout::Tooltip,
644-
&ctx.current_query(),
645-
ctx.recording(),
646-
);
640+
instance_path.data_ui_recording(ctx, ui, UiLayout::Tooltip);
647641
})
648642
};
649643
}
@@ -720,22 +714,10 @@ fn image_hover_ui(
720714
instance_path.entity_path.clone(),
721715
re_types::components::TensorData::name(),
722716
);
723-
component_path.data_ui(
724-
ctx,
725-
ui,
726-
UiLayout::List,
727-
&ctx.current_query(),
728-
ctx.recording(),
729-
);
717+
component_path.data_ui_recording(ctx, ui, UiLayout::List);
730718
} else {
731719
// Show it all, like we do for any other thing we hover
732-
instance_path.data_ui(
733-
ctx,
734-
ui,
735-
UiLayout::List,
736-
&ctx.current_query(),
737-
ctx.recording(),
738-
);
720+
instance_path.data_ui_recording(ctx, ui, UiLayout::List);
739721
}
740722

741723
if let Some([h, w, ..]) = tensor.image_height_width_channels() {

crates/re_time_panel/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ use egui::emath::Rangef;
1717
use egui::{pos2, Color32, CursorIcon, NumExt, Painter, PointerButton, Rect, Shape, Ui, Vec2};
1818

1919
use re_context_menu::{context_menu_ui_for_item, SelectionUpdateBehavior};
20-
use re_data_ui::item_ui::guess_instance_path_icon;
2120
use re_data_ui::DataUi as _;
21+
use re_data_ui::{item_ui::guess_instance_path_icon, sorted_component_list_for_ui};
2222
use re_entity_db::{EntityTree, InstancePath, TimeHistogram};
2323
use re_log_types::{
2424
external::re_types_core::ComponentName, ComponentPath, EntityPath, EntityPathPart,
@@ -745,7 +745,7 @@ impl TimePanel {
745745

746746
// If this is an entity:
747747
if !tree.entity.components.is_empty() {
748-
for component_name in re_data_ui::component_list_for_ui(tree.entity.components.keys()) {
748+
for component_name in sorted_component_list_for_ui(tree.entity.components.keys()) {
749749
let data = &tree.entity.components[&component_name];
750750

751751
let is_static = data.is_static();

crates/re_types_builder/src/codegen/cpp/mod.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,12 @@ impl CppCodeGenerator {
174174
let mut files_to_write = GeneratedFiles::default();
175175

176176
// Generate folder contents:
177-
let ordered_objects = objects
178-
.ordered_objects(object_kind.into())
179-
.into_iter()
177+
let objects_of_kind = objects
178+
.objects_of_kind(object_kind)
180179
.filter(|obj| &obj.scope() == scope)
181180
.collect_vec();
182181

183-
for &obj in &ordered_objects {
182+
for &obj in &objects_of_kind {
184183
if let Err(err) = generate_object_files(
185184
objects,
186185
&folder_path_sdk,
@@ -196,7 +195,7 @@ impl CppCodeGenerator {
196195
for testing in [false, true] {
197196
let hash = quote! { # };
198197
let pragma_once = pragma_once();
199-
let header_file_names = ordered_objects
198+
let header_file_names = objects_of_kind
200199
.iter()
201200
.filter(|obj| obj.is_testing() == testing)
202201
.map(|obj| format!("{folder_name}/{}.hpp", obj.snake_case_name()))

0 commit comments

Comments
 (0)