Skip to content

Commit 714ed37

Browse files
committed
Allow rotation of rectangles and ellipses
1 parent 01770be commit 714ed37

File tree

5 files changed

+85
-5
lines changed

5 files changed

+85
-5
lines changed

crates/egui_demo_lib/src/demo/tests/tessellation_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ fn rect_shape_ui(ui: &mut egui::Ui, shape: &mut RectShape) {
294294
blur_width,
295295
round_to_pixels,
296296
brush: _,
297+
angle: _,
297298
} = shape;
298299

299300
let round_to_pixels = round_to_pixels.get_or_insert(true);

crates/epaint/src/shape_transform.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ pub fn adjust_colors(
5757
radius: _,
5858
fill,
5959
stroke,
60+
angle: _,
6061
})
6162
| Shape::Rect(RectShape {
6263
rect: _,
@@ -67,6 +68,7 @@ pub fn adjust_colors(
6768
round_to_pixels: _,
6869
blur_width: _,
6970
brush: _,
71+
angle: _,
7072
}) => {
7173
adjust_color(fill);
7274
adjust_color(&mut stroke.color);

crates/epaint/src/shapes/ellipse_shape.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ pub struct EllipseShape {
1010
pub radius: Vec2,
1111
pub fill: Color32,
1212
pub stroke: Stroke,
13+
14+
/// Rotate ellipse by this many radians clockwise around its center.
15+
pub angle: f32,
1316
}
1417

1518
impl EllipseShape {
@@ -20,6 +23,7 @@ impl EllipseShape {
2023
radius,
2124
fill: fill_color.into(),
2225
stroke: Default::default(),
26+
angle: 0.0,
2327
}
2428
}
2529

@@ -30,18 +34,38 @@ impl EllipseShape {
3034
radius,
3135
fill: Default::default(),
3236
stroke: stroke.into(),
37+
angle: 0.0,
3338
}
3439
}
3540

41+
/// Set the rotation of the ellipse (in radians, clockwise).
42+
/// The ellipse rotates around its center.
43+
#[inline]
44+
pub fn with_angle(mut self, angle: f32) -> Self {
45+
self.angle = angle;
46+
self
47+
}
48+
49+
/// Set the rotation of the ellipse (in radians, clockwise) around a custom pivot point.
50+
#[inline]
51+
pub fn with_angle_and_pivot(mut self, angle: f32, pivot: Pos2) -> Self {
52+
self.angle = angle;
53+
let rot = emath::Rot2::from_angle(angle);
54+
self.center = pivot + rot * (self.center - pivot);
55+
self
56+
}
57+
3658
/// The visual bounding rectangle (includes stroke width)
3759
pub fn visual_bounding_rect(&self) -> Rect {
3860
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
3961
Rect::NOTHING
4062
} else {
41-
Rect::from_center_size(
42-
self.center,
63+
let rect = Rect::from_center_size(
64+
Pos2::ZERO,
4365
self.radius * 2.0 + Vec2::splat(self.stroke.width),
44-
)
66+
);
67+
rect.rotate_bb(emath::Rot2::from_angle(self.angle))
68+
.translate(self.center.to_vec2())
4569
}
4670
}
4771
}

crates/epaint/src/shapes/rect_shape.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,16 @@ pub struct RectShape {
5454
/// Since most rectangles do not have a texture, this is optional and in an `Arc`,
5555
/// so that [`RectShape`] is kept small..
5656
pub brush: Option<Arc<Brush>>,
57+
58+
/// Rotate rectangle by this many radians clockwise around its center.
59+
pub angle: f32,
5760
}
5861

