From 869d556335dc43e3bf22a133df201be89a9fb113 Mon Sep 17 00:00:00 2001 From: nongiach Date: Wed, 2 Feb 2022 16:32:46 +0100 Subject: [PATCH] Plot boxed zoom with secondary mouse button (#1188) --- CHANGELOG.md | 1 + egui/src/widgets/plot/mod.rs | 77 ++++++++++++++++++++++++ egui/src/widgets/plot/transform.rs | 4 ++ egui_demo_lib/src/apps/demo/plot_demo.rs | 1 + 4 files changed, 83 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed554523..906f896f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * Added `CollapsingHeader::icon` to override the default open/close icon using a custom function. ([1147](https://github.com/emilk/egui/pull/1147)). * Added `Plot::x_axis_formatter` and `Plot::y_axis_formatter` for custom axis labels ([#1130](https://github.com/emilk/egui/pull/1130)). * Added `ui.data()`, `ctx.data()`, `ctx.options()` and `ctx.tessellation_options()` ([#1175](https://github.com/emilk/egui/pull/1175)). +* Added `Plot::allow_boxed_zoom()`, `Plot::boxed_zoom_pointer()` for boxed zooming on plots ([#1188](https://github.com/emilk/egui/pull/1188)). * Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)). ### Changed 🔧 diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 0f718c6f6..2cc54bf75 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -37,6 +37,8 @@ struct PlotMemory { hidden_items: AHashSet, min_auto_bounds: PlotBounds, last_screen_transform: ScreenTransform, + /// Allows to remember the first click position when performing a boxed zoom + last_click_pos_for_zoom: Option, } impl PlotMemory { @@ -132,6 +134,8 @@ pub struct Plot { allow_drag: bool, min_auto_bounds: PlotBounds, margin_fraction: Vec2, + allow_boxed_zoom: bool, + boxed_zoom_pointer_button: PointerButton, linked_axes: Option, min_size: Vec2, @@ -161,6 +165,8 @@ impl Plot { allow_drag: true, min_auto_bounds: PlotBounds::NOTHING, margin_fraction: Vec2::splat(0.05), + allow_boxed_zoom: true, + boxed_zoom_pointer_button: PointerButton::Secondary, linked_axes: None, min_size: Vec2::splat(64.0), @@ -247,6 +253,20 @@ impl Plot { self } + /// Whether to allow zooming in the plot by dragging out a box with the secondary mouse button. + /// + /// Default: `true`. + pub fn allow_boxed_zoom(mut self, on: bool) -> Self { + self.allow_boxed_zoom = on; + self + } + + /// Config the button pointer to use for boxed zooming. Default: `Secondary` + pub fn boxed_zoom_pointer_button(mut self, boxed_zoom_pointer_button: PointerButton) -> Self { + self.boxed_zoom_pointer_button = boxed_zoom_pointer_button; + self + } + /// Whether to allow dragging in the plot to move the bounds. Default: `true`. pub fn allow_drag(mut self, on: bool) -> Self { self.allow_drag = on; @@ -357,6 +377,8 @@ impl Plot { center_y_axis, allow_zoom, allow_drag, + allow_boxed_zoom, + boxed_zoom_pointer_button: boxed_zoom_pointer, min_auto_bounds, margin_fraction, width, @@ -414,6 +436,7 @@ impl Plot { center_x_axis, center_y_axis, ), + last_click_pos_for_zoom: None, }); // If the min bounds changed, recalculate everything. @@ -432,6 +455,7 @@ impl Plot { mut hovered_entry, mut hidden_items, last_screen_transform, + mut last_click_pos_for_zoom, .. } = memory; @@ -530,6 +554,53 @@ impl Plot { } // Zooming + let mut boxed_zoom_rect = None; + if allow_boxed_zoom { + // Save last click to allow boxed zooming + if response.drag_started() && response.dragged_by(boxed_zoom_pointer) { + // it would be best for egui that input has a memory of the last click pos because it's a common pattern + last_click_pos_for_zoom = response.hover_pos(); + } + let box_start_pos = last_click_pos_for_zoom; + let box_end_pos = response.hover_pos(); + if let (Some(box_start_pos), Some(box_end_pos)) = (box_start_pos, box_end_pos) { + // while dragging prepare a Shape and draw it later on top of the plot + if response.dragged_by(boxed_zoom_pointer) { + response = response.on_hover_cursor(CursorIcon::ZoomIn); + let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos); + boxed_zoom_rect = Some(( + epaint::RectShape::stroke( + rect, + 0.0, + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), // Outer stroke + epaint::RectShape::stroke( + rect, + 0.0, + epaint::Stroke::new(2., Color32::WHITE), + ), // Inner stroke + )); + } + // when the click is release perform the zoom + if response.drag_released() { + let box_start_pos = transform.value_from_position(box_start_pos); + let box_end_pos = transform.value_from_position(box_end_pos); + let new_bounds = PlotBounds { + min: [box_start_pos.x, box_end_pos.y], + max: [box_end_pos.x, box_start_pos.y], + }; + if new_bounds.is_valid() { + *transform.bounds_mut() = new_bounds; + auto_bounds = false; + } else { + auto_bounds = true; + } + // reset the boxed zoom state + last_click_pos_for_zoom = None; + } + } + } + if allow_zoom { if let Some(hover_pos) = response.hover_pos() { let zoom_factor = if data_aspect.is_some() { @@ -566,6 +637,11 @@ impl Plot { }; prepared.ui(ui, &response); + if let Some(boxed_zoom_rect) = boxed_zoom_rect { + ui.painter().sub_region(rect).add(boxed_zoom_rect.0); + ui.painter().sub_region(rect).add(boxed_zoom_rect.1); + } + if let Some(mut legend) = legend { ui.add(&mut legend); hidden_items = legend.get_hidden_items(); @@ -582,6 +658,7 @@ impl Plot { hidden_items, min_auto_bounds, last_screen_transform: transform, + last_click_pos_for_zoom, }; memory.store(ui.ctx(), plot_id); diff --git a/egui/src/widgets/plot/transform.rs b/egui/src/widgets/plot/transform.rs index 211818b7a..c208bc6e2 100644 --- a/egui/src/widgets/plot/transform.rs +++ b/egui/src/widgets/plot/transform.rs @@ -178,6 +178,10 @@ impl ScreenTransform { &self.bounds } + pub fn bounds_mut(&mut self) -> &mut PlotBounds { + &mut self.bounds + } + pub fn translate_bounds(&mut self, mut delta_pos: Vec2) { if self.x_centered { delta_pos.x = 0.; diff --git a/egui_demo_lib/src/apps/demo/plot_demo.rs b/egui_demo_lib/src/apps/demo/plot_demo.rs index 565660daa..fb0ab761c 100644 --- a/egui_demo_lib/src/apps/demo/plot_demo.rs +++ b/egui_demo_lib/src/apps/demo/plot_demo.rs @@ -753,6 +753,7 @@ impl super::View for PlotDemo { egui::reset_button(ui, self); ui.collapsing("Instructions", |ui| { ui.label("Pan by dragging, or scroll (+ shift = horizontal)."); + ui.label("Box zooming: Right click to zoom in and zoom out using a selection."); if cfg!(target_arch = "wasm32") { ui.label("Zoom with ctrl / ⌘ + pointer wheel, or with pinch gesture."); } else if cfg!(target_os = "macos") {