From b52299a8b818cb2c9bc0650f712fde09808d9e4b Mon Sep 17 00:00:00 2001 From: Mick Harrigan Date: Mon, 8 Sep 2025 17:44:13 -0700 Subject: [PATCH 1/9] basic attempt at single dimension zoom --- egui_plot/src/lib.rs | 108 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index c597d257..620d94d9 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -1176,22 +1176,79 @@ impl<'a> Plot<'a> { 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_button) { + const SINGLE_THRESHOLD: f32 = 0.5; + const SQUARE_THRESHOLD: f32 = 0.5; response = response.on_hover_cursor(CursorIcon::ZoomIn); + // if the rect is "nearly" square or the smaller dimension is > some threshold => rect + // else draw 2 bars on either side. 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), - egui::StrokeKind::Middle, - ), // Outer stroke - epaint::RectShape::stroke( - rect, - 0.0, - epaint::Stroke::new(2., Color32::WHITE), - egui::StrokeKind::Middle, - ), // Inner stroke - )); + let min_dimension = rect.width().min(rect.height()) < SINGLE_THRESHOLD; + let nearly_square = (rect.aspect_ratio() - 1.0).abs() > SQUARE_THRESHOLD; + let is_single_dimension = min_dimension || nearly_square; + boxed_zoom_rect = if is_single_dimension { + rect.width().partial_cmp(&rect.height()).and_then(|ord| { + match ord { + Ordering::Less => { + // width < height => vertical selection -> horizontal bars + Some(BoxedZoomType::Bars( + epaint::Shape::line_segment( + [rect.left_top(), rect.right_top()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.left_bottom(), rect.right_bottom()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.left_top(), rect.right_top()], + epaint::Stroke::new(2., Color32::WHITE), + ), + epaint::Shape::line_segment( + [rect.left_bottom(), rect.right_bottom()], + epaint::Stroke::new(2., Color32::WHITE), + ), + )) + } + Ordering::Greater => { + // width > height => horizontal selection -> vertical bars + Some(BoxedZoomType::Bars( + epaint::Shape::line_segment( + [rect.left_top(), rect.left_bottom()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.right_top(), rect.right_bottom()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.left_top(), rect.left_bottom()], + epaint::Stroke::new(2., Color32::WHITE), + ), + epaint::Shape::line_segment( + [rect.right_top(), rect.right_bottom()], + epaint::Stroke::new(2., Color32::WHITE), + ), + )) + } + Ordering::Equal => None, + } + }) + } else { + Some(BoxedZoomType::Rect( + epaint::RectShape::stroke( + rect, + 0.0, + epaint::Stroke::new(4., Color32::DARK_BLUE), + egui::StrokeKind::Middle, + ), // Outer stroke + epaint::RectShape::stroke( + rect, + 0.0, + epaint::Stroke::new(2., Color32::WHITE), + egui::StrokeKind::Middle, + ), // Inner stroke + )) + }; } // when the click is release perform the zoom if response.drag_stopped() { @@ -1318,12 +1375,18 @@ impl<'a> Plot<'a> { let (plot_cursors, mut hovered_plot_item) = prepared.ui(ui, &response); if let Some(boxed_zoom_rect) = boxed_zoom_rect { - ui.painter() - .with_clip_rect(plot_rect) - .add(boxed_zoom_rect.0); - ui.painter() - .with_clip_rect(plot_rect) - .add(boxed_zoom_rect.1); + match boxed_zoom_rect { + BoxedZoomType::Rect(rect_shape, rect_shape1) => { + ui.painter().with_clip_rect(plot_rect).add(rect_shape); + ui.painter().with_clip_rect(plot_rect).add(rect_shape1); + } + BoxedZoomType::Bars(shape, shape1, shape2, shape3) => { + ui.painter().with_clip_rect(plot_rect).add(shape); + ui.painter().with_clip_rect(plot_rect).add(shape1); + ui.painter().with_clip_rect(plot_rect).add(shape2); + ui.painter().with_clip_rect(plot_rect).add(shape3); + } + } } if let Some(mut legend) = legend { @@ -1382,6 +1445,11 @@ impl<'a> Plot<'a> { } } +enum BoxedZoomType { + Rect(epaint::RectShape, epaint::RectShape), + Bars(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), +} + /// Returns the rect left after adding axes. fn axis_widgets<'a>( mem: Option<&PlotMemory>, From fcba4fc6519f6d888fb40035cbf9c9f33857a2c6 Mon Sep 17 00:00:00 2001 From: Mick Harrigan Date: Tue, 9 Sep 2025 10:02:03 -0700 Subject: [PATCH 2/9] new method --- egui_plot/src/lib.rs | 148 +++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 70 deletions(-) diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index 620d94d9..1008a886 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -1176,79 +1176,87 @@ impl<'a> Plot<'a> { 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_button) { - const SINGLE_THRESHOLD: f32 = 0.5; - const SQUARE_THRESHOLD: f32 = 0.5; + // as soon as there is a drag, add a buffer centered around `box_start_pos` in the `min` direction + // on the near and far ends of `rect` + // + // if `min` > (buffer/2) || aspect_ratio ~= 1 => rect zoom + // otherwise => single zoom + + // random values for testing + // "buffer" on `min` before rect should be used + // NOTE: May want to be based on the axes? + const BUFFER: f32 = 100.0; + // if the ratio between `min` and `max` is about 10% different or less, its "square" + const SQUARENESS_THRESHOLD: f32 = 0.1; + response = response.on_hover_cursor(CursorIcon::ZoomIn); - // if the rect is "nearly" square or the smaller dimension is > some threshold => rect - // else draw 2 bars on either side. let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos); - let min_dimension = rect.width().min(rect.height()) < SINGLE_THRESHOLD; - let nearly_square = (rect.aspect_ratio() - 1.0).abs() > SQUARE_THRESHOLD; - let is_single_dimension = min_dimension || nearly_square; - boxed_zoom_rect = if is_single_dimension { - rect.width().partial_cmp(&rect.height()).and_then(|ord| { - match ord { - Ordering::Less => { - // width < height => vertical selection -> horizontal bars - Some(BoxedZoomType::Bars( - epaint::Shape::line_segment( - [rect.left_top(), rect.right_top()], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [rect.left_bottom(), rect.right_bottom()], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [rect.left_top(), rect.right_top()], - epaint::Stroke::new(2., Color32::WHITE), - ), - epaint::Shape::line_segment( - [rect.left_bottom(), rect.right_bottom()], - epaint::Stroke::new(2., Color32::WHITE), - ), - )) - } - Ordering::Greater => { - // width > height => horizontal selection -> vertical bars - Some(BoxedZoomType::Bars( - epaint::Shape::line_segment( - [rect.left_top(), rect.left_bottom()], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [rect.right_top(), rect.right_bottom()], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [rect.left_top(), rect.left_bottom()], - epaint::Stroke::new(2., Color32::WHITE), - ), - epaint::Shape::line_segment( - [rect.right_top(), rect.right_bottom()], - epaint::Stroke::new(2., Color32::WHITE), - ), - )) - } - Ordering::Equal => None, + + let Vec2 { x, y } = rect.size(); + if let Some((min, is_width_min)) = x.partial_cmp(&y).and_then(|ord| match ord { + Ordering::Less => Some((x, true)), + Ordering::Greater => Some((y, false)), + Ordering::Equal => None, + }) { + boxed_zoom_rect = if min > BUFFER + || (rect.aspect_ratio() - 1.0).abs() < SQUARENESS_THRESHOLD + { + Some(BoxedZoomType::Rect( + epaint::RectShape::stroke( + rect, + 0.0, + epaint::Stroke::new(4., Color32::DARK_BLUE), + egui::StrokeKind::Middle, + ), // Outer stroke + epaint::RectShape::stroke( + rect, + 0.0, + epaint::Stroke::new(2., Color32::WHITE), + egui::StrokeKind::Middle, + ), // Inner stroke + )) + } else { + if is_width_min { + Some(BoxedZoomType::Bars( + epaint::Shape::line_segment( + [rect.left_top(), rect.right_top()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.left_bottom(), rect.right_bottom()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.left_top(), rect.right_top()], + epaint::Stroke::new(2., Color32::WHITE), + ), + epaint::Shape::line_segment( + [rect.left_bottom(), rect.right_bottom()], + epaint::Stroke::new(2., Color32::WHITE), + ), + )) + } else { + Some(BoxedZoomType::Bars( + epaint::Shape::line_segment( + [rect.left_top(), rect.left_bottom()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.right_top(), rect.right_bottom()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.left_top(), rect.left_bottom()], + epaint::Stroke::new(2., Color32::WHITE), + ), + epaint::Shape::line_segment( + [rect.right_top(), rect.right_bottom()], + epaint::Stroke::new(2., Color32::WHITE), + ), + )) } - }) - } else { - Some(BoxedZoomType::Rect( - epaint::RectShape::stroke( - rect, - 0.0, - epaint::Stroke::new(4., Color32::DARK_BLUE), - egui::StrokeKind::Middle, - ), // Outer stroke - epaint::RectShape::stroke( - rect, - 0.0, - epaint::Stroke::new(2., Color32::WHITE), - egui::StrokeKind::Middle, - ), // Inner stroke - )) - }; + }; + } } // when the click is release perform the zoom if response.drag_stopped() { From 414458c589a5a6ad000ef72b0a83b01eb9e48e67 Mon Sep 17 00:00:00 2001 From: Mick Harrigan Date: Tue, 9 Sep 2025 10:21:12 -0700 Subject: [PATCH 3/9] fix shapes --- egui_plot/src/lib.rs | 91 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 11 deletions(-) diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index 1008a886..2cf6a19c 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -1193,12 +1193,12 @@ impl<'a> Plot<'a> { let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos); let Vec2 { x, y } = rect.size(); - if let Some((min, is_width_min)) = x.partial_cmp(&y).and_then(|ord| match ord { + if let Some((min, is_vertical)) = x.partial_cmp(&y).and_then(|ord| match ord { Ordering::Less => Some((x, true)), Ordering::Greater => Some((y, false)), Ordering::Equal => None, }) { - boxed_zoom_rect = if min > BUFFER + boxed_zoom_rect = if min > (BUFFER / 2.0) || (rect.aspect_ratio() - 1.0).abs() < SQUARENESS_THRESHOLD { Some(BoxedZoomType::Rect( @@ -1216,41 +1216,110 @@ impl<'a> Plot<'a> { ), // Inner stroke )) } else { - if is_width_min { + if is_vertical { + let (top_center, bottom_center) = if box_start_pos.y > box_end_pos.y + { + ( + box_start_pos, + Pos2 { + x: box_start_pos.x, + y: box_start_pos.y - rect.height(), + }, + ) + } else { + ( + Pos2 { + x: box_start_pos.x, + y: box_start_pos.y + rect.height(), + }, + box_start_pos, + ) + }; + let top_left = Pos2 { + x: top_center.x - (BUFFER / 2.0), + y: top_center.y, + }; + let top_right = Pos2 { + x: top_center.x + (BUFFER / 2.0), + y: top_center.y, + }; + let bottom_left = Pos2 { + x: bottom_center.x - (BUFFER / 2.0), + y: bottom_center.y, + }; + let bottom_right = Pos2 { + x: bottom_center.x + (BUFFER / 2.0), + y: bottom_center.y, + }; + Some(BoxedZoomType::Bars( epaint::Shape::line_segment( - [rect.left_top(), rect.right_top()], + [top_left, top_right], epaint::Stroke::new(4., Color32::DARK_BLUE), ), epaint::Shape::line_segment( - [rect.left_bottom(), rect.right_bottom()], + [bottom_left, bottom_right], epaint::Stroke::new(4., Color32::DARK_BLUE), ), epaint::Shape::line_segment( - [rect.left_top(), rect.right_top()], + [top_left, top_right], epaint::Stroke::new(2., Color32::WHITE), ), epaint::Shape::line_segment( - [rect.left_bottom(), rect.right_bottom()], + [bottom_left, bottom_right], epaint::Stroke::new(2., Color32::WHITE), ), )) } else { + let (left_center, right_center) = if box_start_pos.x > box_end_pos.x + { + ( + Pos2 { + x: box_start_pos.x - rect.width(), + y: box_start_pos.y, + }, + box_start_pos, + ) + } else { + ( + box_start_pos, + Pos2 { + x: box_start_pos.x + rect.width(), + y: box_start_pos.y, + }, + ) + }; + let top_left = Pos2 { + x: left_center.x, + y: left_center.y + (BUFFER / 2.0), + }; + let bottom_left = Pos2 { + x: left_center.x, + y: left_center.y - (BUFFER / 2.0), + }; + let top_right = Pos2 { + x: right_center.x, + y: right_center.y + (BUFFER / 2.0), + }; + let bottom_right = Pos2 { + x: right_center.x, + y: right_center.y - (BUFFER / 2.0), + }; Some(BoxedZoomType::Bars( epaint::Shape::line_segment( - [rect.left_top(), rect.left_bottom()], + [top_left, bottom_left], epaint::Stroke::new(4., Color32::DARK_BLUE), ), epaint::Shape::line_segment( - [rect.right_top(), rect.right_bottom()], + [top_right, bottom_right], epaint::Stroke::new(4., Color32::DARK_BLUE), ), epaint::Shape::line_segment( - [rect.left_top(), rect.left_bottom()], + [top_left, bottom_left], epaint::Stroke::new(2., Color32::WHITE), ), epaint::Shape::line_segment( - [rect.right_top(), rect.right_bottom()], + [top_right, bottom_right], epaint::Stroke::new(2., Color32::WHITE), ), )) From 48d2a3d03f0d03193b280c84226a9ff2bce0a087 Mon Sep 17 00:00:00 2001 From: Mick Harrigan Date: Tue, 9 Sep 2025 11:41:01 -0700 Subject: [PATCH 4/9] reorg function --- egui_plot/src/lib.rs | 256 ++++++++++++++++++------------------------- 1 file changed, 107 insertions(+), 149 deletions(-) diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index 2cf6a19c..4ac182eb 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -1176,156 +1176,8 @@ impl<'a> Plot<'a> { 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_button) { - // as soon as there is a drag, add a buffer centered around `box_start_pos` in the `min` direction - // on the near and far ends of `rect` - // - // if `min` > (buffer/2) || aspect_ratio ~= 1 => rect zoom - // otherwise => single zoom - - // random values for testing - // "buffer" on `min` before rect should be used - // NOTE: May want to be based on the axes? - const BUFFER: f32 = 100.0; - // if the ratio between `min` and `max` is about 10% different or less, its "square" - const SQUARENESS_THRESHOLD: f32 = 0.1; - response = response.on_hover_cursor(CursorIcon::ZoomIn); - let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos); - - let Vec2 { x, y } = rect.size(); - if let Some((min, is_vertical)) = x.partial_cmp(&y).and_then(|ord| match ord { - Ordering::Less => Some((x, true)), - Ordering::Greater => Some((y, false)), - Ordering::Equal => None, - }) { - boxed_zoom_rect = if min > (BUFFER / 2.0) - || (rect.aspect_ratio() - 1.0).abs() < SQUARENESS_THRESHOLD - { - Some(BoxedZoomType::Rect( - epaint::RectShape::stroke( - rect, - 0.0, - epaint::Stroke::new(4., Color32::DARK_BLUE), - egui::StrokeKind::Middle, - ), // Outer stroke - epaint::RectShape::stroke( - rect, - 0.0, - epaint::Stroke::new(2., Color32::WHITE), - egui::StrokeKind::Middle, - ), // Inner stroke - )) - } else { - if is_vertical { - let (top_center, bottom_center) = if box_start_pos.y > box_end_pos.y - { - ( - box_start_pos, - Pos2 { - x: box_start_pos.x, - y: box_start_pos.y - rect.height(), - }, - ) - } else { - ( - Pos2 { - x: box_start_pos.x, - y: box_start_pos.y + rect.height(), - }, - box_start_pos, - ) - }; - let top_left = Pos2 { - x: top_center.x - (BUFFER / 2.0), - y: top_center.y, - }; - let top_right = Pos2 { - x: top_center.x + (BUFFER / 2.0), - y: top_center.y, - }; - let bottom_left = Pos2 { - x: bottom_center.x - (BUFFER / 2.0), - y: bottom_center.y, - }; - let bottom_right = Pos2 { - x: bottom_center.x + (BUFFER / 2.0), - y: bottom_center.y, - }; - - Some(BoxedZoomType::Bars( - epaint::Shape::line_segment( - [top_left, top_right], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [bottom_left, bottom_right], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [top_left, top_right], - epaint::Stroke::new(2., Color32::WHITE), - ), - epaint::Shape::line_segment( - [bottom_left, bottom_right], - epaint::Stroke::new(2., Color32::WHITE), - ), - )) - } else { - let (left_center, right_center) = if box_start_pos.x > box_end_pos.x - { - ( - Pos2 { - x: box_start_pos.x - rect.width(), - y: box_start_pos.y, - }, - box_start_pos, - ) - } else { - ( - box_start_pos, - Pos2 { - x: box_start_pos.x + rect.width(), - y: box_start_pos.y, - }, - ) - }; - let top_left = Pos2 { - x: left_center.x, - y: left_center.y + (BUFFER / 2.0), - }; - let bottom_left = Pos2 { - x: left_center.x, - y: left_center.y - (BUFFER / 2.0), - }; - let top_right = Pos2 { - x: right_center.x, - y: right_center.y + (BUFFER / 2.0), - }; - let bottom_right = Pos2 { - x: right_center.x, - y: right_center.y - (BUFFER / 2.0), - }; - Some(BoxedZoomType::Bars( - epaint::Shape::line_segment( - [top_left, bottom_left], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [top_right, bottom_right], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [top_left, bottom_left], - epaint::Stroke::new(2., Color32::WHITE), - ), - epaint::Shape::line_segment( - [top_right, bottom_right], - epaint::Stroke::new(2., Color32::WHITE), - ), - )) - } - }; - } + boxed_zoom_rect = Some(BoxedZoomType::from_corners(box_start_pos, box_end_pos)); } // when the click is release perform the zoom if response.drag_stopped() { @@ -1527,6 +1379,112 @@ enum BoxedZoomType { Bars(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), } +impl BoxedZoomType { + // random values for testing + // "buffer" on `min` before rect should be used + // NOTE: May want to be based on the axes? + const BUFFER: f32 = 100.0; + // if the ratio between `min` and `max` is about 10% different or less, its "square" + const SQUARENESS_THRESHOLD: f32 = 0.1; + + // as soon as there is a drag, add a buffer centered around `box_start_pos` in the `min` direction + // on the near and far ends of `rect` + // + // if `min` > (buffer/2) || aspect_ratio ~= 1 => rect zoom + // otherwise => single zoom + fn from_corners(box_start_pos: Pos2, box_end_pos: Pos2) -> Self { + let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos); + + let (min, is_vertical) = { + let Vec2 { x, y } = rect.size(); + if x < y { (x, true) } else { (y, false) } + }; + + if min > (Self::BUFFER / 2.0) + || (rect.aspect_ratio() - 1.0).abs() < Self::SQUARENESS_THRESHOLD + { + Self::Rect( + epaint::RectShape::stroke( + rect, + 0.0, + epaint::Stroke::new(4., Color32::DARK_BLUE), + egui::StrokeKind::Middle, + ), // Outer stroke + epaint::RectShape::stroke( + rect, + 0.0, + epaint::Stroke::new(2., Color32::WHITE), + egui::StrokeKind::Middle, + ), // Inner stroke + ) + } else { + let height = egui::vec2(0.0, rect.height()); + let width = egui::vec2(rect.width(), 0.0); + let half_buffer_x = egui::vec2(Self::BUFFER / 2.0, 0.0); + let half_buffer_y = egui::vec2(0.0, Self::BUFFER / 2.0); + + if is_vertical { + let (top_center, bottom_center) = if box_start_pos.y > box_end_pos.y { + (box_start_pos, box_start_pos - height) + } else { + (box_start_pos + height, box_start_pos) + }; + let top_left = top_center - half_buffer_x; + let top_right = top_center + half_buffer_x; + let bottom_left = bottom_center - half_buffer_x; + let bottom_right = bottom_center + half_buffer_x; + + Self::Bars( + epaint::Shape::line_segment( + [top_left, top_right], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [bottom_left, bottom_right], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [top_left, top_right], + epaint::Stroke::new(2., Color32::WHITE), + ), + epaint::Shape::line_segment( + [bottom_left, bottom_right], + epaint::Stroke::new(2., Color32::WHITE), + ), + ) + } else { + let (left_center, right_center) = if box_start_pos.x > box_end_pos.x { + (box_start_pos - width, box_start_pos) + } else { + (box_start_pos, box_start_pos + width) + }; + let top_left = left_center + half_buffer_y; + let bottom_left = left_center - half_buffer_y; + let top_right = right_center + half_buffer_y; + let bottom_right = right_center - half_buffer_y; + Self::Bars( + epaint::Shape::line_segment( + [top_left, bottom_left], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [top_right, bottom_right], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [top_left, bottom_left], + epaint::Stroke::new(2., Color32::WHITE), + ), + epaint::Shape::line_segment( + [top_right, bottom_right], + epaint::Stroke::new(2., Color32::WHITE), + ), + ) + } + } + } +} + /// Returns the rect left after adding axes. fn axis_widgets<'a>( mem: Option<&PlotMemory>, From e32ae81f636f3bc2c3e8b7039591ecd8dd6a4ad6 Mon Sep 17 00:00:00 2001 From: Mick Harrigan Date: Tue, 9 Sep 2025 12:22:16 -0700 Subject: [PATCH 5/9] another refactor --- egui_plot/src/lib.rs | 189 +++++++++++++++++++++++-------------------- 1 file changed, 103 insertions(+), 86 deletions(-) diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index 4ac182eb..ada34ef4 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -1181,6 +1181,7 @@ impl<'a> Plot<'a> { } // when the click is release perform the zoom if response.drag_stopped() { + let zoom_type = ZoomType::new_from_corners(box_start_pos, box_end_pos); let box_start_pos = mem.transform.value_from_position(box_start_pos); let box_end_pos = mem.transform.value_from_position(box_end_pos); let new_bounds = PlotBounds { @@ -1309,7 +1310,8 @@ impl<'a> Plot<'a> { ui.painter().with_clip_rect(plot_rect).add(rect_shape); ui.painter().with_clip_rect(plot_rect).add(rect_shape1); } - BoxedZoomType::Bars(shape, shape1, shape2, shape3) => { + BoxedZoomType::Horizontal(shape, shape1, shape2, shape3) + | BoxedZoomType::Vertical(shape, shape1, shape2, shape3) => { ui.painter().with_clip_rect(plot_rect).add(shape); ui.painter().with_clip_rect(plot_rect).add(shape1); ui.painter().with_clip_rect(plot_rect).add(shape2); @@ -1374,113 +1376,128 @@ impl<'a> Plot<'a> { } } -enum BoxedZoomType { - Rect(epaint::RectShape, epaint::RectShape), - Bars(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), +enum ZoomType { + Rect(Rect), + Horizontal(Rect), + Vertical(Rect), } -impl BoxedZoomType { +impl ZoomType { // random values for testing // "buffer" on `min` before rect should be used // NOTE: May want to be based on the axes? - const BUFFER: f32 = 100.0; + pub const BUFFER: f32 = 100.0; // if the ratio between `min` and `max` is about 10% different or less, its "square" - const SQUARENESS_THRESHOLD: f32 = 0.1; + pub const SQUARENESS_THRESHOLD: f32 = 0.1; - // as soon as there is a drag, add a buffer centered around `box_start_pos` in the `min` direction - // on the near and far ends of `rect` - // - // if `min` > (buffer/2) || aspect_ratio ~= 1 => rect zoom - // otherwise => single zoom - fn from_corners(box_start_pos: Pos2, box_end_pos: Pos2) -> Self { - let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos); + fn new_from_corners(start: Pos2, end: Pos2) -> Self { + let rect = epaint::Rect::from_two_pos(start, end); let (min, is_vertical) = { let Vec2 { x, y } = rect.size(); if x < y { (x, true) } else { (y, false) } }; + let height = egui::vec2(0.0, rect.height()); + let width = egui::vec2(rect.width(), 0.0); + let half_buffer_x = egui::vec2(Self::BUFFER / 2.0, 0.0); + let half_buffer_y = egui::vec2(0.0, Self::BUFFER / 2.0); + if min > (Self::BUFFER / 2.0) || (rect.aspect_ratio() - 1.0).abs() < Self::SQUARENESS_THRESHOLD { - Self::Rect( - epaint::RectShape::stroke( - rect, - 0.0, - epaint::Stroke::new(4., Color32::DARK_BLUE), - egui::StrokeKind::Middle, - ), // Outer stroke - epaint::RectShape::stroke( - rect, - 0.0, - epaint::Stroke::new(2., Color32::WHITE), - egui::StrokeKind::Middle, - ), // Inner stroke - ) + Self::Rect(rect) + } else if is_vertical { + let (top_center, bottom_center) = if start.y > end.y { + (start, start - height) + } else { + (start + height, start) + }; + let top_left = top_center - half_buffer_x; + let bottom_right = bottom_center + half_buffer_x; + + Self::Vertical(Rect::from_two_pos(top_left, bottom_right)) } else { - let height = egui::vec2(0.0, rect.height()); - let width = egui::vec2(rect.width(), 0.0); - let half_buffer_x = egui::vec2(Self::BUFFER / 2.0, 0.0); - let half_buffer_y = egui::vec2(0.0, Self::BUFFER / 2.0); - - if is_vertical { - let (top_center, bottom_center) = if box_start_pos.y > box_end_pos.y { - (box_start_pos, box_start_pos - height) - } else { - (box_start_pos + height, box_start_pos) - }; - let top_left = top_center - half_buffer_x; - let top_right = top_center + half_buffer_x; - let bottom_left = bottom_center - half_buffer_x; - let bottom_right = bottom_center + half_buffer_x; - - Self::Bars( - epaint::Shape::line_segment( - [top_left, top_right], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [bottom_left, bottom_right], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [top_left, top_right], - epaint::Stroke::new(2., Color32::WHITE), - ), - epaint::Shape::line_segment( - [bottom_left, bottom_right], - epaint::Stroke::new(2., Color32::WHITE), - ), - ) + let (left_center, right_center) = if start.x > end.x { + (start - width, start) } else { - let (left_center, right_center) = if box_start_pos.x > box_end_pos.x { - (box_start_pos - width, box_start_pos) - } else { - (box_start_pos, box_start_pos + width) - }; - let top_left = left_center + half_buffer_y; - let bottom_left = left_center - half_buffer_y; - let top_right = right_center + half_buffer_y; - let bottom_right = right_center - half_buffer_y; - Self::Bars( - epaint::Shape::line_segment( - [top_left, bottom_left], - epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [top_right, bottom_right], + (start, start + width) + }; + let top_left = left_center + half_buffer_y; + let bottom_right = right_center - half_buffer_y; + + Self::Horizontal(Rect::from_two_pos(top_left, bottom_right)) + } + } +} + +enum BoxedZoomType { + Rect(epaint::RectShape, epaint::RectShape), + // TODO: make this api not atrocious + Horizontal(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), + Vertical(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), +} + +impl BoxedZoomType { + // as soon as there is a drag, add a buffer centered around `box_start_pos` in the `min` direction + // on the near and far ends of `rect` + // + // if `min` > (buffer/2) || aspect_ratio ~= 1 => rect zoom + // otherwise => single zoom + fn from_corners(start: Pos2, end: Pos2) -> Self { + match ZoomType::new_from_corners(start, end) { + ZoomType::Rect(rect) => { + Self::Rect( + epaint::RectShape::stroke( + rect, + 0.0, epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( - [top_left, bottom_left], + egui::StrokeKind::Middle, + ), // Outer stroke + epaint::RectShape::stroke( + rect, + 0.0, epaint::Stroke::new(2., Color32::WHITE), - ), - epaint::Shape::line_segment( - [top_right, bottom_right], - epaint::Stroke::new(2., Color32::WHITE), - ), + egui::StrokeKind::Middle, + ), // Inner stroke ) } + ZoomType::Horizontal(rect) => Self::Horizontal( + epaint::Shape::line_segment( + [rect.left_top(), rect.left_bottom()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.right_top(), rect.right_bottom()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.left_top(), rect.left_bottom()], + epaint::Stroke::new(2., Color32::WHITE), + ), + epaint::Shape::line_segment( + [rect.right_top(), rect.right_bottom()], + epaint::Stroke::new(2., Color32::WHITE), + ), + ), + ZoomType::Vertical(rect) => Self::Vertical( + epaint::Shape::line_segment( + [rect.left_top(), rect.right_top()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.left_bottom(), rect.right_bottom()], + epaint::Stroke::new(4., Color32::DARK_BLUE), + ), + epaint::Shape::line_segment( + [rect.left_top(), rect.right_top()], + epaint::Stroke::new(2., Color32::WHITE), + ), + epaint::Shape::line_segment( + [rect.left_bottom(), rect.right_bottom()], + epaint::Stroke::new(2., Color32::WHITE), + ), + ), } } } From 7c67209772a636b3c186727a07d82485872d5691 Mon Sep 17 00:00:00 2001 From: Mick Harrigan Date: Tue, 9 Sep 2025 12:38:53 -0700 Subject: [PATCH 6/9] single dimension zooming --- egui_plot/src/lib.rs | 69 ++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index ada34ef4..36a084b3 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -1177,27 +1177,60 @@ impl<'a> Plot<'a> { // while dragging prepare a Shape and draw it later on top of the plot if response.dragged_by(boxed_zoom_pointer_button) { response = response.on_hover_cursor(CursorIcon::ZoomIn); - boxed_zoom_rect = Some(BoxedZoomType::from_corners(box_start_pos, box_end_pos)); + boxed_zoom_rect = + Some(BoxedZoomType::new_from_corners(box_start_pos, box_end_pos)); } // when the click is release perform the zoom if response.drag_stopped() { let zoom_type = ZoomType::new_from_corners(box_start_pos, box_end_pos); - let box_start_pos = mem.transform.value_from_position(box_start_pos); - let box_end_pos = mem.transform.value_from_position(box_end_pos); - let new_bounds = PlotBounds { - min: [ - box_start_pos.x.min(box_end_pos.x), - box_start_pos.y.min(box_end_pos.y), - ], - max: [ - box_start_pos.x.max(box_end_pos.x), - box_start_pos.y.max(box_end_pos.y), - ], - }; - if new_bounds.is_valid() { - mem.transform.set_bounds(new_bounds); - mem.auto_bounds = false.into(); + match zoom_type { + ZoomType::Rect(rect) => { + let top_left = mem.transform.value_from_position(rect.left_top()); + let bottom_right = + mem.transform.value_from_position(rect.right_bottom()); + let new_bounds = PlotBounds { + min: [top_left.x, bottom_right.y], + max: [bottom_right.x, top_left.y], + }; + if new_bounds.is_valid() { + mem.transform.set_bounds(new_bounds); + mem.auto_bounds = false.into(); + } + } + ZoomType::Horizontal(rect) => { + let top_left = mem.transform.value_from_position(rect.left_top()); + let bottom_right = + mem.transform.value_from_position(rect.right_bottom()); + let selected_bounds = PlotBounds { + min: [top_left.x, bottom_right.y], + max: [bottom_right.x, top_left.y], + }; + + let mut new_bounds = *mem.transform.bounds(); + new_bounds.set_x(&selected_bounds); + if new_bounds.is_valid() { + mem.transform.set_bounds(new_bounds); + mem.auto_bounds = false.into(); + } + } + ZoomType::Vertical(rect) => { + let top_left = mem.transform.value_from_position(rect.left_top()); + let bottom_right = + mem.transform.value_from_position(rect.right_bottom()); + let selected_bounds = PlotBounds { + min: [top_left.x, bottom_right.y], + max: [bottom_right.x, top_left.y], + }; + + let mut new_bounds = *mem.transform.bounds(); + new_bounds.set_y(&selected_bounds); + if new_bounds.is_valid() { + mem.transform.set_bounds(new_bounds); + mem.auto_bounds = false.into(); + } + } } + // reset the boxed zoom state mem.last_click_pos_for_zoom = None; } @@ -1433,7 +1466,7 @@ impl ZoomType { enum BoxedZoomType { Rect(epaint::RectShape, epaint::RectShape), - // TODO: make this api not atrocious + // TODO(Mick): make this api not atrocious Horizontal(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), Vertical(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), } @@ -1444,7 +1477,7 @@ impl BoxedZoomType { // // if `min` > (buffer/2) || aspect_ratio ~= 1 => rect zoom // otherwise => single zoom - fn from_corners(start: Pos2, end: Pos2) -> Self { + fn new_from_corners(start: Pos2, end: Pos2) -> Self { match ZoomType::new_from_corners(start, end) { ZoomType::Rect(rect) => { Self::Rect( From d34bb66fa10541605f5ddbfa69a6882d26b20fa6 Mon Sep 17 00:00:00 2001 From: Mick Harrigan Date: Tue, 9 Sep 2025 12:59:17 -0700 Subject: [PATCH 7/9] filter box zoom based on axes aspect ratio --- egui_plot/src/lib.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index 36a084b3..2e0e57e2 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -1177,13 +1177,20 @@ impl<'a> Plot<'a> { // while dragging prepare a Shape and draw it later on top of the plot if response.dragged_by(boxed_zoom_pointer_button) { response = response.on_hover_cursor(CursorIcon::ZoomIn); - boxed_zoom_rect = - Some(BoxedZoomType::new_from_corners(box_start_pos, box_end_pos)); + boxed_zoom_rect = Some(BoxedZoomType::new_from_corners( + box_start_pos, + box_end_pos, + data_aspect.is_none(), + )); } // when the click is release perform the zoom if response.drag_stopped() { - let zoom_type = ZoomType::new_from_corners(box_start_pos, box_end_pos); - match zoom_type { + // NOTE(Mick): maybe a `PlotBounds::From` could be useful here? + match ZoomType::new_from_corners( + box_start_pos, + box_end_pos, + data_aspect.is_none(), + ) { ZoomType::Rect(rect) => { let top_left = mem.transform.value_from_position(rect.left_top()); let bottom_right = @@ -1416,14 +1423,14 @@ enum ZoomType { } impl ZoomType { - // random values for testing // "buffer" on `min` before rect should be used // NOTE: May want to be based on the axes? pub const BUFFER: f32 = 100.0; // if the ratio between `min` and `max` is about 10% different or less, its "square" pub const SQUARENESS_THRESHOLD: f32 = 0.1; - fn new_from_corners(start: Pos2, end: Pos2) -> Self { + // NOTE(Mick): non-Rect zooming is only supported if proportional axes is off. + fn new_from_corners(start: Pos2, end: Pos2, supports_single_dimension_zoom: bool) -> Self { let rect = epaint::Rect::from_two_pos(start, end); let (min, is_vertical) = { @@ -1436,6 +1443,10 @@ impl ZoomType { let half_buffer_x = egui::vec2(Self::BUFFER / 2.0, 0.0); let half_buffer_y = egui::vec2(0.0, Self::BUFFER / 2.0); + if !supports_single_dimension_zoom { + return Self::Rect(rect); + } + if min > (Self::BUFFER / 2.0) || (rect.aspect_ratio() - 1.0).abs() < Self::SQUARENESS_THRESHOLD { @@ -1466,19 +1477,19 @@ impl ZoomType { enum BoxedZoomType { Rect(epaint::RectShape, epaint::RectShape), - // TODO(Mick): make this api not atrocious Horizontal(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), Vertical(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), } +// TODO(Mick): make this api not atrocious impl BoxedZoomType { // as soon as there is a drag, add a buffer centered around `box_start_pos` in the `min` direction // on the near and far ends of `rect` // // if `min` > (buffer/2) || aspect_ratio ~= 1 => rect zoom // otherwise => single zoom - fn new_from_corners(start: Pos2, end: Pos2) -> Self { - match ZoomType::new_from_corners(start, end) { + fn new_from_corners(start: Pos2, end: Pos2, supports_single_dimension_zoom: bool) -> Self { + match ZoomType::new_from_corners(start, end, supports_single_dimension_zoom) { ZoomType::Rect(rect) => { Self::Rect( epaint::RectShape::stroke( From 2f2877e4213d2ab1b772b6c87b4e23b51830702f Mon Sep 17 00:00:00 2001 From: Mick Harrigan Date: Wed, 10 Sep 2025 13:30:43 -0700 Subject: [PATCH 8/9] simplify api --- egui_plot/src/lib.rs | 113 ++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 65 deletions(-) diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index 2e0e57e2..c4938d7d 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -1177,7 +1177,7 @@ impl<'a> Plot<'a> { // while dragging prepare a Shape and draw it later on top of the plot if response.dragged_by(boxed_zoom_pointer_button) { response = response.on_hover_cursor(CursorIcon::ZoomIn); - boxed_zoom_rect = Some(BoxedZoomType::new_from_corners( + boxed_zoom_rect = Some(ZoomType::new_from_corners( box_start_pos, box_end_pos, data_aspect.is_none(), @@ -1185,7 +1185,7 @@ impl<'a> Plot<'a> { } // when the click is release perform the zoom if response.drag_stopped() { - // NOTE(Mick): maybe a `PlotBounds::From` could be useful here? + // TODO(Mick): not a fan of the recalculate match ZoomType::new_from_corners( box_start_pos, box_end_pos, @@ -1345,19 +1345,7 @@ impl<'a> Plot<'a> { let (plot_cursors, mut hovered_plot_item) = prepared.ui(ui, &response); if let Some(boxed_zoom_rect) = boxed_zoom_rect { - match boxed_zoom_rect { - BoxedZoomType::Rect(rect_shape, rect_shape1) => { - ui.painter().with_clip_rect(plot_rect).add(rect_shape); - ui.painter().with_clip_rect(plot_rect).add(rect_shape1); - } - BoxedZoomType::Horizontal(shape, shape1, shape2, shape3) - | BoxedZoomType::Vertical(shape, shape1, shape2, shape3) => { - ui.painter().with_clip_rect(plot_rect).add(shape); - ui.painter().with_clip_rect(plot_rect).add(shape1); - ui.painter().with_clip_rect(plot_rect).add(shape2); - ui.painter().with_clip_rect(plot_rect).add(shape3); - } - } + boxed_zoom_rect.paint(ui, plot_rect); } if let Some(mut legend) = legend { @@ -1473,75 +1461,70 @@ impl ZoomType { Self::Horizontal(Rect::from_two_pos(top_left, bottom_right)) } } -} - -enum BoxedZoomType { - Rect(epaint::RectShape, epaint::RectShape), - Horizontal(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), - Vertical(epaint::Shape, epaint::Shape, epaint::Shape, epaint::Shape), -} -// TODO(Mick): make this api not atrocious -impl BoxedZoomType { - // as soon as there is a drag, add a buffer centered around `box_start_pos` in the `min` direction - // on the near and far ends of `rect` - // - // if `min` > (buffer/2) || aspect_ratio ~= 1 => rect zoom - // otherwise => single zoom - fn new_from_corners(start: Pos2, end: Pos2, supports_single_dimension_zoom: bool) -> Self { - match ZoomType::new_from_corners(start, end, supports_single_dimension_zoom) { + fn paint(&self, ui: &mut egui::Ui, clip_rect: Rect) { + let painter = ui.painter().with_clip_rect(clip_rect); + match self { ZoomType::Rect(rect) => { - Self::Rect( - epaint::RectShape::stroke( - rect, - 0.0, - epaint::Stroke::new(4., Color32::DARK_BLUE), - egui::StrokeKind::Middle, - ), // Outer stroke - epaint::RectShape::stroke( - rect, - 0.0, - epaint::Stroke::new(2., Color32::WHITE), - egui::StrokeKind::Middle, - ), // Inner stroke - ) + // Outer stroke + painter.add(epaint::RectShape::stroke( + *rect, + 0.0, + epaint::Stroke::new(4., Color32::DARK_BLUE), + egui::StrokeKind::Middle, + )); + // Inner stroke + painter.add(epaint::RectShape::stroke( + *rect, + 0.0, + epaint::Stroke::new(2., Color32::WHITE), + egui::StrokeKind::Middle, + )); } - ZoomType::Horizontal(rect) => Self::Horizontal( - epaint::Shape::line_segment( + ZoomType::Horizontal(rect) => { + // Left Outer stroke + painter.add(epaint::Shape::line_segment( [rect.left_top(), rect.left_bottom()], epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( + )); + // Right Outer stroke + painter.add(epaint::Shape::line_segment( [rect.right_top(), rect.right_bottom()], epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( + )); + // Left Inner stroke + painter.add(epaint::Shape::line_segment( [rect.left_top(), rect.left_bottom()], epaint::Stroke::new(2., Color32::WHITE), - ), - epaint::Shape::line_segment( + )); + // Right Inner stroke + painter.add(epaint::Shape::line_segment( [rect.right_top(), rect.right_bottom()], epaint::Stroke::new(2., Color32::WHITE), - ), - ), - ZoomType::Vertical(rect) => Self::Vertical( - epaint::Shape::line_segment( + )); + } + ZoomType::Vertical(rect) => { + // Top Outer stroke + painter.add(epaint::Shape::line_segment( [rect.left_top(), rect.right_top()], epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( + )); + // Bottom Outer stroke + painter.add(epaint::Shape::line_segment( [rect.left_bottom(), rect.right_bottom()], epaint::Stroke::new(4., Color32::DARK_BLUE), - ), - epaint::Shape::line_segment( + )); + // Top Inner stroke + painter.add(epaint::Shape::line_segment( [rect.left_top(), rect.right_top()], epaint::Stroke::new(2., Color32::WHITE), - ), - epaint::Shape::line_segment( + )); + // Bottom Inner stroke + painter.add(epaint::Shape::line_segment( [rect.left_bottom(), rect.right_bottom()], epaint::Stroke::new(2., Color32::WHITE), - ), - ), + )); + } } } } From 034d98d302f4b20b9aff1fc1ef452c06191cacd0 Mon Sep 17 00:00:00 2001 From: Mick Harrigan Date: Wed, 10 Sep 2025 13:59:05 -0700 Subject: [PATCH 9/9] clippy and format --- egui_plot/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index c4938d7d..5f73037d 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -1462,10 +1462,10 @@ impl ZoomType { } } - fn paint(&self, ui: &mut egui::Ui, clip_rect: Rect) { + fn paint(&self, ui: &Ui, clip_rect: Rect) { let painter = ui.painter().with_clip_rect(clip_rect); match self { - ZoomType::Rect(rect) => { + Self::Rect(rect) => { // Outer stroke painter.add(epaint::RectShape::stroke( *rect, @@ -1481,7 +1481,7 @@ impl ZoomType { egui::StrokeKind::Middle, )); } - ZoomType::Horizontal(rect) => { + Self::Horizontal(rect) => { // Left Outer stroke painter.add(epaint::Shape::line_segment( [rect.left_top(), rect.left_bottom()], @@ -1503,7 +1503,7 @@ impl ZoomType { epaint::Stroke::new(2., Color32::WHITE), )); } - ZoomType::Vertical(rect) => { + Self::Vertical(rect) => { // Top Outer stroke painter.add(epaint::Shape::line_segment( [rect.left_top(), rect.right_top()],