Skip to main content

re_data_ui/
latest_at_instance_ui.rs

1use egui::NumExt as _;
2use re_chunk_store::UnitChunkShared;
3use re_entity_db::InstancePath;
4use re_format::format_plural_s;
5use re_log_types::{EntityPath, Instance, TimePoint};
6use re_sdk_types::ComponentIdentifier;
7use re_ui::{SyntaxHighlighting as _, UiExt as _};
8use re_viewer_context::{StoreViewContext, UiLayout};
9
10use crate::item_ui;
11
12use super::DataUi;
13
14/// All the values of a specific [`re_log_types::ComponentPath`].
15#[derive(Clone)]
16pub struct LatestAtInstanceResult<'a> {
17    /// `camera / "left" / points / #42`
18    pub entity_path: EntityPath,
19
20    /// e.g. `Points3D:color`
21    pub component: ComponentIdentifier,
22
23    /// A specific instance (e.g. point in a point cloud), or [`Instance::ALL`] of them.
24    pub instance: Instance,
25
26    pub unit: &'a UnitChunkShared,
27}
28
29impl DataUi for LatestAtInstanceResult<'_> {
30    fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) {
31        let Self {
32            entity_path,
33            component,
34            instance,
35            unit,
36        } = self.clone();
37
38        re_tracing::profile_function!(component);
39
40        ui.sanity_check();
41
42        let tokens = ui.tokens();
43
44        let engine = ctx.db.storage_engine();
45
46        let Some(component_descriptor) = engine
47            .schema()
48            .entity_component_descriptor(&entity_path, component)
49        else {
50            ui.label(format!("Entity {entity_path} has no component {component}"));
51            return;
52        };
53
54        let num_instances = unit.num_instances(component);
55
56        // in some cases, we don't want to display all instances
57        let max_row = match ui_layout {
58            UiLayout::List | UiLayout::Inline => 0,
59            UiLayout::Tooltip => num_instances.at_most(4), // includes "…x more" if any
60            UiLayout::SelectionPanel => num_instances,
61        };
62
63        // Here we enforce that exactly `max_row` rows are displayed, which means that:
64        // - For `num_instances == max_row`, then `max_row` rows are displayed.
65        // - For `num_instances == max_row + 1`, then `max_row-1` rows are displayed and "…2 more"
66        //   is appended.
67        //
68        // ┏━━━┳━━━┳━━━┳━━━┓
69        // ┃ 3 ┃ 4 ┃ 5 ┃ 6 ┃ <- num_instances
70        // ┗━━━┻━━━┻━━━┻━━━┛
71        // ┌───┬───┬───┬───┐ ┐
72        // │ x │ x │ x │ x │ │
73        // ├───┼───┼───┼───┤ │
74        // │ x │ x │ x │ x │ │
75        // ├───┼───┼───┼───┤ ├─ max_row == 4
76        // │ x │ x │ x │ x │ │
77        // ├───┼───┼───┼───┤ │
78        // │   │ x │…+2│…+3│ │
79        // └───┴───┴───┴───┘ ┘
80        let num_displayed_rows = if num_instances <= max_row {
81            num_instances
82        } else {
83            // this accounts for the "…x more" using a row and handles `num_instances == 0`
84            max_row.saturating_sub(1)
85        };
86
87        if num_instances == 1 || instance.is_specific() {
88            // Allow editing recording properties:
89            if num_instances == 1
90                && entity_path.starts_with(&EntityPath::properties())
91                && let Some(array) = unit.component_batch_raw(component)
92                && ctx.app_ctx.component_ui_registry.try_show_edit_ui(
93                    ctx,
94                    ui,
95                    re_viewer_context::EditTarget {
96                        store_id: ctx.db.store_id().clone(),
97                        timepoint: TimePoint::STATIC,
98                        entity_path: entity_path.clone(),
99                    },
100                    array.as_ref(),
101                    component_descriptor.clone(),
102                    !ui_layout.is_single_line(),
103                ) != re_viewer_context::TryShowEditUiResult::NotShown
104            {
105                return;
106            }
107
108            ctx.app_ctx.component_ui_registry.component_ui(
109                ctx,
110                ui,
111                ui_layout,
112                &entity_path,
113                &component_descriptor,
114                unit,
115                &instance,
116            );
117        } else if ui_layout.is_single_line() {
118            ui.label(format_plural_s(num_instances, "value"));
119        } else {
120            let table_style = re_ui::TableStyle::Dense;
121            ui_layout
122                .table(ui)
123                .resizable(false)
124                .cell_layout(egui::Layout::left_to_right(egui::Align::Center))
125                .column(egui_extras::Column::auto())
126                .column(egui_extras::Column::remainder())
127                .header(tokens.deprecated_table_header_height(), |mut header| {
128                    re_ui::DesignTokens::setup_table_header(&mut header);
129                    header.col(|ui| {
130                        ui.label("Index");
131                    });
132                    header.col(|ui| {
133                        ui.label(component_descriptor.display_name());
134                    });
135                })
136                .body(|mut body| {
137                    tokens.setup_table_body(&mut body, table_style);
138                    let row_height = tokens.table_row_height(table_style);
139                    body.rows(row_height, num_displayed_rows as _, |mut row| {
140                        let instance = Instance::from(row.index() as u64);
141                        row.col(|ui| {
142                            let instance_text = instance.syntax_highlighted(ui.style());
143                            if ui.is_tooltip() {
144                                // Avoids interactive tooltips,
145                                // because that means they stick around when you move your mouse
146                                ui.label(instance_text);
147                            } else {
148                                let instance_path =
149                                    InstancePath::instance(entity_path.clone(), instance);
150                                item_ui::instance_path_button_to(
151                                    ctx,
152                                    ui,
153                                    None,
154                                    &instance_path,
155                                    instance_text,
156                                );
157                            }
158                        });
159                        row.col(|ui| {
160                            ctx.app_ctx.component_ui_registry.component_ui(
161                                ctx,
162                                ui,
163                                UiLayout::List,
164                                &entity_path,
165                                &component_descriptor,
166                                unit,
167                                &instance,
168                            );
169                        });
170                    });
171                });
172
173            if num_instances > num_displayed_rows {
174                ui.label(format!(
175                    "…and {} more.",
176                    re_format::format_uint(num_instances - num_displayed_rows)
177                ));
178            }
179        }
180
181        ui.sanity_check();
182    }
183}