From 73e8926f14c4bd1077b87a372e89eb2753d754c1 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 29 Jan 2024 19:00:12 +0100 Subject: [PATCH] wired up egui_plots update to selecting entities from plots --- Cargo.lock | 21 ++++++----- Cargo.toml | 14 ++++---- crates/re_data_ui/src/item_ui.rs | 29 ++------------- .../src/space_view_class.rs | 26 ++++++++++++-- .../src/visualizer_system.rs | 15 ++++++-- .../re_viewer_context/src/viewer_context.rs | 35 ++++++++++++++++++- 6 files changed, 89 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b68fcb5060098..07fc786ae446c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1559,7 +1559,7 @@ checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "ecolor" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "bytemuck", "serde", @@ -1568,7 +1568,7 @@ dependencies = [ [[package]] name = "eframe" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "bytemuck", "cocoa", @@ -1593,7 +1593,6 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "web-time", "wgpu", "winapi", "winit", @@ -1602,7 +1601,7 @@ dependencies = [ [[package]] name = "egui" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "accesskit", "ahash", @@ -1618,7 +1617,7 @@ dependencies = [ [[package]] name = "egui-wgpu" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "bytemuck", "egui", @@ -1635,7 +1634,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "accesskit_winit", "arboard", @@ -1664,7 +1663,7 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "egui", "ehttp", @@ -1679,7 +1678,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "bytemuck", "egui", @@ -1696,7 +1695,7 @@ dependencies = [ [[package]] name = "egui_plot" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "egui", ] @@ -1739,7 +1738,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "bytemuck", "serde", @@ -1840,7 +1839,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.25.0" -source = "git+https://github.com/emilk/egui.git?rev=ab39420c2933d2e402999043375b64e7cf0ee9ed#ab39420c2933d2e402999043375b64e7cf0ee9ed" +source = "git+https://github.com/emilk/egui.git?rev=37aa3973a2046e38c502f9fdb941c76fbd7a5f84#37aa3973a2046e38c502f9fdb941c76fbd7a5f84" dependencies = [ "ab_glyph", "ahash", diff --git a/Cargo.toml b/Cargo.toml index 1927bbb002fc2..416122ddf3ac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -277,13 +277,13 @@ debug = true # As a last resport, patch with a commit to our own repository. # ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk. -ecolor = { git = "https://github.com/emilk/egui.git", rev = "ab39420c2933d2e402999043375b64e7cf0ee9ed" } # egui master 2024-01-29 -eframe = { git = "https://github.com/emilk/egui.git", rev = "ab39420c2933d2e402999043375b64e7cf0ee9ed" } # egui master 2024-01-29 -egui = { git = "https://github.com/emilk/egui.git", rev = "ab39420c2933d2e402999043375b64e7cf0ee9ed" } # egui master 2024-01-29 -egui_extras = { git = "https://github.com/emilk/egui.git", rev = "ab39420c2933d2e402999043375b64e7cf0ee9ed" } # egui master 2024-01-29 -egui_plot = { git = "https://github.com/emilk/egui.git", rev = "ab39420c2933d2e402999043375b64e7cf0ee9ed" } # egui master 2024-01-29 -egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "ab39420c2933d2e402999043375b64e7cf0ee9ed" } # egui master 2024-01-29 -emath = { git = "https://github.com/emilk/egui.git", rev = "ab39420c2933d2e402999043375b64e7cf0ee9ed" } # egui master 2024-01-29 +ecolor = { git = "https://github.com/emilk/egui.git", rev = "37aa3973a2046e38c502f9fdb941c76fbd7a5f84" } # egui TODO: 2024-01-29 +eframe = { git = "https://github.com/emilk/egui.git", rev = "37aa3973a2046e38c502f9fdb941c76fbd7a5f84" } # egui TODO: 2024-01-29 +egui = { git = "https://github.com/emilk/egui.git", rev = "37aa3973a2046e38c502f9fdb941c76fbd7a5f84" } # egui TODO: 2024-01-29 +egui_extras = { git = "https://github.com/emilk/egui.git", rev = "37aa3973a2046e38c502f9fdb941c76fbd7a5f84" } # egui TODO: 2024-01-29 +egui_plot = { git = "https://github.com/emilk/egui.git", rev = "37aa3973a2046e38c502f9fdb941c76fbd7a5f84" } # egui TODO: 2024-01-29 +egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "37aa3973a2046e38c502f9fdb941c76fbd7a5f84" } # egui TODO: 2024-01-29 +emath = { git = "https://github.com/emilk/egui.git", rev = "37aa3973a2046e38c502f9fdb941c76fbd7a5f84" } # egui TODO: 2024-01-29 # Useful while developing: # ecolor = { path = "../../egui/crates/ecolor" } diff --git a/crates/re_data_ui/src/item_ui.rs b/crates/re_data_ui/src/item_ui.rs index dd4dfa3531556..8ef34e51ee8ef 100644 --- a/crates/re_data_ui/src/item_ui.rs +++ b/crates/re_data_ui/src/item_ui.rs @@ -6,8 +6,7 @@ use re_entity_db::{EntityTree, InstancePath}; use re_log_types::{ComponentPath, EntityPath, TimeInt, Timeline}; use re_ui::SyntaxHighlighting; use re_viewer_context::{ - DataQueryId, HoverHighlight, Item, Selection, SpaceViewId, SystemCommandSender, UiVerbosity, - ViewerContext, + DataQueryId, HoverHighlight, Item, Selection, SpaceViewId, UiVerbosity, ViewerContext, }; use super::DataUi; @@ -347,30 +346,8 @@ pub fn select_hovered_on_click( response: &egui::Response, selection: impl Into, ) { - re_tracing::profile_function!(); - - let mut selection = selection.into(); - selection.resolve_mono_instance_path_items(ctx); - let selection_state = ctx.selection_state(); - - if response.hovered() { - selection_state.set_hovered(selection.clone()); - } - - if response.double_clicked() { - if let Some((item, _)) = selection.first() { - ctx.command_sender - .send_system(re_viewer_context::SystemCommand::SetFocus(item.clone())); - } - } - - if response.clicked() { - if response.ctx.input(|i| i.modifiers.command) { - selection_state.toggle_selection(selection); - } else { - selection_state.set_selection(selection); - } - } + // TODO: inline everywhere. + ctx.select_hovered_on_click(response, selection); } /// Displays the "hover card" (i.e. big tooltip) for an instance or an entity. diff --git a/crates/re_space_view_time_series/src/space_view_class.rs b/crates/re_space_view_time_series/src/space_view_class.rs index a2f6e0f756b0b..4b21228a8fee7 100644 --- a/crates/re_space_view_time_series/src/space_view_class.rs +++ b/crates/re_space_view_time_series/src/space_view_class.rs @@ -1,4 +1,4 @@ -use egui::ahash::HashSet; +use egui::ahash::{HashMap, HashSet}; use egui_plot::{Legend, Line, Plot, Points}; use re_data_store::TimeType; @@ -353,10 +353,13 @@ impl SpaceViewClass for TimeSeriesSpaceView { plot = plot.x_grid_spacer(move |spacer| ns_grid_spacer(canvas_size, &spacer)); } + let mut plot_item_id_to_entity_path = HashMap::default(); + let egui_plot::PlotResponse { inner: time_x, response, transform, + hovered_plot_item, } = plot.show(ui, |plot_ui| { if plot_ui.response().secondary_clicked() { let mut time_ctrl_write = ctx.rec_cfg.time_ctrl.write(); @@ -376,19 +379,23 @@ impl SpaceViewClass for TimeSeriesSpaceView { .collect::>(); let color = line.color; + let id = egui::Id::new(line.entity_path.hash()); + plot_item_id_to_entity_path.insert(id, line.entity_path.clone()); match line.kind { PlotSeriesKind::Continuous => plot_ui.line( Line::new(points) .name(&line.label) .color(color) - .width(line.width), + .width(line.width) + .id(id), ), PlotSeriesKind::Scatter => plot_ui.points( Points::new(points) .name(&line.label) .color(color) - .radius(line.width), + .radius(line.width) + .id(id), ), } } @@ -416,6 +423,19 @@ impl SpaceViewClass for TimeSeriesSpaceView { .map(|x| plot_ui.screen_from_plot([x, 0.0].into()).x) }); + // Interact with the plot items (lines, scatters, etc.) + if let Some(hovered_plot_item) = hovered_plot_item { + if let Some(entity_path) = plot_item_id_to_entity_path.get(&hovered_plot_item) { + ctx.select_hovered_on_click( + &response, + re_viewer_context::Item::InstancePath( + Some(query.space_view_id), + entity_path.clone().into(), + ), + ); + } + } + if let Some(time_x) = time_x { let interact_radius = ui.style().interaction.resize_grab_radius_side; let line_rect = egui::Rect::from_x_y_ranges(time_x..=time_x, response.rect.y_range()) diff --git a/crates/re_space_view_time_series/src/visualizer_system.rs b/crates/re_space_view_time_series/src/visualizer_system.rs index bf83c34499888..7f4636fc6e4d3 100644 --- a/crates/re_space_view_time_series/src/visualizer_system.rs +++ b/crates/re_space_view_time_series/src/visualizer_system.rs @@ -1,5 +1,5 @@ use re_data_store::TimeRange; -use re_log_types::{StoreKind, TimeInt}; +use re_log_types::{EntityPath, StoreKind, TimeInt}; use re_query_cache::{MaybeCachedComponentData, QueryError}; use re_types::{ archetypes::TimeSeriesScalar, @@ -59,6 +59,7 @@ pub struct PlotSeries { pub width: f32, pub kind: PlotSeriesKind, pub points: Vec<(i64, f64)>, + pub entity_path: EntityPath, } /// A scene for a time series plot, with everything needed to render it. @@ -362,9 +363,10 @@ impl TimeSeriesSystem { width: 2.0 * points[0].attrs.radius, kind: PlotSeriesKind::Scatter, points: vec![(points[0].time, points[0].value)], + entity_path: data_result.entity_path.clone(), }); } else { - self.add_line_segments(&line_label, points); + self.add_line_segments(&line_label, points, &data_result.entity_path); } } @@ -376,7 +378,12 @@ impl TimeSeriesSystem { // A line segment is a continuous run of points with identical attributes: each time // we notice a change in attributes, we need a new line segment. #[inline(never)] // Better callstacks on crashes - fn add_line_segments(&mut self, line_label: &str, points: Vec) { + fn add_line_segments( + &mut self, + line_label: &str, + points: Vec, + entity_path: &EntityPath, + ) { re_tracing::profile_function!(); let num_points = points.len(); @@ -391,6 +398,7 @@ impl TimeSeriesSystem { PlotSeriesKind::Continuous }, points: Vec::with_capacity(num_points), + entity_path: entity_path.clone(), }; for (i, p) in points.into_iter().enumerate() { @@ -417,6 +425,7 @@ impl TimeSeriesSystem { width: 2.0 * attrs.radius, kind, points: Vec::with_capacity(num_points - i), + entity_path: entity_path.clone(), }, ); let prev_point = *prev_line.points.last().unwrap(); diff --git a/crates/re_viewer_context/src/viewer_context.rs b/crates/re_viewer_context/src/viewer_context.rs index ed79cb7fa7d00..2978c0e7ffc26 100644 --- a/crates/re_viewer_context/src/viewer_context.rs +++ b/crates/re_viewer_context/src/viewer_context.rs @@ -7,7 +7,8 @@ use re_entity_db::entity_db::EntityDb; use crate::{ query_context::DataQueryResult, AppOptions, ApplicableEntities, ApplicationSelectionState, Caches, CommandSender, ComponentUiRegistry, DataQueryId, IndicatorMatchingEntities, - PerVisualizer, Selection, SpaceViewClassRegistry, StoreContext, TimeControl, + PerVisualizer, Selection, SpaceViewClassRegistry, StoreContext, SystemCommandSender as _, + TimeControl, }; /// Common things needed by many parts of the viewer. @@ -91,6 +92,38 @@ impl<'a> ViewerContext<'a> { pub fn current_query(&self) -> re_data_store::LatestAtQuery { self.rec_cfg.time_ctrl.read().current_query() } + + /// Set hover/select/focus for a given selection based on an egui response. + pub fn select_hovered_on_click( + &self, + response: &egui::Response, + selection: impl Into, + ) { + re_tracing::profile_function!(); + + let mut selection = selection.into(); + selection.resolve_mono_instance_path_items(self); + let selection_state = self.selection_state(); + + if response.hovered() { + selection_state.set_hovered(selection.clone()); + } + + if response.double_clicked() { + if let Some((item, _)) = selection.first() { + self.command_sender + .send_system(crate::SystemCommand::SetFocus(item.clone())); + } + } + + if response.clicked() { + if response.ctx.input(|i| i.modifiers.command) { + selection_state.toggle_selection(selection); + } else { + selection_state.set_selection(selection); + } + } + } } // ----------------------------------------------------------------------------