mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Allow rotation of rectangles and ellipses (#7682)
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to test and add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> Added the ability to rotate rectangles and ellipses. Similar to the existing text implementation * [x ] I have followed the instructions in the PR template
This commit is contained in:
@@ -297,6 +297,7 @@ fn rect_shape_ui(ui: &mut egui::Ui, shape: &mut RectShape) {
|
||||
blur_width,
|
||||
round_to_pixels,
|
||||
brush: _,
|
||||
angle: _,
|
||||
} = shape;
|
||||
|
||||
let round_to_pixels = round_to_pixels.get_or_insert(true);
|
||||
|
||||
@@ -57,6 +57,7 @@ pub fn adjust_colors(
|
||||
radius: _,
|
||||
fill,
|
||||
stroke,
|
||||
angle: _,
|
||||
})
|
||||
| Shape::Rect(RectShape {
|
||||
rect: _,
|
||||
@@ -67,6 +68,7 @@ pub fn adjust_colors(
|
||||
round_to_pixels: _,
|
||||
blur_width: _,
|
||||
brush: _,
|
||||
angle: _,
|
||||
}) => {
|
||||
adjust_color(fill);
|
||||
adjust_color(&mut stroke.color);
|
||||
|
||||
@@ -10,6 +10,9 @@ pub struct EllipseShape {
|
||||
pub radius: Vec2,
|
||||
pub fill: Color32,
|
||||
pub stroke: Stroke,
|
||||
|
||||
/// Rotate ellipse by this many radians clockwise around its center.
|
||||
pub angle: f32,
|
||||
}
|
||||
|
||||
impl EllipseShape {
|
||||
@@ -20,6 +23,7 @@ impl EllipseShape {
|
||||
radius,
|
||||
fill: fill_color.into(),
|
||||
stroke: Default::default(),
|
||||
angle: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,18 +34,38 @@ impl EllipseShape {
|
||||
radius,
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
angle: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the rotation of the ellipse (in radians, clockwise).
|
||||
/// The ellipse rotates around its center.
|
||||
#[inline]
|
||||
pub fn with_angle(mut self, angle: f32) -> Self {
|
||||
self.angle = angle;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the rotation of the ellipse (in radians, clockwise) around a custom pivot point.
|
||||
#[inline]
|
||||
pub fn with_angle_and_pivot(mut self, angle: f32, pivot: Pos2) -> Self {
|
||||
self.angle = angle;
|
||||
let rot = emath::Rot2::from_angle(angle);
|
||||
self.center = pivot + rot * (self.center - pivot);
|
||||
self
|
||||
}
|
||||
|
||||
/// 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,
|
||||
let rect = Rect::from_center_size(
|
||||
Pos2::ZERO,
|
||||
self.radius * 2.0 + Vec2::splat(self.stroke.width),
|
||||
)
|
||||
);
|
||||
rect.rotate_bb(emath::Rot2::from_angle(self.angle))
|
||||
.translate(self.center.to_vec2())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,13 +54,16 @@ pub struct RectShape {
|
||||
/// Since most rectangles do not have a texture, this is optional and in an `Arc`,
|
||||
/// so that [`RectShape`] is kept small..
|
||||
pub brush: Option<Arc<Brush>>,
|
||||
|
||||
/// Rotate rectangle by this many radians clockwise around its center.
|
||||
pub angle: f32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rect_shape_size() {
|
||||
assert_eq!(
|
||||
std::mem::size_of::<RectShape>(),
|
||||
48,
|
||||
56,
|
||||
"RectShape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
|
||||
);
|
||||
assert!(
|
||||
@@ -88,6 +91,7 @@ impl RectShape {
|
||||
round_to_pixels: None,
|
||||
blur_width: 0.0,
|
||||
brush: Default::default(),
|
||||
angle: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +161,25 @@ impl RectShape {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the rotation of the rectangle (in radians, clockwise).
|
||||
/// The rectangle rotates around its center.
|
||||
#[inline]
|
||||
pub fn with_angle(mut self, angle: f32) -> Self {
|
||||
self.angle = angle;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the rotation of the rectangle (in radians, clockwise) around a custom pivot point.
|
||||
#[inline]
|
||||
pub fn with_angle_and_pivot(mut self, angle: f32, pivot: Pos2) -> Self {
|
||||
self.angle = angle;
|
||||
let rot = emath::Rot2::from_angle(angle);
|
||||
let center = self.rect.center();
|
||||
let new_center = pivot + rot * (center - pivot);
|
||||
self.rect = self.rect.translate(new_center - center);
|
||||
self
|
||||
}
|
||||
|
||||
/// The visual bounding rectangle (includes stroke width)
|
||||
#[inline]
|
||||
pub fn visual_bounding_rect(&self) -> Rect {
|
||||
@@ -168,7 +191,17 @@ impl RectShape {
|
||||
StrokeKind::Middle => self.stroke.width / 2.0,
|
||||
StrokeKind::Outside => self.stroke.width,
|
||||
};
|
||||
self.rect.expand(expand + self.blur_width / 2.0)
|
||||
let expanded = self.rect.expand(expand + self.blur_width / 2.0);
|
||||
if self.angle == 0.0 {
|
||||
expanded
|
||||
} else {
|
||||
// Rotate around the rectangle's center and compute bounding box
|
||||
let center = self.rect.center();
|
||||
let rect_relative = Rect::from_center_size(Pos2::ZERO, expanded.size());
|
||||
rect_relative
|
||||
.rotate_bb(emath::Rot2::from_angle(self.angle))
|
||||
.translate(center.to_vec2())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1546,6 +1546,7 @@ impl Tessellator {
|
||||
radius,
|
||||
fill,
|
||||
stroke,
|
||||
angle,
|
||||
} = shape;
|
||||
|
||||
if radius.x <= 0.0 || radius.y <= 0.0 {
|
||||
@@ -1596,6 +1597,14 @@ impl Tessellator {
|
||||
points.push(center + Vec2::new(0.0, -radius.y));
|
||||
points.extend(quarter.iter().rev().map(|p| center + Vec2::new(p.x, -p.y)));
|
||||
|
||||
// Apply rotation if angle is non-zero
|
||||
if angle != 0.0 {
|
||||
let rot = emath::Rot2::from_angle(angle);
|
||||
for point in &mut points {
|
||||
*point = center + rot * (*point - center);
|
||||
}
|
||||
}
|
||||
|
||||
let path_stroke = PathStroke::from(stroke).outside();
|
||||
self.scratchpad_path.clear();
|
||||
self.scratchpad_path.add_line_loop(&points);
|
||||
@@ -1773,6 +1782,7 @@ impl Tessellator {
|
||||
round_to_pixels,
|
||||
mut blur_width,
|
||||
brush: _, // brush is extracted on its own, because it is not Copy
|
||||
angle,
|
||||
} = *rect_shape;
|
||||
|
||||
let mut corner_radius = CornerRadiusF32::from(corner_radius);
|
||||
@@ -1940,6 +1950,16 @@ impl Tessellator {
|
||||
let path = &mut self.scratchpad_path;
|
||||
path.clear();
|
||||
path::rounded_rectangle(&mut self.scratchpad_points, rect, corner_radius);
|
||||
|
||||
// Apply rotation if angle is non-zero
|
||||
if angle != 0.0 {
|
||||
let rot = emath::Rot2::from_angle(angle);
|
||||
let center = rect.center();
|
||||
for point in &mut self.scratchpad_points {
|
||||
*point = center + rot * (*point - center);
|
||||
}
|
||||
}
|
||||
|
||||
path.add_line_loop(&self.scratchpad_points);
|
||||
|
||||
let path_stroke = PathStroke::from(stroke).with_kind(stroke_kind);
|
||||
|
||||
Reference in New Issue
Block a user