diff --git a/egui_demo_lib/src/apps/demo/paint_bezier.rs b/egui_demo_lib/src/apps/demo/paint_bezier.rs index d96e68a4..6cbbd140 100644 --- a/egui_demo_lib/src/apps/demo/paint_bezier.rs +++ b/egui_demo_lib/src/apps/demo/paint_bezier.rs @@ -122,7 +122,7 @@ impl PaintBezier { let shape = QuadraticBezierShape::from_points_stroke(points, true, self.fill, self.stroke); painter.add(epaint::RectShape::stroke( - shape.bounding_rect(), + shape.visual_bounding_rect(), 0.0, self.bounding_box_stroke, )); @@ -133,7 +133,7 @@ impl PaintBezier { let shape = CubicBezierShape::from_points_stroke(points, true, self.fill, self.stroke); painter.add(epaint::RectShape::stroke( - shape.bounding_rect(), + shape.visual_bounding_rect(), 0.0, self.bounding_box_stroke, )); diff --git a/epaint/src/bezier.rs b/epaint/src/bezier.rs index d0a63c8a..453671d3 100644 --- a/epaint/src/bezier.rs +++ b/epaint/src/bezier.rs @@ -74,8 +74,17 @@ impl CubicBezierShape { pathshapes } - /// Screen-space bounding rectangle. - pub fn bounding_rect(&self) -> Rect { + /// The visual bounding rectangle (includes stroke width) + pub fn visual_bounding_rect(&self) -> Rect { + if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() { + Rect::NOTHING + } else { + self.logical_bounding_rect().expand(self.stroke.width / 2.0) + } + } + + /// Logical bounding rectangle (ignoring stroke width) + pub fn logical_bounding_rect(&self) -> Rect { //temporary solution let (mut min_x, mut max_x) = if self.points[0].x < self.points[3].x { (self.points[0].x, self.points[3].x) @@ -421,8 +430,17 @@ impl QuadraticBezierShape { } } - /// bounding box of the quadratic Bézier shape - pub fn bounding_rect(&self) -> Rect { + /// The visual bounding rectangle (includes stroke width) + pub fn visual_bounding_rect(&self) -> Rect { + if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() { + Rect::NOTHING + } else { + self.logical_bounding_rect().expand(self.stroke.width / 2.0) + } + } + + /// Logical bounding rectangle (ignoring stroke width) + pub fn logical_bounding_rect(&self) -> Rect { let (mut min_x, mut max_x) = if self.points[0].x < self.points[2].x { (self.points[0].x, self.points[2].x) } else { @@ -755,7 +773,7 @@ mod tests { fill: Default::default(), stroke: Default::default(), }; - let bbox = curve.bounding_rect(); + let bbox = curve.logical_bounding_rect(); assert!((bbox.min.x - 72.96).abs() < 0.01); assert!((bbox.min.y - 27.78).abs() < 0.01); @@ -779,7 +797,7 @@ mod tests { fill: Default::default(), stroke: Default::default(), }; - let bbox = curve.bounding_rect(); + let bbox = curve.logical_bounding_rect(); assert!((bbox.min.x - 10.0).abs() < 0.01); assert!((bbox.min.y - 10.0).abs() < 0.01); @@ -848,7 +866,7 @@ mod tests { stroke: Default::default(), }; - let bbox = curve.bounding_rect(); + let bbox = curve.logical_bounding_rect(); assert_eq!(bbox.min.x, 10.0); assert_eq!(bbox.min.y, 10.0); assert_eq!(bbox.max.x, 270.0); @@ -866,7 +884,7 @@ mod tests { stroke: Default::default(), }; - let bbox = curve.bounding_rect(); + let bbox = curve.logical_bounding_rect(); assert_eq!(bbox.min.x, 10.0); assert_eq!(bbox.min.y, 10.0); assert!((bbox.max.x - 206.50).abs() < 0.01); @@ -884,7 +902,7 @@ mod tests { stroke: Default::default(), }; - let bbox = curve.bounding_rect(); + let bbox = curve.logical_bounding_rect(); assert!((bbox.min.x - 86.71).abs() < 0.01); assert!((bbox.min.y - 30.0).abs() < 0.01); diff --git a/epaint/src/shape.rs b/epaint/src/shape.rs index 6e2ab5df..d097b8c2 100644 --- a/epaint/src/shape.rs +++ b/epaint/src/shape.rs @@ -170,6 +170,34 @@ impl Shape { crate::epaint_assert!(mesh.is_valid()); Self::Mesh(mesh) } + + /// The visual bounding rectangle (includes stroke widths) + pub fn visual_bounding_rect(&self) -> Rect { + match self { + Self::Noop => Rect::NOTHING, + Self::Vec(shapes) => { + let mut rect = Rect::NOTHING; + for shape in shapes { + rect = rect.union(shape.visual_bounding_rect()); + } + rect + } + Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(), + Self::LineSegment { points, stroke } => { + if stroke.is_empty() { + Rect::NOTHING + } else { + Rect::from_two_pos(points[0], points[1]).expand(stroke.width / 2.0) + } + } + Self::Path(path_shape) => path_shape.visual_bounding_rect(), + Self::Rect(rect_shape) => rect_shape.visual_bounding_rect(), + Self::Text(text_shape) => text_shape.visual_bounding_rect(), + Self::Mesh(mesh) => mesh.calc_bounds(), + Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(), + Self::CubicBezier(bezier) => bezier.visual_bounding_rect(), + } + } } /// ## Inspection and transforms @@ -260,6 +288,18 @@ impl CircleShape { stroke: stroke.into(), } } + + /// The visual bounding rectangle (includes stroke width) + pub fn visual_bounding_rect(&self) -> Rect { + if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() { + Rect::NOTHING + } else { + Rect::from_center_size( + self.center, + Vec2::splat(self.radius + self.stroke.width / 2.0), + ) + } + } } impl From for Shape { @@ -327,10 +367,14 @@ impl PathShape { } } - /// Screen-space bounding rectangle. + /// The visual bounding rectangle (includes stroke width) #[inline] - pub fn bounding_rect(&self) -> Rect { - Rect::from_points(&self.points).expand(self.stroke.width) + pub fn visual_bounding_rect(&self) -> Rect { + if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() { + Rect::NOTHING + } else { + Rect::from_points(&self.points).expand(self.stroke.width / 2.0) + } } } @@ -379,10 +423,14 @@ impl RectShape { } } - /// Screen-space bounding rectangle. + /// The visual bounding rectangle (includes stroke width) #[inline] - pub fn bounding_rect(&self) -> Rect { - self.rect.expand(self.stroke.width) + pub fn visual_bounding_rect(&self) -> Rect { + if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() { + Rect::NOTHING + } else { + self.rect.expand(self.stroke.width / 2.0) + } } } @@ -491,9 +539,9 @@ impl TextShape { } } - /// Screen-space bounding rectangle. + /// The visual bounding rectangle #[inline] - pub fn bounding_rect(&self) -> Rect { + pub fn visual_bounding_rect(&self) -> Rect { self.galley.mesh_bounds.translate(self.pos.to_vec2()) } } diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index 89bcb1d6..cc1c75fc 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -793,7 +793,7 @@ impl Tessellator { let clip_rect = self.clip_rect; if options.coarse_tessellation_culling - && !quadratic_shape.bounding_rect().intersects(clip_rect) + && !quadratic_shape.visual_bounding_rect().intersects(clip_rect) { return; } @@ -816,7 +816,8 @@ impl Tessellator { ) { let options = &self.options; let clip_rect = self.clip_rect; - if options.coarse_tessellation_culling && !cubic_shape.bounding_rect().intersects(clip_rect) + if options.coarse_tessellation_culling + && !cubic_shape.visual_bounding_rect().intersects(clip_rect) { return; } @@ -870,7 +871,7 @@ impl Tessellator { } if self.options.coarse_tessellation_culling - && !path_shape.bounding_rect().intersects(self.clip_rect) + && !path_shape.visual_bounding_rect().intersects(self.clip_rect) { return; }