diff --git a/CHANGELOG.md b/CHANGELOG.md index 3adff09a184..fbb48ea4d0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG ## Unreleased ### Added ⭐ +* Add `Plot::plot_hovered_indexes` Returns the index and subindex of the point of the hovered shape. +* Add `Plot::Item::allow_hover` give possibility to masked the interaction on hovered item. * `Event::Key` now has a `repeat` field that is set to `true` if the event was the result of a key-repeat ([#2435](https://github.com/emilk/egui/pull/2435)). * Add `Slider::drag_value_speed`, which lets you ask for finer precision when dragging the slider value rather than the actual slider. * Add `Memory::any_popup_open`, which returns true if any popup is currently open ([#2464](https://github.com/emilk/egui/pull/2464)). diff --git a/crates/egui/src/widgets/plot/items/mod.rs b/crates/egui/src/widgets/plot/items/mod.rs index 378f27eb35c..2fa106fb83b 100644 --- a/crates/egui/src/widgets/plot/items/mod.rs +++ b/crates/egui/src/widgets/plot/items/mod.rs @@ -45,6 +45,8 @@ pub(super) trait PlotItem { fn highlighted(&self) -> bool; + fn allow_hover(&self) -> bool; + fn geometry(&self) -> PlotGeometry<'_>; fn bounds(&self) -> PlotBounds; @@ -119,6 +121,7 @@ pub struct HLine { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) style: LineStyle, } @@ -129,6 +132,7 @@ impl HLine { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: String::default(), highlight: false, + allow_hover: true, style: LineStyle::Solid, } } @@ -139,6 +143,12 @@ impl HLine { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a stroke. pub fn stroke(mut self, stroke: impl Into) -> Self { self.stroke = stroke.into(); @@ -216,6 +226,10 @@ impl PlotItem for HLine { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -235,6 +249,7 @@ pub struct VLine { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) style: LineStyle, } @@ -245,6 +260,7 @@ impl VLine { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: String::default(), highlight: false, + allow_hover: true, style: LineStyle::Solid, } } @@ -255,6 +271,12 @@ impl VLine { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a stroke. pub fn stroke(mut self, stroke: impl Into) -> Self { self.stroke = stroke.into(); @@ -332,6 +354,10 @@ impl PlotItem for VLine { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -350,6 +376,7 @@ pub struct Line { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) fill: Option, pub(super) style: LineStyle, } @@ -361,6 +388,7 @@ impl Line { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: Default::default(), highlight: false, + allow_hover: true, fill: None, style: LineStyle::Solid, } @@ -372,6 +400,12 @@ impl Line { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a stroke. pub fn stroke(mut self, stroke: impl Into) -> Self { self.stroke = stroke.into(); @@ -502,6 +536,10 @@ impl PlotItem for Line { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.series.points()) } @@ -517,6 +555,7 @@ pub struct Polygon { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) fill_alpha: f32, pub(super) style: LineStyle, } @@ -528,6 +567,7 @@ impl Polygon { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: Default::default(), highlight: false, + allow_hover: true, fill_alpha: DEFAULT_FILL_ALPHA, style: LineStyle::Solid, } @@ -540,6 +580,12 @@ impl Polygon { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a custom stroke. pub fn stroke(mut self, stroke: impl Into) -> Self { self.stroke = stroke.into(); @@ -632,6 +678,10 @@ impl PlotItem for Polygon { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.series.points()) } @@ -648,6 +698,7 @@ pub struct Text { pub(super) position: PlotPoint, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) color: Color32, pub(super) anchor: Align2, } @@ -659,6 +710,7 @@ impl Text { position, name: Default::default(), highlight: false, + allow_hover: true, color: Color32::TRANSPARENT, anchor: Align2::CENTER_CENTER, } @@ -670,6 +722,12 @@ impl Text { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Text color. pub fn color(mut self, color: impl Into) -> Self { self.color = color.into(); @@ -746,6 +804,10 @@ impl PlotItem for Text { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -769,6 +831,7 @@ pub struct Points { pub(super) radius: f32, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) stems: Option, } @@ -782,6 +845,7 @@ impl Points { radius: 1.0, name: Default::default(), highlight: false, + allow_hover: true, stems: None, } } @@ -798,6 +862,12 @@ impl Points { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Set the marker's color. pub fn color(mut self, color: impl Into) -> Self { self.color = color.into(); @@ -984,6 +1054,10 @@ impl PlotItem for Points { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.series.points()) } @@ -1000,6 +1074,7 @@ pub struct Arrows { pub(super) color: Color32, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, } impl Arrows { @@ -1010,6 +1085,7 @@ impl Arrows { color: Color32::TRANSPARENT, name: Default::default(), highlight: false, + allow_hover: true, } } @@ -1019,6 +1095,12 @@ impl Arrows { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Set the arrows' color. pub fn color(mut self, color: impl Into) -> Self { self.color = color.into(); @@ -1099,6 +1181,10 @@ impl PlotItem for Arrows { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.origins.points()) } @@ -1118,6 +1204,7 @@ pub struct PlotImage { pub(super) bg_fill: Color32, pub(super) tint: Color32, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) name: String, } @@ -1132,6 +1219,7 @@ impl PlotImage { position: center_position, name: Default::default(), highlight: false, + allow_hover: true, texture_id: texture_id.into(), uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), size: size.into(), @@ -1146,6 +1234,12 @@ impl PlotImage { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. pub fn uv(mut self, uv: impl Into) -> Self { self.uv = uv.into(); @@ -1234,6 +1328,10 @@ impl PlotItem for PlotImage { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -1264,6 +1362,7 @@ pub struct BarChart { /// A custom element formatter pub(super) element_formatter: Option String>>, highlight: bool, + allow_hover: bool, } impl BarChart { @@ -1275,6 +1374,7 @@ impl BarChart { name: String::new(), element_formatter: None, highlight: false, + allow_hover: true, } } @@ -1336,6 +1436,12 @@ impl BarChart { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a custom way to format an element. /// Can be used to display a set number of decimals or custom labels. pub fn element_formatter(mut self, formatter: Box String>) -> Self { @@ -1395,6 +1501,10 @@ impl PlotItem for BarChart { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Rects } @@ -1434,6 +1544,7 @@ pub struct BoxPlot { /// A custom element formatter pub(super) element_formatter: Option String>>, highlight: bool, + allow_hover: bool, } impl BoxPlot { @@ -1445,6 +1556,7 @@ impl BoxPlot { name: String::new(), element_formatter: None, highlight: false, + allow_hover: true, } } @@ -1500,6 +1612,12 @@ impl BoxPlot { self } + // Allowed hovering this line in the plot. + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a custom way to format an element. /// Can be used to display a set number of decimals or custom labels. pub fn element_formatter( @@ -1538,6 +1656,10 @@ impl PlotItem for BoxPlot { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Rects } diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index bbefe4b44b7..4031f86b63e 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -7,7 +7,6 @@ use std::{ }; use crate::*; -use epaint::util::FloatOrd; use epaint::Hsva; use items::PlotItem; @@ -92,6 +91,38 @@ impl From for AxisBools { } } +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Debug)] +pub struct IndexesHovered { + pub is_in_radius: bool, + pub indexes: Option>, + pub hover_name: String, +} + +impl Default for IndexesHovered { + fn default() -> Self { + Self { + is_in_radius: false, + indexes: None, + hover_name: String::new(), + } + } +} + +impl IndexesHovered { + fn new( + is_in_radius: bool, + indexes: Option>, + hover_name: String, + ) -> Self { + Self { + is_in_radius, + indexes, + hover_name, + } + } +} + /// Information about the plot that has to persist between frames. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone)] @@ -104,6 +135,7 @@ struct PlotMemory { last_screen_transform: ScreenTransform, /// Allows to remember the first click position when performing a boxed zoom last_click_pos_for_zoom: Option, + hovered_indexes: IndexesHovered, } impl PlotMemory { @@ -739,6 +771,7 @@ impl Plot { center_y_axis, ), last_click_pos_for_zoom: None, + hovered_indexes: IndexesHovered::default(), }); let PlotMemory { @@ -747,6 +780,7 @@ impl Plot { mut hidden_items, last_screen_transform, mut last_click_pos_for_zoom, + hovered_indexes, } = memory; // Call the plot build function. @@ -756,12 +790,14 @@ impl Plot { last_screen_transform, response, ctx: ui.ctx().clone(), + hovered_indexes: hovered_indexes, }; let inner = build_fn(&mut plot_ui); let PlotUi { mut items, mut response, last_screen_transform, + hovered_indexes, .. } = plot_ui; @@ -991,8 +1027,9 @@ impl Plot { grid_spacers, sharp_grid_lines, clamp_grid, + hovered_indexes, }; - let plot_cursors = prepared.ui(ui, &response); + let (plot_cursors, hovered_indexes_shape) = prepared.ui(ui, &response); if let Some(boxed_zoom_rect) = boxed_zoom_rect { ui.painter().with_clip_rect(rect).add(boxed_zoom_rect.0); @@ -1023,6 +1060,7 @@ impl Plot { hidden_items, last_screen_transform: transform, last_click_pos_for_zoom, + hovered_indexes: hovered_indexes_shape, }; memory.store(ui.ctx(), plot_id); @@ -1044,6 +1082,7 @@ pub struct PlotUi { last_screen_transform: ScreenTransform, response: Response, ctx: Context, + hovered_indexes: IndexesHovered, } impl PlotUi { @@ -1081,6 +1120,11 @@ impl PlotUi { self.response.hovered() } + /// Returns the is_in_radius and index and subindex of shape and the name item of the hovered point in the plot. + pub fn plot_hovered_indexes(&self) -> IndexesHovered { + self.hovered_indexes.clone() + } + /// Returns `true` if the plot was clicked by the primary button. pub fn plot_clicked(&self) -> bool { self.response.clicked() @@ -1319,10 +1363,11 @@ struct PreparedPlot { grid_spacers: [GridSpacer; 2], sharp_grid_lines: bool, clamp_grid: bool, + hovered_indexes: IndexesHovered, } impl PreparedPlot { - fn ui(self, ui: &mut Ui, response: &Response) -> Vec { + fn ui(mut self, ui: &mut Ui, response: &Response) -> (Vec, IndexesHovered) { let mut axes_shapes = Vec::new(); for d in 0..2 { @@ -1350,12 +1395,22 @@ impl PreparedPlot { item.shapes(&mut plot_ui, transform, &mut shapes); } - let cursors = if let Some(pointer) = response.hover_pos() { + let (cursors, hovered_indexes, hovered_name) = if let Some(pointer) = response.hover_pos() { self.hover(ui, pointer, &mut shapes) } else { - Vec::new() + (Vec::new(), None, String::new()) }; + if hovered_indexes.is_some() { + self.hovered_indexes = IndexesHovered::new(true, hovered_indexes, hovered_name); + } else { + self.hovered_indexes = IndexesHovered::new( + false, + self.hovered_indexes.indexes, + self.hovered_indexes.hover_name, + ); + } + // Draw cursors let line_color = rulers_color(ui); @@ -1406,7 +1461,7 @@ impl PreparedPlot { } } - cursors + (cursors, self.hovered_indexes) } fn paint_axis( @@ -1558,7 +1613,12 @@ impl PreparedPlot { } } - fn hover(&self, ui: &Ui, pointer: Pos2, shapes: &mut Vec) -> Vec { + fn hover( + &self, + ui: &Ui, + pointer: Pos2, + shapes: &mut Vec, + ) -> (Vec, Option>, String) { let Self { transform, show_x, @@ -1569,21 +1629,35 @@ impl PreparedPlot { } = self; if !show_x && !show_y { - return Vec::new(); + return (Vec::new(), None, String::new()); } let interact_radius_sq: f32 = (16.0f32).powi(2); - let candidates = items.iter().filter_map(|item| { - let item = &**item; - let closest = item.find_closest(pointer, transform); - - Some(item).zip(closest) - }); + let candidates = items + .iter() + .filter(|entry| entry.allow_hover()) + .enumerate() + .filter_map(|(i, item)| { + let item = &**item; + let closest = item.find_closest(pointer, transform); + Some((i, item)).zip(closest) + }); let closest = candidates - .min_by_key(|(_, elem)| elem.dist_sq.ord()) - .filter(|(_, elem)| elem.dist_sq <= interact_radius_sq); + .clone() + .filter(|(_, elem)| elem.dist_sq <= interact_radius_sq) + .fold(None, |acc, ((i, item), elem)| { + let name = item.name().clone().to_owned(); + let elem_index = elem.index; + match acc { + None => Some((vec![(i, elem_index, name)], item, elem)), + Some((mut idx, item, elem)) => { + idx.push((i, elem_index, name)); + Some((idx, item, elem)) + } + } + }); let mut cursors = Vec::new(); @@ -1594,7 +1668,12 @@ impl PreparedPlot { show_y: *show_y, }; - if let Some((item, elem)) = closest { + let mut index_interact_radius_sq = None; + let mut name_interact_radius_sq = String::new(); + + if let Some((index_root, item, elem)) = closest { + index_interact_radius_sq = Some(index_root); + name_interact_radius_sq = item.name().to_owned(); item.on_hover(elem, shapes, &mut cursors, &plot, label_formatter); } else { let value = transform.value_from_position(pointer); @@ -1609,7 +1688,7 @@ impl PreparedPlot { ); } - cursors + (cursors, index_interact_radius_sq, name_interact_radius_sq) } } diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 7e9d88335f2..f12d31bf5e3 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -755,18 +755,45 @@ struct InteractionDemo {} impl InteractionDemo { #[allow(clippy::unused_self)] fn ui(&mut self, ui: &mut Ui) -> Response { - let plot = Plot::new("interaction_demo").height(300.0); + let plot = Plot::new("interaction_demo") + .height(300.0) + .legend(Legend::default()); let InnerResponse { response, - inner: (screen_pos, pointer_coordinate, pointer_coordinate_drag_delta, bounds, hovered), + inner: + ( + screen_pos, + pointer_coordinate, + pointer_coordinate_drag_delta, + bounds, + hovered, + hovered_indexes, + ), } = plot.show(ui, |plot_ui| { + plot_ui.line( + Line::new(PlotPoints::from_explicit_callback( + move |x| x.sin(), + .., + 100, + )) + .name("Sin"), + ); + plot_ui.line( + Line::new(PlotPoints::from_explicit_callback( + move |x| x.cos(), + .., + 200, + )) + .name("cos"), + ); ( plot_ui.screen_from_plot(PlotPoint::new(0.0, 0.0)), plot_ui.pointer_coordinate(), plot_ui.pointer_coordinate_drag_delta(), plot_ui.plot_bounds(), plot_ui.plot_hovered(), + plot_ui.plot_hovered_indexes(), ) }); @@ -794,6 +821,7 @@ impl InteractionDemo { "pointer coordinate drag delta: {}", coordinate_text )); + ui.label(format!("pointer is hovered_indexes: {:?}", hovered_indexes)); response }