Add Shape::visual_bounding_rect()

This commit is contained in:
Emil Ernerfeldt 2022-02-21 21:40:46 +01:00
parent fd3fb726c1
commit 8f887e2ebd
4 changed files with 89 additions and 22 deletions

View file

@ -122,7 +122,7 @@ impl PaintBezier {
let shape = let shape =
QuadraticBezierShape::from_points_stroke(points, true, self.fill, self.stroke); QuadraticBezierShape::from_points_stroke(points, true, self.fill, self.stroke);
painter.add(epaint::RectShape::stroke( painter.add(epaint::RectShape::stroke(
shape.bounding_rect(), shape.visual_bounding_rect(),
0.0, 0.0,
self.bounding_box_stroke, self.bounding_box_stroke,
)); ));
@ -133,7 +133,7 @@ impl PaintBezier {
let shape = let shape =
CubicBezierShape::from_points_stroke(points, true, self.fill, self.stroke); CubicBezierShape::from_points_stroke(points, true, self.fill, self.stroke);
painter.add(epaint::RectShape::stroke( painter.add(epaint::RectShape::stroke(
shape.bounding_rect(), shape.visual_bounding_rect(),
0.0, 0.0,
self.bounding_box_stroke, self.bounding_box_stroke,
)); ));

View file

@ -74,8 +74,17 @@ impl CubicBezierShape {
pathshapes pathshapes
} }
/// Screen-space bounding rectangle. /// The visual bounding rectangle (includes stroke width)
pub fn bounding_rect(&self) -> Rect { 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 //temporary solution
let (mut min_x, mut max_x) = if self.points[0].x < self.points[3].x { let (mut min_x, mut max_x) = if self.points[0].x < self.points[3].x {
(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 /// The visual bounding rectangle (includes stroke width)
pub fn bounding_rect(&self) -> Rect { 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 { let (mut min_x, mut max_x) = if self.points[0].x < self.points[2].x {
(self.points[0].x, self.points[2].x) (self.points[0].x, self.points[2].x)
} else { } else {
@ -755,7 +773,7 @@ mod tests {
fill: Default::default(), fill: Default::default(),
stroke: 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.x - 72.96).abs() < 0.01);
assert!((bbox.min.y - 27.78).abs() < 0.01); assert!((bbox.min.y - 27.78).abs() < 0.01);
@ -779,7 +797,7 @@ mod tests {
fill: Default::default(), fill: Default::default(),
stroke: 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.x - 10.0).abs() < 0.01);
assert!((bbox.min.y - 10.0).abs() < 0.01); assert!((bbox.min.y - 10.0).abs() < 0.01);
@ -848,7 +866,7 @@ mod tests {
stroke: Default::default(), 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.x, 10.0);
assert_eq!(bbox.min.y, 10.0); assert_eq!(bbox.min.y, 10.0);
assert_eq!(bbox.max.x, 270.0); assert_eq!(bbox.max.x, 270.0);
@ -866,7 +884,7 @@ mod tests {
stroke: Default::default(), 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.x, 10.0);
assert_eq!(bbox.min.y, 10.0); assert_eq!(bbox.min.y, 10.0);
assert!((bbox.max.x - 206.50).abs() < 0.01); assert!((bbox.max.x - 206.50).abs() < 0.01);
@ -884,7 +902,7 @@ mod tests {
stroke: Default::default(), 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.x - 86.71).abs() < 0.01);
assert!((bbox.min.y - 30.0).abs() < 0.01); assert!((bbox.min.y - 30.0).abs() < 0.01);

View file

@ -170,6 +170,34 @@ impl Shape {
crate::epaint_assert!(mesh.is_valid()); crate::epaint_assert!(mesh.is_valid());
Self::Mesh(mesh) 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 /// ## Inspection and transforms
@ -260,6 +288,18 @@ impl CircleShape {
stroke: stroke.into(), 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<CircleShape> for Shape { impl From<CircleShape> for Shape {
@ -327,10 +367,14 @@ impl PathShape {
} }
} }
/// Screen-space bounding rectangle. /// The visual bounding rectangle (includes stroke width)
#[inline] #[inline]
pub fn bounding_rect(&self) -> Rect { pub fn visual_bounding_rect(&self) -> Rect {
Rect::from_points(&self.points).expand(self.stroke.width) 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] #[inline]
pub fn bounding_rect(&self) -> Rect { pub fn visual_bounding_rect(&self) -> Rect {
self.rect.expand(self.stroke.width) 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] #[inline]
pub fn bounding_rect(&self) -> Rect { pub fn visual_bounding_rect(&self) -> Rect {
self.galley.mesh_bounds.translate(self.pos.to_vec2()) self.galley.mesh_bounds.translate(self.pos.to_vec2())
} }
} }

View file

@ -793,7 +793,7 @@ impl Tessellator {
let clip_rect = self.clip_rect; let clip_rect = self.clip_rect;
if options.coarse_tessellation_culling if options.coarse_tessellation_culling
&& !quadratic_shape.bounding_rect().intersects(clip_rect) && !quadratic_shape.visual_bounding_rect().intersects(clip_rect)
{ {
return; return;
} }
@ -816,7 +816,8 @@ impl Tessellator {
) { ) {
let options = &self.options; let options = &self.options;
let clip_rect = self.clip_rect; 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; return;
} }
@ -870,7 +871,7 @@ impl Tessellator {
} }
if self.options.coarse_tessellation_culling if self.options.coarse_tessellation_culling
&& !path_shape.bounding_rect().intersects(self.clip_rect) && !path_shape.visual_bounding_rect().intersects(self.clip_rect)
{ {
return; return;
} }