use egui::Color32;
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::{SyntaxHighlighting as _, UiExt as _, icons, list_item};
use re_viewer_context::{Contents, Item, ViewId, ViewerContext};
use re_viewport_blueprint::ViewportBlueprint;
use crate::item_title::ItemTitle;
pub fn item_heading_with_breadcrumbs(
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &mut egui::Ui,
item: &Item,
) {
re_tracing::profile_function!();
let tokens = ui.tokens();
ui.list_item()
.with_height(tokens.title_bar_height())
.interactive(false)
.selected(true)
.show_flat(
ui,
list_item::CustomContent::new(|ui, _| {
ui.spacing_mut().item_spacing.x = 4.0;
let tokens = ui.tokens();
{
let visuals = ui.visuals_mut();
visuals.widgets.noninteractive.weak_bg_fill = Color32::TRANSPARENT;
visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
visuals.widgets.active.weak_bg_fill = tokens.surface_on_primary_hovered;
visuals.widgets.hovered.weak_bg_fill = tokens.surface_on_primary_hovered;
visuals.widgets.noninteractive.fg_stroke.color = tokens.icon_color_on_primary;
visuals.widgets.inactive.fg_stroke.color = tokens.icon_color_on_primary;
visuals.widgets.active.fg_stroke.color = tokens.icon_color_on_primary_hovered;
visuals.widgets.hovered.fg_stroke.color = tokens.icon_color_on_primary_hovered;
}
item_bread_crumbs_ui(ctx, viewport, ui, item);
{
let visuals = ui.visuals_mut();
visuals.widgets.noninteractive.fg_stroke.color = tokens.text_color_on_primary;
visuals.widgets.inactive.fg_stroke.color = tokens.text_color_on_primary;
visuals.widgets.active.fg_stroke.color = tokens.text_color_on_primary_hovered;
visuals.widgets.hovered.fg_stroke.color = tokens.text_color_on_primary_hovered;
}
last_part_of_item_heading(ctx, viewport, ui, item);
}),
);
}
fn item_bread_crumbs_ui(
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &mut egui::Ui,
item: &Item,
) {
match item {
Item::AppId(_)
| Item::DataSource(_)
| Item::StoreId(_)
| Item::RedapEntry(_)
| Item::RedapServer(_)
| Item::TableId(_) => {
}
Item::InstancePath(instance_path) => {
let InstancePath {
entity_path,
instance,
} = instance_path;
if instance.is_all() {
if let [all_but_last @ .., _] = entity_path.as_slice() {
entity_path_breadcrumbs(ctx, ui, None, &EntityPath::root(), all_but_last, true);
}
} else {
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);
if instance.is_all() {
if let [all_but_last @ .., _] = relative {
entity_path_breadcrumbs(
ctx,
ui,
Some(*view_id),
&common_ancestor,
all_but_last,
!is_projection,
);
}
} else {
entity_path_breadcrumbs(
ctx,
ui,
Some(*view_id),
&common_ancestor,
relative,
!is_projection,
);
}
}
}
}
}
fn last_part_of_item_heading(
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &mut egui::Ui,
item: &Item,
) {
let ItemTitle {
icon,
label,
label_style: _, tooltip,
} = ItemTitle::from_item(ctx, viewport, ui.style(), item);
let with_icon = match item {
Item::AppId { .. }
| Item::DataSource { .. }
| Item::Container { .. }
| Item::View { .. }
| Item::TableId { .. }
| Item::StoreId { .. }
| Item::RedapEntry(_)
| Item::RedapServer(_) => true,
Item::InstancePath { .. } | Item::DataResult { .. } | Item::ComponentPath { .. } => false,
};
let mut response = if with_icon {
ui.selectable_label_with_icon(
icon,
label,
ctx.is_selected_or_loading(item),
re_ui::LabelStyle::Normal,
)
} else {
ui.add(egui::Button::new(label).truncate())
};
if let Some(tooltip) = tooltip {
response = response.on_hover_text(tooltip);
}
cursor_interact_with_selectable(ctx, response, item.clone());
}
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) {
viewport_breadcrumbs(ctx, viewport, ui, parent.into());
}
let ItemTitle {
icon,
label: _, label_style: _, tooltip,
} = ItemTitle::from_contents(ctx, viewport, &contents);
let mut response = ui.add(icon.as_button());
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.tokens().icon_color_on_primary),
);
}
fn entity_path_breadcrumbs(
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
view_id: Option<ViewId>,
origin: &EntityPath,
entity_parts: &[EntityPathPart],
include_root: bool,
) {
if let [ancestry @ .., _] = entity_parts {
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 {
let icon = if view_id.is_some() {
guess_instance_path_icon(ctx, &InstancePath::from(full_entity_path.clone()))
} else {
&icons::RECORDING };
icon.as_button()
};
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);
}