diff --git a/crates/re_data_ui/src/item_ui.rs b/crates/re_data_ui/src/item_ui.rs index 38aaa98e031b..f22c2ca18a2b 100644 --- a/crates/re_data_ui/src/item_ui.rs +++ b/crates/re_data_ui/src/item_ui.rs @@ -2,6 +2,7 @@ //! //! TODO(andreas): This is not a `data_ui`, can this go somewhere else, shouldn't be in `re_data_ui`. +use egui::Ui; use re_data_store::InstancePath; use re_log_types::{ComponentPath, EntityPath, TimeInt, Timeline}; use re_viewer_context::{ @@ -94,31 +95,11 @@ pub fn instance_path_button_to( text: impl Into, ) -> egui::Response { let item = Item::InstancePath(space_view_id, instance_path.clone()); - let subtype_string = if instance_path.instance_key.is_splat() { - "Entity" - } else { - "Entity Instance" - }; let response = ui .selectable_label(ctx.selection().contains(&item), text) .on_hover_ui(|ui| { - ui.strong(subtype_string); - ui.label(format!("Path: {instance_path}")); - - // TODO(emilk): give data_ui an alternate "everything on this timeline" query? - // Then we can move the size view into `data_ui`. - let query = ctx.current_query(); - - if instance_path.instance_key.is_splat() { - let store = &ctx.store_db.entity_db.data_store; - let stats = store.entity_stats(query.timeline, instance_path.entity_path.hash()); - entity_stats_ui(ui, &query.timeline, &stats); - } else { - // TODO(emilk): per-component stats - } - - instance_path.data_ui(ctx, ui, UiVerbosity::Reduced, &query); + instance_hover_card_ui(ui, ctx, instance_path); }); cursor_interact_with_selectable(ctx, response, item) @@ -245,21 +226,11 @@ pub fn data_blueprint_button_to( let response = ui .selectable_label(ctx.selection().contains(&item), text) .on_hover_ui(|ui| { - data_blueprint_tooltip(ui, ctx, entity_path); + entity_hover_card_ui(ui, ctx, entity_path); }); cursor_interact_with_selectable(ctx, response, item) } -pub fn data_blueprint_tooltip( - ui: &mut egui::Ui, - ctx: &mut ViewerContext<'_>, - entity_path: &EntityPath, -) { - ui.strong("Space View Entity"); - ui.label(format!("Path: {entity_path}")); - entity_path.data_ui(ctx, ui, UiVerbosity::Reduced, &ctx.current_query()); -} - pub fn time_button( ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui, @@ -344,3 +315,44 @@ pub fn select_hovered_on_click( } } } + +/// Displays the "hover card" (i.e. big tooltip) for an instance or an entity. +/// +/// The entity hover card is displayed the provided instance path is a splat. +pub fn instance_hover_card_ui( + ui: &mut Ui, + ctx: &mut ViewerContext<'_>, + instance_path: &InstancePath, +) { + let subtype_string = if instance_path.instance_key.is_splat() { + "Entity" + } else { + "Entity Instance" + }; + ui.strong(subtype_string); + ui.label(format!("Path: {instance_path}")); + + // TODO(emilk): give data_ui an alternate "everything on this timeline" query? + // Then we can move the size view into `data_ui`. + let query = ctx.current_query(); + + if instance_path.instance_key.is_splat() { + let store = &ctx.store_db.entity_db.data_store; + let stats = store.entity_stats(query.timeline, instance_path.entity_path.hash()); + entity_stats_ui(ui, &query.timeline, &stats); + } else { + // TODO(emilk): per-component stats + } + + instance_path.data_ui(ctx, ui, UiVerbosity::Reduced, &query); +} + +/// Displays the "hover card" (i.e. big tooltip) for an entity. +pub fn entity_hover_card_ui( + ui: &mut egui::Ui, + ctx: &mut ViewerContext<'_>, + entity_path: &EntityPath, +) { + let instance_path = InstancePath::entity_splat(entity_path.clone()); + instance_hover_card_ui(ui, ctx, &instance_path); +} diff --git a/crates/re_time_panel/src/data_density_graph.rs b/crates/re_time_panel/src/data_density_graph.rs index b17884052966..b911f156da58 100644 --- a/crates/re_time_panel/src/data_density_graph.rs +++ b/crates/re_time_panel/src/data_density_graph.rs @@ -503,8 +503,9 @@ pub fn data_density_graph_ui( fn graph_color(ctx: &mut ViewerContext<'_>, item: &Item, ui: &mut egui::Ui) -> Color32 { let is_selected = ctx.selection().contains(item); if is_selected { - make_brighter(ui.visuals().selection.bg_fill) + make_brighter(ui.visuals().widgets.active.fg_stroke.color) } else { + //TODO(ab): tokenize that! Color32::from_gray(225) } } diff --git a/crates/re_time_panel/src/lib.rs b/crates/re_time_panel/src/lib.rs index 9e3277914185..62a5b491ce2e 100644 --- a/crates/re_time_panel/src/lib.rs +++ b/crates/re_time_panel/src/lib.rs @@ -11,12 +11,14 @@ mod time_ranges_ui; mod time_selection_ui; use egui::emath::Rangef; -use egui::{pos2, Color32, CursorIcon, NumExt, PointerButton, Rect, Shape, Vec2}; +use egui::{pos2, Color32, CursorIcon, NumExt, Painter, PointerButton, Rect, Shape, Ui, Vec2}; +use std::slice; use re_data_store::{EntityTree, InstancePath, TimeHistogram}; use re_data_ui::item_ui; use re_log_types::{ComponentPath, EntityPathPart, TimeInt, TimeRange, TimeReal}; -use re_viewer_context::{Item, TimeControl, TimeView, ViewerContext}; +use re_ui::list_item::{ListItem, WidthAllocationMode}; +use re_viewer_context::{HoverHighlight, Item, TimeControl, TimeView, ViewerContext}; use time_axis::TimelineAxis; use time_control_ui::TimeControlUi; @@ -115,10 +117,10 @@ impl TimePanel { // Expanded: ui.vertical(|ui| { // Add back the margin we removed from the panel: - let mut top_rop_frame = egui::Frame::default(); - top_rop_frame.inner_margin.right = margin.x; - top_rop_frame.inner_margin.bottom = margin.y; - let rop_row_rect = top_rop_frame + let mut top_row_frame = egui::Frame::default(); + top_row_frame.inner_margin.right = margin.x; + top_row_frame.inner_margin.bottom = margin.y; + let top_row_rect = top_row_frame .show(ui, |ui| { ui.horizontal(|ui| { ui.spacing_mut().interact_size = Vec2::splat(top_bar_height); @@ -131,17 +133,17 @@ impl TimePanel { // Draw separator between top bar and the rest: ui.painter().hline( - 0.0..=rop_row_rect.right(), - rop_row_rect.bottom(), + 0.0..=top_row_rect.right(), + top_row_rect.bottom(), ui.visuals().widgets.noninteractive.bg_stroke, ); ui.spacing_mut().scroll_bar_outer_margin = 4.0; // needed, because we have no panel margin on the right side. // Add extra margin on the left which was intentionally missing on the controls. - let mut top_rop_frame = egui::Frame::default(); - top_rop_frame.inner_margin.left = 8.0; - top_rop_frame.show(ui, |ui| { + let mut streams_frame = egui::Frame::default(); + streams_frame.inner_margin.left = margin.x; + streams_frame.show(ui, |ui| { self.expanded_ui(ctx, ui); }); }); @@ -202,6 +204,8 @@ impl TimePanel { // tree |streams | // | . . . . . . | // | . . . . | + // ▲ + // └ tree_max_y (= time_x_left) self.next_col_right = ui.min_rect().left(); // next_col_right will expand during the call @@ -305,7 +309,13 @@ impl TimePanel { )); // All the entity rows and their data density graphs: - self.tree_ui(ctx, &time_area_response, &lower_time_area_painter, ui); + self.tree_ui( + ctx, + &time_area_response, + &lower_time_area_painter, + time_x_left, + ui, + ); { // Paint a shadow between the stream names on the left @@ -351,6 +361,7 @@ impl TimePanel { ctx: &mut ViewerContext<'_>, time_area_response: &egui::Response, time_area_painter: &egui::Painter, + tree_max_y: f32, ui: &mut egui::Ui, ) { re_tracing::profile_function!(); @@ -362,6 +373,8 @@ impl TimePanel { // We implement drag-to-scroll manually instead! .drag_to_scroll(false) .show(ui, |ui| { + ui.spacing_mut().item_spacing.y = 0.0; // no spacing needed for ListItems + if time_area_response.dragged_by(PointerButton::Primary) { ui.scroll_with_delta(Vec2::Y * time_area_response.drag_delta().y); } @@ -369,6 +382,7 @@ impl TimePanel { ctx, time_area_response, time_area_painter, + tree_max_y, &ctx.store_db.entity_db.tree, ui, ); @@ -381,6 +395,7 @@ impl TimePanel { ctx: &mut ViewerContext<'_>, time_area_response: &egui::Response, time_area_painter: &egui::Painter, + tree_max_y: f32, last_path_part: &EntityPathPart, tree: &EntityTree, ui: &mut egui::Ui, @@ -402,21 +417,43 @@ impl TimePanel { let collapsing_header_id = ui.make_persistent_id(&tree.path); let default_open = tree.path.len() <= 1 && !tree.is_leaf(); - let (_collapsing_button_response, custom_header_response, body_returned) = - egui::collapsing_header::CollapsingState::load_with_default_open( - ui.ctx(), - collapsing_header_id, - default_open, - ) - .show_header(ui, |ui| { - item_ui::entity_path_button_to(ctx, ui, None, &tree.path, text) - }) - .body(|ui| { - self.show_children(ctx, time_area_response, time_area_painter, tree, ui); + + let item = Item::InstancePath(None, InstancePath::entity_splat(tree.path.clone())); + let is_selected = ctx.selection().contains(&item); + let is_item_hovered = + ctx.selection_state().highlight_for_ui_element(&item) == HoverHighlight::Hovered; + + let clip_rect_save = ui.clip_rect(); + let mut clip_rect = clip_rect_save; + clip_rect.max.x = tree_max_y; + ui.set_clip_rect(clip_rect); + + let re_ui::list_item::ShowCollapsingResponse { + item_response: response, + body_response, + } = ListItem::new(ctx.re_ui, text) + .width_allocation_mode(WidthAllocationMode::Compact) + .selected(is_selected) + .force_hovered(is_item_hovered) + .show_collapsing(ui, collapsing_header_id, default_open, |_, ui| { + self.show_children( + ctx, + time_area_response, + time_area_painter, + tree_max_y, + tree, + ui, + ); }); - let is_closed = body_returned.is_none(); - let response = custom_header_response.response; + ui.set_clip_rect(clip_rect_save); + + let response = response + .on_hover_ui(|ui| re_data_ui::item_ui::entity_hover_card_ui(ui, ctx, &tree.path)); + + item_ui::select_hovered_on_click(ctx, &response, slice::from_ref(&item)); + + let is_closed = body_response.is_none(); let response_rect = response.rect; self.next_col_right = self.next_col_right.max(response_rect.right()); @@ -431,33 +468,33 @@ impl TimePanel { // ---------------------------------------------- // show the data in the time area: - - if is_visible && is_closed { - let item = Item::InstancePath(None, InstancePath::entity_splat(tree.path.clone())); - - paint_streams_guide_line(ctx, &item, ui, response_rect); - - let empty = re_data_store::TimeHistogram::default(); - let num_messages_at_time = tree - .prefix_times - .get(ctx.rec_cfg.time_ctrl.timeline()) - .unwrap_or(&empty); - + if is_visible { let row_rect = Rect::from_x_y_ranges(time_area_response.rect.x_range(), response_rect.y_range()); - data_density_graph::data_density_graph_ui( - &mut self.data_density_graph_painter, - ctx, - time_area_response, - time_area_painter, - ui, - tree.num_timeless_messages(), - num_messages_at_time, - row_rect, - &self.time_ranges_ui, - &item, - ); + highlight_timeline_row(ui, ctx, time_area_painter, &item, &row_rect); + + // show the density graph only if that item is closed + if is_closed { + let empty = re_data_store::TimeHistogram::default(); + let num_messages_at_time = tree + .prefix_times + .get(ctx.rec_cfg.time_ctrl.timeline()) + .unwrap_or(&empty); + + data_density_graph::data_density_graph_ui( + &mut self.data_density_graph_painter, + ctx, + time_area_response, + time_area_painter, + ui, + tree.num_timeless_messages(), + num_messages_at_time, + row_rect, + &self.time_ranges_ui, + &item, + ); + } } } @@ -466,6 +503,7 @@ impl TimePanel { ctx: &mut ViewerContext<'_>, time_area_response: &egui::Response, time_area_painter: &egui::Painter, + tree_max_y: f32, tree: &EntityTree, ui: &mut egui::Ui, ) { @@ -474,6 +512,7 @@ impl TimePanel { ctx, time_area_response, time_area_painter, + tree_max_y, last_component, child, ui, @@ -482,7 +521,7 @@ impl TimePanel { // If this is an entity: if !tree.components.is_empty() { - let indent = ui.spacing().indent; + let clip_rect_save = ui.clip_rect(); for (component_name, data) in &tree.components { if !data.times.has_timeline(ctx.rec_cfg.time_ctrl.timeline()) @@ -492,21 +531,33 @@ impl TimePanel { } let component_path = ComponentPath::new(tree.path.clone(), *component_name); - - let response = ui - .horizontal(|ui| { - // Add some spacing to match CollapsingHeader: - ui.spacing_mut().item_spacing.x = 0.0; - let response = - ui.allocate_response(egui::vec2(indent, 0.0), egui::Sense::hover()); - ui.painter().circle_filled( - response.rect.center(), - 2.0, - ui.visuals().text_color(), - ); - item_ui::component_path_button(ctx, ui, &component_path); + let component_name = component_path.component_name.short_name(); + let item = Item::ComponentPath(component_path); + + let mut clip_rect = clip_rect_save; + clip_rect.max.x = tree_max_y; + ui.set_clip_rect(clip_rect); + + let response = ListItem::new(ctx.re_ui, component_name) + .selected(ctx.selection().contains(&item)) + .width_allocation_mode(WidthAllocationMode::Compact) + .force_hovered( + ctx.selection_state().highlight_for_ui_element(&item) + == HoverHighlight::Hovered, + ) + .with_icon_fn(|_, ui, rect, visual| { + ui.painter() + .circle_filled(rect.center(), 2.0, visual.text_color()); }) - .response; + .show(ui); + + ui.set_clip_rect(clip_rect_save); + + re_data_ui::item_ui::select_hovered_on_click( + ctx, + &response, + slice::from_ref(&item), + ); let response_rect = response.rect; @@ -534,14 +585,13 @@ impl TimePanel { }); // show the data in the time area: - let item = Item::ComponentPath(component_path); - paint_streams_guide_line(ctx, &item, ui, response_rect); - let row_rect = Rect::from_x_y_ranges( time_area_response.rect.x_range(), response_rect.y_range(), ); + highlight_timeline_row(ui, ctx, time_area_painter, &item, &row_rect); + data_density_graph::data_density_graph_ui( &mut self.data_density_graph_painter, ctx, @@ -610,6 +660,35 @@ impl TimePanel { } } +/// Draw the hovered/selected highlight background for a timeline row. +fn highlight_timeline_row( + ui: &mut Ui, + ctx: &ViewerContext<'_>, + painter: &Painter, + item: &Item, + row_rect: &Rect, +) { + let item_hovered = + ctx.selection_state().highlight_for_ui_element(item) == HoverHighlight::Hovered; + let item_selected = ctx.selection().contains(item); + let bg_color = if item_selected { + Some(ui.visuals().selection.bg_fill.gamma_multiply(0.4)) + } else if item_hovered { + Some( + ui.visuals() + .widgets + .hovered + .weak_bg_fill + .gamma_multiply(0.3), + ) + } else { + None + }; + if let Some(bg_color) = bg_color { + painter.rect_filled(*row_rect, egui::Rounding::ZERO, bg_color); + } +} + fn collapsed_time_marker_and_time(ui: &mut egui::Ui, ctx: &mut ViewerContext<'_>) { let space_needed_for_current_time = match ctx.rec_cfg.time_ctrl.timeline().typ() { re_arrow_store::TimeType::Time => 220.0, @@ -646,31 +725,6 @@ fn collapsed_time_marker_and_time(ui: &mut egui::Ui, ctx: &mut ViewerContext<'_> current_time_ui(ctx, ui); } -/// Painted behind the data density graph. -fn paint_streams_guide_line( - ctx: &mut ViewerContext<'_>, - item: &Item, - ui: &mut egui::Ui, - response_rect: Rect, -) { - let is_selected = ctx.selection().contains(item); - let is_hovered = ctx.hovered().contains(item); - - let stroke_width = if is_hovered { 1.0 } else { 0.5 }; - - let line_color = if is_selected { - ui.visuals().selection.bg_fill - } else { - ui.visuals().widgets.noninteractive.bg_stroke.color - }; - - ui.painter().hline( - response_rect.right()..=ui.max_rect().right(), - response_rect.center().y, - (stroke_width, line_color), - ); -} - fn help_button(ui: &mut egui::Ui) { // TODO(andreas): Nicer help text like on space views. re_ui::help_hover_button(ui).on_hover_text( @@ -681,7 +735,7 @@ fn help_button(ui: &mut egui::Ui) { Zoom: Ctrl/cmd + scroll, or drag up/down with secondary mouse button.\n\ Double-click to reset view.\n\ \n\ - Press spacebar to play/pause.", + Press the space bar to play/pause.", ); } diff --git a/crates/re_ui/src/list_item.rs b/crates/re_ui/src/list_item.rs index af887ea5fce4..9ac52e7c7076 100644 --- a/crates/re_ui/src/list_item.rs +++ b/crates/re_ui/src/list_item.rs @@ -1,6 +1,7 @@ use crate::{Icon, ReUi}; use egui::epaint::text::TextWrapping; use egui::{Align, Align2, Response, Shape, Ui}; +use std::default::Default; struct ListItemResponse { response: Response, @@ -16,22 +17,77 @@ pub struct ShowCollapsingResponse { pub body_response: Option, } +/// Specification of how the width of the [`ListItem`] must be allocated. +#[derive(Default, Clone, Copy, Debug)] +pub enum WidthAllocationMode { + /// Allocate the full available width. + /// + /// This mode is useful for fixed-width container, but should be avoided for dynamically-sized + /// containers as they will immediately grow to their max width. + /// + /// Examples of resulting layouts: + /// ```text + /// ◀──────available width────▶ + /// + /// ┌─────────────────────────┐ + /// normal: │▼ □ label │ + /// └─────────────────────────┘ + /// ┌─────────────────────────┐ + /// hovered: │▼ □ label ■ ■│ + /// └─────────────────────────┘ + /// ┌─────────────────────────┐ + /// normal, long label: │▼ □ a very very long lab…│ + /// └─────────────────────────┘ + /// ┌─────────────────────────┐ + /// hovered, long label: │▼ □ a very very long… ■ ■│ + /// └─────────────────────────┘ + /// ``` + /// The allocated size is always the same, and the label is truncated depending on the available + /// space, which is further reduced whenever buttons are displayed. + #[default] + Available, + + /// Allocate the width needed for the text and icon(s) (if any). + /// + /// This mode doesn't account for buttons (if any). If buttons are enabled, the label will get + /// truncated when they are displayed. + /// + /// Examples of resulting layouts: + /// ```text + /// ┌─────────┐ + /// normal: │▼ □ label│ + /// └─────────┘ + /// ┌─────────┐ + /// hovered: │▼ □ … ■ ■│ + /// └─────────┘ + /// ┌──────────────────────────┐ + /// normal, long label: │▼ □ a very very long label│ + /// └──────────────────────────┘ + /// ┌──────────────────────────┐ + /// hovered, long label: │▼ □ a very very long … ■ ■│ + /// └──────────────────────────┘ + /// ``` + Compact, +} + /// Generic widget for use in lists. /// /// Layout: /// ```text -/// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -/// ┃┌──────┐ ┌──────┐ ┌──────┐┌──────┐┃ -/// ┃│ __ │ │ │ │ ││ │┃ -/// ┃│ \/ │ │ icon │ label │ btns ││ btns │┃ -/// ┃│ │ │ │ │ ││ │┃ -/// ┃└──────┘ └──────┘ └──────┘└──────┘┃ -/// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +/// ┌───┬────────────────────────────────────────────────────────────┬───┐ +/// │ │┌──────┐ ┌──────┐ ┌──────┐┌──────┐│ │ +/// │ ││ __ │ │ │ │ ││ ││ │ +/// │ ││ \/ │ │ icon │ label │ btns ││ btns ││ │ +/// │ ││ │ │ │ │ ││ ││ │ +/// │ │└──────┘ └──────┘ └──────┘└──────┘│ │ +/// └───┴────────────────────────────────────────────────────────────┴───┘ +/// ◀───────────── allocated width (used for layout) ───────────▶ +/// ◀────────────── clip rectangle (used for highlighting) ─────────────▶ /// ``` /// /// Features: /// - selectable -/// - full span highlighting +/// - full span highlighting based on clip rectangle /// - optional icon /// - optional on-hover buttons on the right /// - optional collapsing behavior for trees @@ -57,6 +113,7 @@ pub struct ListItem<'a> { subdued: bool, force_hovered: bool, collapse_openness: Option, + width_allocation_mode: WidthAllocationMode, icon_fn: Option>, buttons_fn: Option egui::Response + 'a>>, @@ -73,6 +130,7 @@ impl<'a> ListItem<'a> { subdued: false, force_hovered: false, collapse_openness: None, + width_allocation_mode: Default::default(), icon_fn: None, buttons_fn: None, } @@ -110,6 +168,12 @@ impl<'a> ListItem<'a> { self } + /// Set the width allocation mode. + pub fn width_allocation_mode(mut self, mode: WidthAllocationMode) -> Self { + self.width_allocation_mode = mode; + self + } + /// Provide an [`Icon`] to be displayed on the left of the item. pub fn with_icon(self, icon: &'a Icon) -> Self { self.with_icon_fn(|re_ui, ui, rect, visuals| { @@ -137,7 +201,11 @@ impl<'a> ListItem<'a> { /// Provide a closure to display on-hover buttons on the right of the item. /// - /// Note that the a right to left layout is used, so the right-most button must be added first. + /// Notes: + /// - If buttons are used, the item will allocate the full available width of the parent. If the + /// enclosing UI adapts to the childrens width, it will unnecessarily grow. If buttons aren't + /// used, the item will only allocate the width needed for the text and icons if any. + /// - A right to left layout is used, so the right-most button must be added first. pub fn with_buttons( mut self, buttons: impl FnOnce(&ReUi, &mut egui::Ui) -> egui::Response + 'a, @@ -198,8 +266,47 @@ impl<'a> ListItem<'a> { 0.0 }; - let desired_size = egui::vec2(ui.available_width(), ReUi::list_item_height()); - let (rect, response) = ui.allocate_at_least(desired_size, egui::Sense::click()); + /// Compute the "ideal" desired width of the item, accounting for text and icon(s) (but not + /// buttons). + fn icons_and_label_width( + ui: &mut egui::Ui, + item: &ListItem<'_>, + collapse_extra: f32, + icon_extra: f32, + ) -> f32 { + let text_job = item.text.clone().into_text_job( + ui.style(), + egui::FontSelection::Default, + Align::LEFT, + ); + + let text_width = ui.fonts(|f| text_job.into_galley(f)).size().x; + + // The `ceil()` is needed to avoid some rounding errors which leads to text being + // truncated even though we allocated enough space. + (collapse_extra + icon_extra + text_width).ceil() + } + + let desired_width = match self.width_allocation_mode { + WidthAllocationMode::Available => ui.available_width(), + WidthAllocationMode::Compact => { + icons_and_label_width(ui, &self, collapse_extra, icon_extra) + } + }; + + let desired_size = egui::vec2(desired_width, ReUi::list_item_height()); + let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); + + // compute the full-span background rect + let mut bg_rect = rect; + bg_rect.extend_with_x(ui.clip_rect().right()); + bg_rect.extend_with_x(ui.clip_rect().left()); + + // we want to be able to select/hover the item across its full span, so we sense that and + // update the response accordingly. + let full_span_response = ui.interact(bg_rect, response.id, egui::Sense::click()); + response.clicked = full_span_response.clicked; + response.hovered = full_span_response.hovered; // override_hover should not affect the returned response let mut style_response = response.clone(); @@ -209,7 +316,7 @@ impl<'a> ListItem<'a> { let mut collapse_response = None; - if ui.is_rect_visible(rect) { + if ui.is_rect_visible(bg_rect) { let mut visuals = if self.active { ui.style() .interact_selectable(&style_response, self.selected) @@ -222,9 +329,6 @@ impl<'a> ListItem<'a> { visuals.fg_stroke.color = visuals.fg_stroke.color.gamma_multiply(0.5); } - let mut bg_rect = rect; - bg_rect.extend_with_x(ui.clip_rect().right()); - bg_rect.extend_with_x(ui.clip_rect().left()); let background_frame = ui.painter().add(egui::Shape::Noop); // Draw collapsing triangle diff --git a/crates/re_viewer/src/ui/recordings_panel.rs b/crates/re_viewer/src/ui/recordings_panel.rs index df28851be8ac..43b01f1256ad 100644 --- a/crates/re_viewer/src/ui/recordings_panel.rs +++ b/crates/re_viewer/src/ui/recordings_panel.rs @@ -80,7 +80,7 @@ fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) -> bool { } else { ctx.re_ui.list_item(app_id).active(false).show_collapsing( ui, - ui.id().with(app_id), + ui.make_persistent_id(app_id), true, |_, ui| { for store_db in store_dbs { diff --git a/crates/re_viewport/src/viewport_blueprint_ui.rs b/crates/re_viewport/src/viewport_blueprint_ui.rs index 3ad14eba78fa..047bffdaae8e 100644 --- a/crates/re_viewport/src/viewport_blueprint_ui.rs +++ b/crates/re_viewport/src/viewport_blueprint_ui.rs @@ -264,7 +264,7 @@ impl ViewportBlueprint<'_> { }) .show(ui) .on_hover_ui(|ui| { - re_data_ui::item_ui::data_blueprint_tooltip(ui, ctx, entity_path); + re_data_ui::item_ui::entity_hover_card_ui(ui, ctx, entity_path); }); item_ui::select_hovered_on_click(ctx, &response, &[item]);