Plot auto-bounds API improvement (part 1/2): clean-up (#3587)

Part 1 of 2 of adding a better API for egui_plot's auto-bounds feature.

In this PR:
- change the `Plot` builder struct field to `default_auto_bounds` (was
`auto_bounds`)
- change the `Plot` state field to `auto_bounds` (was `bounds_modified`)
- minor improvements to `Vec2b`
This commit is contained in:
Antoine Beyeler 2023-11-21 11:22:19 +01:00 committed by GitHub
parent 78a93f81f0
commit f20b7b43bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 44 additions and 32 deletions

View File

@ -81,9 +81,9 @@ const MIN_LINE_SPACING_IN_POINTS: f64 = 6.0; // TODO(emilk): large enough for a
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone)] #[derive(Clone)]
struct PlotMemory { struct PlotMemory {
/// Indicates if the user has modified the bounds, for example by moving or zooming, /// Indicates if the plot uses automatic bounds. This is disengaged whenever the user modifies
/// or if the bounds should be calculated based by included point or auto bounds. /// the bounds, for example by moving or zooming.
bounds_modified: Vec2b, auto_bounds: Vec2b,
hovered_entry: Option<String>, hovered_entry: Option<String>,
hidden_items: ahash::HashSet<String>, hidden_items: ahash::HashSet<String>,
@ -137,7 +137,7 @@ struct CursorLinkGroups(HashMap<Id, Vec<PlotFrameCursors>>);
#[derive(Clone)] #[derive(Clone)]
struct LinkedBounds { struct LinkedBounds {
bounds: PlotBounds, bounds: PlotBounds,
bounds_modified: Vec2b, auto_bounds: Vec2b,
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
@ -183,7 +183,7 @@ pub struct Plot {
allow_scroll: bool, allow_scroll: bool,
allow_double_click_reset: bool, allow_double_click_reset: bool,
allow_boxed_zoom: bool, allow_boxed_zoom: bool,
auto_bounds: Vec2b, default_auto_bounds: Vec2b,
min_auto_bounds: PlotBounds, min_auto_bounds: PlotBounds,
margin_fraction: Vec2, margin_fraction: Vec2,
boxed_zoom_pointer_button: PointerButton, boxed_zoom_pointer_button: PointerButton,
@ -225,7 +225,7 @@ impl Plot {
allow_scroll: true, allow_scroll: true,
allow_double_click_reset: true, allow_double_click_reset: true,
allow_boxed_zoom: true, allow_boxed_zoom: true,
auto_bounds: false.into(), default_auto_bounds: false.into(),
min_auto_bounds: PlotBounds::NOTHING, min_auto_bounds: PlotBounds::NOTHING,
margin_fraction: Vec2::splat(0.05), margin_fraction: Vec2::splat(0.05),
boxed_zoom_pointer_button: PointerButton::Secondary, boxed_zoom_pointer_button: PointerButton::Secondary,
@ -501,14 +501,14 @@ impl Plot {
/// Expand bounds to fit all items across the x axis, including values given by `include_x`. /// Expand bounds to fit all items across the x axis, including values given by `include_x`.
#[inline] #[inline]
pub fn auto_bounds_x(mut self) -> Self { pub fn auto_bounds_x(mut self) -> Self {
self.auto_bounds.x = true; self.default_auto_bounds.x = true;
self self
} }
/// Expand bounds to fit all items across the y axis, including values given by `include_y`. /// Expand bounds to fit all items across the y axis, including values given by `include_y`.
#[inline] #[inline]
pub fn auto_bounds_y(mut self) -> Self { pub fn auto_bounds_y(mut self) -> Self {
self.auto_bounds.y = true; self.default_auto_bounds.y = true;
self self
} }
@ -711,8 +711,8 @@ impl Plot {
allow_scroll, allow_scroll,
allow_double_click_reset, allow_double_click_reset,
allow_boxed_zoom, allow_boxed_zoom,
boxed_zoom_pointer_button: boxed_zoom_pointer, boxed_zoom_pointer_button,
auto_bounds, default_auto_bounds,
min_auto_bounds, min_auto_bounds,
margin_fraction, margin_fraction,
width, width,
@ -854,7 +854,7 @@ impl Plot {
PlotMemory::load(ui.ctx(), plot_id) PlotMemory::load(ui.ctx(), plot_id)
} }
.unwrap_or_else(|| PlotMemory { .unwrap_or_else(|| PlotMemory {
bounds_modified: false.into(), auto_bounds: true.into(),
hovered_entry: None, hovered_entry: None,
hidden_items: Default::default(), hidden_items: Default::default(),
last_plot_transform: PlotTransform::new( last_plot_transform: PlotTransform::new(
@ -867,7 +867,7 @@ impl Plot {
}); });
let PlotMemory { let PlotMemory {
mut bounds_modified, mut auto_bounds,
mut hovered_entry, mut hovered_entry,
mut hidden_items, mut hidden_items,
last_plot_transform, last_plot_transform,
@ -963,19 +963,19 @@ impl Plot {
if let Some(linked_bounds) = link_groups.0.get(id) { if let Some(linked_bounds) = link_groups.0.get(id) {
if axes.x { if axes.x {
bounds.set_x(&linked_bounds.bounds); bounds.set_x(&linked_bounds.bounds);
bounds_modified.x = linked_bounds.bounds_modified.x; auto_bounds.x = linked_bounds.auto_bounds.x;
} }
if axes.y { if axes.y {
bounds.set_y(&linked_bounds.bounds); bounds.set_y(&linked_bounds.bounds);
bounds_modified.y = linked_bounds.bounds_modified.y; auto_bounds.y = linked_bounds.auto_bounds.y;
} }
}; };
}); });
}; };
// Allow double clicking to reset to the initial bounds. // Allow double-clicking to reset to the initial bounds.
if allow_double_click_reset && response.double_clicked() { if allow_double_click_reset && response.double_clicked() {
bounds_modified = false.into(); auto_bounds = true.into();
} }
// Apply bounds modifications. // Apply bounds modifications.
@ -983,25 +983,25 @@ impl Plot {
match modification { match modification {
BoundsModification::Set(new_bounds) => { BoundsModification::Set(new_bounds) => {
bounds = new_bounds; bounds = new_bounds;
bounds_modified = true.into(); auto_bounds = false.into();
} }
BoundsModification::Translate(delta) => { BoundsModification::Translate(delta) => {
bounds.translate(delta); bounds.translate(delta);
bounds_modified = true.into(); auto_bounds = false.into();
} }
} }
} }
// Reset bounds to initial bounds if they haven't been modified. // Reset bounds to initial bounds if they haven't been modified.
if !bounds_modified.x { if auto_bounds.x {
bounds.set_x(&min_auto_bounds); bounds.set_x(&min_auto_bounds);
} }
if !bounds_modified.y { if auto_bounds.y {
bounds.set_y(&min_auto_bounds); bounds.set_y(&min_auto_bounds);
} }
let auto_x = !bounds_modified.x && (!min_auto_bounds.is_valid_x() || auto_bounds.x); let auto_x = auto_bounds.x && (!min_auto_bounds.is_valid_x() || default_auto_bounds.x);
let auto_y = !bounds_modified.y && (!min_auto_bounds.is_valid_y() || auto_bounds.y); let auto_y = auto_bounds.y && (!min_auto_bounds.is_valid_y() || default_auto_bounds.y);
// Set bounds automatically based on content. // Set bounds automatically based on content.
if auto_x || auto_y { if auto_x || auto_y {
@ -1031,7 +1031,7 @@ impl Plot {
if let Some((_, linked_axes)) = &linked_axes { if let Some((_, linked_axes)) = &linked_axes {
let change_x = linked_axes.y && !linked_axes.x; let change_x = linked_axes.y && !linked_axes.x;
transform.set_aspect_by_changing_axis(data_aspect as f64, change_x); transform.set_aspect_by_changing_axis(data_aspect as f64, change_x);
} else if auto_bounds.any() { } else if default_auto_bounds.any() {
transform.set_aspect_by_expanding(data_aspect as f64); transform.set_aspect_by_expanding(data_aspect as f64);
} else { } else {
transform.set_aspect_by_changing_axis(data_aspect as f64, false); transform.set_aspect_by_changing_axis(data_aspect as f64, false);
@ -1049,14 +1049,14 @@ impl Plot {
delta.y = 0.0; delta.y = 0.0;
} }
transform.translate_bounds(delta); transform.translate_bounds(delta);
bounds_modified = allow_drag; auto_bounds = !allow_drag;
} }
// Zooming // Zooming
let mut boxed_zoom_rect = None; let mut boxed_zoom_rect = None;
if allow_boxed_zoom { if allow_boxed_zoom {
// Save last click to allow boxed zooming // Save last click to allow boxed zooming
if response.drag_started() && response.dragged_by(boxed_zoom_pointer) { if response.drag_started() && response.dragged_by(boxed_zoom_pointer_button) {
// it would be best for egui that input has a memory of the last click pos because it's a common pattern // 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(); last_click_pos_for_zoom = response.hover_pos();
} }
@ -1064,7 +1064,7 @@ impl Plot {
let box_end_pos = response.hover_pos(); let box_end_pos = response.hover_pos();
if let (Some(box_start_pos), Some(box_end_pos)) = (box_start_pos, box_end_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 // while dragging prepare a Shape and draw it later on top of the plot
if response.dragged_by(boxed_zoom_pointer) { if response.dragged_by(boxed_zoom_pointer_button) {
response = response.on_hover_cursor(CursorIcon::ZoomIn); response = response.on_hover_cursor(CursorIcon::ZoomIn);
let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos); let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos);
boxed_zoom_rect = Some(( boxed_zoom_rect = Some((
@ -1096,7 +1096,7 @@ impl Plot {
}; };
if new_bounds.is_valid() { if new_bounds.is_valid() {
transform.set_bounds(new_bounds); transform.set_bounds(new_bounds);
bounds_modified = true.into(); auto_bounds = false.into();
} }
// reset the boxed zoom state // reset the boxed zoom state
last_click_pos_for_zoom = None; last_click_pos_for_zoom = None;
@ -1120,14 +1120,14 @@ impl Plot {
} }
if zoom_factor != Vec2::splat(1.0) { if zoom_factor != Vec2::splat(1.0) {
transform.zoom(zoom_factor, hover_pos); transform.zoom(zoom_factor, hover_pos);
bounds_modified = allow_zoom; auto_bounds = !allow_zoom;
} }
} }
if allow_scroll { if allow_scroll {
let scroll_delta = ui.input(|i| i.scroll_delta); let scroll_delta = ui.input(|i| i.scroll_delta);
if scroll_delta != Vec2::ZERO { if scroll_delta != Vec2::ZERO {
transform.translate_bounds(-scroll_delta); transform.translate_bounds(-scroll_delta);
bounds_modified = true.into(); auto_bounds = false.into();
} }
} }
} }
@ -1220,14 +1220,14 @@ impl Plot {
*id, *id,
LinkedBounds { LinkedBounds {
bounds: *transform.bounds(), bounds: *transform.bounds(),
bounds_modified, auto_bounds,
}, },
); );
}); });
} }
let memory = PlotMemory { let memory = PlotMemory {
bounds_modified, auto_bounds,
hovered_entry, hovered_entry,
hidden_items, hidden_items,
last_plot_transform: transform, last_plot_transform: transform,

View File

@ -1,5 +1,5 @@
/// Two bools, one for each axis (X and Y). /// Two bools, one for each axis (X and Y).
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Vec2b { pub struct Vec2b {
pub x: bool, pub x: bool,
@ -58,3 +58,15 @@ impl std::ops::IndexMut<usize> for Vec2b {
} }
} }
} }
impl std::ops::Not for Vec2b {
type Output = Self;
#[inline]
fn not(self) -> Self::Output {
Self {
x: !self.x,
y: !self.y,
}
}
}