5962
#[test]
6063
fn rect_shape_size() {
6164
assert_eq!(
6265
std::mem::size_of::<RectShape>(),
63-
48,
66+
52,
6467
"RectShape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
6568
);
6669
assert!(
@@ -88,6 +91,7 @@ impl RectShape {
8891
round_to_pixels: None,
8992
blur_width: 0.0,
9093
brush: Default::default(),
94+
angle: 0.0,
9195
}
9296
}
9397

@@ -157,6 +161,25 @@ impl RectShape {
157161
self
158162
}
159163

164+
/// Set the rotation of the rectangle (in radians, clockwise).
165+
/// The rectangle rotates around its center.
166+
#[inline]
167+
pub fn with_angle(mut self, angle: f32) -> Self {
168+
self.angle = angle;
169+
self
170+
}
171+
172+
/// Set the rotation of the rectangle (in radians, clockwise) around a custom pivot point.
173+
#[inline]
174+
pub fn with_angle_and_pivot(mut self, angle: f32, pivot: Pos2) -> Self {
175+
self.angle = angle;
176+
let rot = emath::Rot2::from_angle(angle);
177+
let center = self.rect.center();
178+
let new_center = pivot + rot * (center - pivot);
179+
self.rect = self.rect.translate(new_center - center);
180+
self
181+
}
182+
160183
/// The visual bounding rectangle (includes stroke width)
161184
#[inline]
162185
pub fn visual_bounding_rect(&self) -> Rect {
@@ -168,7 +191,17 @@ impl RectShape {
168191
StrokeKind::Middle => self.stroke.width / 2.0,
169192
StrokeKind::Outside => self.stroke.width,
170193
};
171-
self.rect.expand(expand + self.blur_width / 2.0)
194+
let expanded = self.rect.expand(expand + self.blur_width / 2.0);
195+
if self.angle == 0.0 {
196+
expanded
197+
} else {
198+
// Rotate around the rectangle's center and compute bounding box
199+
let center = self.rect.center();
200+
let rect_relative = Rect::from_center_size(Pos2::ZERO, expanded.size());
201+
rect_relative
202+
.rotate_bb(emath::Rot2::from_angle(self.angle))
203+
.translate(center.to_vec2())
204+
}
172205
}
173206
}
174207

crates/epaint/src/tessellator.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,7 @@ impl Tessellator {
15451545
radius,
15461546
fill,
15471547
stroke,
1548+
angle,
15481549
} = shape;
15491550

15501551
if radius.x <= 0.0 || radius.y <= 0.0 {
@@ -1595,6 +1596,14 @@ impl Tessellator {
15951596
points.push(center + Vec2::new(0.0, -radius.y));
15961597
points.extend(quarter.iter().rev().map(|p| center + Vec2::new(p.x, -p.y)));
15971598

1599+
// Apply rotation if angle is non-zero
1600+
if angle != 0.0 {
1601+
let rot = emath::Rot2::from_angle(angle);
1602+
for point in &mut points {
1603+
*point = center + rot * (*point - center);
1604+
}
1605+
}
1606+
15981607
let path_stroke = PathStroke::from(stroke).outside();
15991608
self.scratchpad_path.clear();
16001609
self.scratchpad_path.add_line_loop(&points);
@@ -1772,6 +1781,7 @@ impl Tessellator {
17721781
round_to_pixels,
17731782
mut blur_width,
17741783
brush: _, // brush is extracted on its own, because it is not Copy
1784+
angle,
17751785
} = *rect_shape;
17761786

17771787
let mut corner_radius = CornerRadiusF32::from(corner_radius);
@@ -1939,6 +1949,16 @@ impl Tessellator {
19391949
let path = &mut self.scratchpad_path;
19401950
path.clear();
19411951
path::rounded_rectangle(&mut self.scratchpad_points, rect, corner_radius);
1952+
1953+
// Apply rotation if angle is non-zero
1954+
if angle != 0.0 {
1955+
let rot = emath::Rot2::from_angle(angle);
1956+
let center = rect.center();
1957+
for point in &mut self.scratchpad_points {
1958+
*point = center + rot * (*point - center);
1959+
}
1960+
}
1961+
19421962
path.add_line_loop(&self.scratchpad_points);
19431963

19441964
let path_stroke = PathStroke::from(stroke).with_kind(stroke_kind);

0 commit comments

Comments
 (0)