1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
//! 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.
//!
//! 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, guess_instance_path_icon};
use re_entity_db::InstancePath;
use re_log_types::EntityPathPart;
use re_ui::{icons, list_item, DesignTokens, SyntaxHighlighting, UiExt as _};
use re_viewer_context::{Contents, Item, ViewId, ViewerContext};
use re_viewport_blueprint::ViewportBlueprint;

use crate::item_title::ItemTitle;

/// We show this above each item section
pub fn item_heading_with_breadcrumbs(
    ctx: &ViewerContext<'_>,
    viewport: &ViewportBlueprint,
    ui: &mut egui::Ui,
    item: &Item,
) {
    re_tracing::profile_function!();

    ui.list_item()
        .with_height(DesignTokens::title_bar_height())
        .interactive(false)
        .selected(true)
        .show_flat(
            ui,
            list_item::CustomContent::new(|ui, _| {
                ui.spacing_mut().item_spacing.x = 4.0;

                {
                    // 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>
                {
                    let previous_style = ui.style().clone();
                    // Dimmer colors for breadcrumbs
                    let visuals = ui.visuals_mut();
                    visuals.widgets.inactive.fg_stroke.color = egui::hex_color!("#6A8CD0"); // TODO(#3133): use design tokens
                    item_bread_crumbs_ui(ctx, viewport, ui, item);
                    ui.set_style(previous_style);
                }

                // Then the full name of the main item:
                last_part_of_item_heading(ctx, viewport, ui, item);
            }),
        );
}

// Show the bread crumbs leading to (but not including) the final item.
fn item_bread_crumbs_ui(
    ctx: &ViewerContext<'_>,
    viewport: &ViewportBlueprint,
    ui: &mut egui::Ui,
    item: &Item,
) {
    match item {
        Item::AppId(_) | Item::DataSource(_) | Item::StoreId(_) => {
            // 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 {
                entity_path,
                instance,
            } = instance_path;

            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);
                }
            } else {
                // Instance path.
                // Show the full entity path, and save the `[instance_nr]` for later.
                entity_path_breadcrumbs(
                    ctx,
                    ui,
                    None,
                    &EntityPath::root(),
                    entity_path.as_slice(),
                    true,
                );
            }
        }
        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::View(view_id) => {
            if let Some(parent) = viewport.parent(&Contents::View(*view_id)) {
                viewport_breadcrumbs(ctx, viewport, ui, Contents::Container(parent));
            }
        }
        Item::DataResult(view_id, instance_path) => {
            viewport_breadcrumbs(ctx, viewport, ui, Contents::View(*view_id));

            let InstancePath {
                entity_path,
                instance,
            } = instance_path;

            if let Some(view) = viewport.view(view_id) {
                let common_ancestor = instance_path
                    .entity_path
                    .common_ancestor(&view.space_origin);

                let relative = &entity_path.as_slice()[common_ancestor.len()..];

                let is_projection = !entity_path.starts_with(&view.space_origin);
                // TODO(#4491): the projection breadcrumbs are wrong for nuscenes (but correct for arkit!),
                // at least if we consider the blueprint tree panel as "correct".
                // I fear we need to use the undocumented `DataResultNodeOrPath` and friends to match the
                // hierarchy of the blueprint tree panel.

                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,
                    );
                }
            }
        }
    }
}

// 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,
) {
    let ItemTitle {
        icon,
        label,
        label_style: _, // Intentionally ignored
        tooltip,
    } = ItemTitle::from_item(ctx, viewport, ui.style(), item);

    let with_icon = match item {
        Item::AppId { .. }
        | Item::DataSource { .. }
        | Item::Container { .. }
        | Item::View { .. }
        | Item::StoreId { .. } => true,

        Item::InstancePath { .. } | Item::DataResult { .. } | Item::ComponentPath { .. } => false,
    };

    let button = if with_icon {
        egui::Button::image_and_text(icon.as_image(), label).image_tint_follows_text_color(true)
    } else {
        egui::Button::new(label)
    };
    let mut response = ui.add(button);
    if let Some(tooltip) = tooltip {
        response = response.on_hover_text(tooltip);
    }
    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()).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);
}

pub fn separator_icon_ui(ui: &mut egui::Ui) {
    ui.add(
        icons::BREADCRUMBS_SEPARATOR
            .as_image()
            .tint(ui.visuals().text_color().gamma_multiply(0.65)),
    );
}

/// 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,
    // If we are in a view
    view_id: Option<ViewId>,
    // Everything is relative to this
    origin: &EntityPath,
    // Show crumbs for all of these
    entity_parts: &[EntityPathPart],
    include_root: bool,
) {
    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 = 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
        let icon = if view_id.is_some() {
            // Inside a 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()).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())
    } else {
        Item::from(full_entity_path)
    };
    cursor_interact_with_selectable(ctx, response, item);

    separator_icon_ui(ui);
}