diff --git a/crates/viewer/re_space_view_graph/src/draw.rs b/crates/viewer/re_space_view_graph/src/draw.rs index 1694be55ba32..91166349ab9d 100644 --- a/crates/viewer/re_space_view_graph/src/draw.rs +++ b/crates/viewer/re_space_view_graph/src/draw.rs @@ -1,11 +1,13 @@ use egui::{ - Align2, Color32, FontId, Painter, Pos2, Rect, Response, Sense, Shape, Stroke, Ui, UiBuilder, - Vec2, + Align2, Color32, FontId, Label, Painter, Pos2, Rect, Response, Sense, Shape, Stroke, Ui, + UiBuilder, Vec2, }; use re_chunk::EntityPath; -use re_viewer_context::{InteractionHighlight, SpaceViewHighlights}; +use re_viewer_context::{ + HoverHighlight, InteractionHighlight, SelectionHighlight, SpaceViewHighlights, +}; -use crate::ui::draw::DrawableLabel; +use crate::ui::draw::{CircleLabel, DrawableLabel, TextLabel}; // Sorry for the pun, could not resist 😎. // On a serious note, is there no other way to create a `Sense` that does nothing? @@ -15,6 +17,49 @@ const NON_SENSE: Sense = Sense { focusable: false, }; +fn draw_circle_label( + ui: &mut Ui, + label: &CircleLabel, + _highlight: InteractionHighlight, +) -> Response { + let &CircleLabel { radius, color } = label; + let (resp, painter) = ui.allocate_painter(Vec2::splat(radius * 2.0), Sense::click()); + painter.circle( + resp.rect.center(), + radius, + color.unwrap_or_else(|| ui.style().visuals.text_color()), + Stroke::NONE, + ); + resp +} + +fn draw_text_label(ui: &mut Ui, label: &TextLabel, highlight: InteractionHighlight) -> Response { + let TextLabel { galley, frame } = label; + let visuals = &ui.style().visuals; + + let bg = match highlight.hover { + HoverHighlight::None => visuals.widgets.noninteractive.bg_fill, + HoverHighlight::Hovered => visuals.widgets.hovered.bg_fill, + }; + + let stroke = match highlight.selection { + SelectionHighlight::Selection => visuals.selection.stroke, + _ => Stroke::new(1.0, visuals.text_color()), + }; + + frame + .stroke(stroke) + .fill(bg) + .show(ui, |ui| { + ui.add( + Label::new(galley.clone()) + .selectable(false) + .sense(Sense::click()), + ) + }) + .inner +} + /// Draws a node at the given position. pub fn draw_node( ui: &mut Ui, @@ -24,7 +69,13 @@ pub fn draw_node( ) -> Response { let builder = UiBuilder::new().max_rect(Rect::from_center_size(center, node.size())); let mut node_ui = ui.new_child(builder); - node.draw(&mut node_ui, highlight) + + // TODO(grtlr): handle highlights + + match node { + DrawableLabel::Circle(label) => draw_circle_label(&mut node_ui, label, highlight), + DrawableLabel::Text(label) => draw_text_label(&mut node_ui, label, highlight), + } } /// Draws a bounding box, as well as a basic coordinate system. diff --git a/crates/viewer/re_space_view_graph/src/ui/draw/mod.rs b/crates/viewer/re_space_view_graph/src/ui/draw/mod.rs index 33c49a30caa0..50e708682348 100644 --- a/crates/viewer/re_space_view_graph/src/ui/draw/mod.rs +++ b/crates/viewer/re_space_view_graph/src/ui/draw/mod.rs @@ -1,3 +1,3 @@ mod node; -pub use node::DrawableLabel; +pub use node::{CircleLabel, TextLabel, DrawableLabel}; diff --git a/crates/viewer/re_space_view_graph/src/ui/draw/node.rs b/crates/viewer/re_space_view_graph/src/ui/draw/node.rs index 0bf4b00fbfd5..d35d034a5692 100644 --- a/crates/viewer/re_space_view_graph/src/ui/draw/node.rs +++ b/crates/viewer/re_space_view_graph/src/ui/draw/node.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use egui::{ - Color32, FontSelection, Frame, Galley, Pos2, Rect, Response, RichText, Sense, Stroke, + Color32, FontSelection, Frame, Galley, RichText, Stroke, TextWrapMode, Ui, Vec2, WidgetText, }; use re_types::ArrowString; @@ -21,26 +21,26 @@ impl DrawableLabel { pub fn from_label(ui: &Ui, label: &Label) -> Self { match label { &Label::Circle { radius, color } => Self::circle(radius, color), - Label::Text { text, color } => Self::text(ui, text.clone(), *color), + Label::Text { text, color } => Self::text(ui, text, *color), } } } pub struct TextLabel { - frame: Frame, - galley: Arc, + pub frame: Frame, + pub galley: Arc, } pub struct CircleLabel { - radius: f32, - color: Option, + pub radius: f32, + pub color: Option, } impl DrawableLabel { pub fn size(&self) -> Vec2 { match self { - DrawableLabel::Circle(CircleLabel { radius, .. }) => Vec2::splat(radius * 2.0), - DrawableLabel::Text(TextLabel { galley, frame }) => { + Self::Circle(CircleLabel { radius, .. }) => Vec2::splat(radius * 2.0), + Self::Text(TextLabel { galley, frame }) => { frame.inner_margin.sum() + galley.size() + Vec2::splat(frame.stroke.width * 2.0) } } @@ -57,7 +57,7 @@ impl DrawableLabel { }) } - pub fn text(ui: &Ui, text: ArrowString, color: Option) -> Self { + pub fn text(ui: &Ui, text: &ArrowString, color: Option) -> Self { let galley = WidgetText::from( RichText::new(text.to_string()) .color(color.unwrap_or_else(|| ui.style().visuals.text_color())), @@ -76,34 +76,6 @@ impl DrawableLabel { Self::Text(TextLabel { frame, galley }) } - - pub fn draw(&self, ui: &mut Ui, highlight: InteractionHighlight) -> Response { - // TODO(grtlr): handle highlights - let sense = Sense::drag(); - match self { - Self::Circle(CircleLabel { radius, color }) => { - let (resp, painter) = ui.allocate_painter(Vec2::splat(radius * 2.0), sense); - painter.circle( - resp.rect.center(), - *radius, - color.unwrap_or_else(|| ui.style().visuals.text_color()), - Stroke::NONE, - ); - resp - } - Self::Text(TextLabel { galley, frame }) => { - frame - .show(ui, |ui| { - ui.add( - egui::Label::new(galley.clone()) - .selectable(false) - .sense(sense), - ) - }) - .response - } - } - } } // /// The `world_to_ui_scale` parameter is used to convert between world and ui coordinates. diff --git a/crates/viewer/re_space_view_graph/src/view.rs b/crates/viewer/re_space_view_graph/src/view.rs index 63e722f0210c..38cef2fb38ad 100644 --- a/crates/viewer/re_space_view_graph/src/view.rs +++ b/crates/viewer/re_space_view_graph/src/view.rs @@ -21,7 +21,7 @@ use re_viewport_blueprint::ViewProperty; use crate::{ draw::{draw_debug, draw_edge, draw_entity_rect, draw_node}, - graph::Graph, + graph::{Graph, Node}, layout::LayoutRequest, ui::GraphSpaceViewState, visualizers::{merge, EdgesVisualizer, NodeVisualizer}, @@ -176,15 +176,40 @@ Display a graph of nodes and edges. let mut world_bounding_rect = egui::Rect::NOTHING; for graph in &graphs { + let entity_path = graph.entity(); + let entity_highlights = query.highlights.entity_highlight(entity_path.hash()); + // For now we compute the entity rectangles on the fly. let mut current_rect = egui::Rect::NOTHING; for node in graph.nodes() { let center = layout.get_node(&node.id()).center(); - // TODO(grtlr): Add proper highlights here: - let resp = draw_node(ui, center, node.label(), Default::default()); - current_rect = current_rect.union(resp.rect); + let response = match node { + Node::Explicit { instance, .. } => { + let highlight = entity_highlights.index_highlight(*instance); + let response = + draw_node(ui, center, node.label(), highlight); + + let instance_path = + InstancePath::instance(entity_path.clone(), *instance); + ctx.select_hovered_on_click( + &response, + vec![( + Item::DataResult(query.space_view_id, instance_path), + None, + )] + .into_iter(), + ); + response + } + Node::Implicit { .. } => { + draw_node(ui, center, node.label(), Default::default()) + } + }; + + // TODO(grtlr): handle tooltips + current_rect = current_rect.union(response.rect); } for edge in graph.edges() { @@ -195,7 +220,6 @@ Display a graph of nodes and edges. // We only show entity rects if there are multiple entities. if graphs.len() > 1 { - let entity_path = graph.entity(); let resp = draw_entity_rect(ui, current_rect, entity_path, &query.highlights);