1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 23:13:13 -04:00
Files
egui/crates/epaint/src/shapes/rect_shape.rs
Ryan Bluth 5d5f0dedcc 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
2026-03-24 13:58:02 +01:00

224 lines
6.9 KiB
Rust

use std::sync::Arc;
use crate::*;
/// How to paint a rectangle.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RectShape {
pub rect: Rect,
/// How rounded the corners of the rectangle are.
///
/// Use [`CornerRadius::ZERO`] for for sharp corners.
///
/// This is the corner radii of the rectangle.
/// If there is a stroke, then the stroke will have an inner and outer corner radius,
/// and those will depend on [`StrokeKind`] and the stroke width.
///
/// For [`StrokeKind::Inside`], the outside of the stroke coincides with the rectangle,
/// so the rounding will in this case specify the outer corner radius.
pub corner_radius: CornerRadius,
/// How to fill the rectangle.
pub fill: Color32,
/// The thickness and color of the outline.
///
/// Whether or not the stroke is inside or outside the edge of [`Self::rect`],
/// is controlled by [`Self::stroke_kind`].
pub stroke: Stroke,
/// Is the stroke on the inside, outside, or centered on the rectangle?
///
/// If you want to perfectly tile rectangles, use [`StrokeKind::Inside`].
pub stroke_kind: StrokeKind,
/// Snap the rectangle to pixels?
///
/// Rounding produces sharper rectangles.
///
/// If `None`, [`crate::TessellationOptions::round_rects_to_pixels`] will be used.
pub round_to_pixels: Option<bool>,
/// If larger than zero, the edges of the rectangle
/// (for both fill and stroke) will be blurred.
///
/// This can be used to produce shadows and glow effects.
///
/// The blur is currently implemented using a simple linear blur in sRGBA gamma space.
pub blur_width: f32,
/// Controls texturing, if any.
///
/// 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>(),
56,
"RectShape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
);
assert!(
std::mem::size_of::<RectShape>() <= 64,
"RectShape is getting way too big!"
);
}
impl RectShape {
/// See also [`Self::filled`] and [`Self::stroke`].
#[inline]
pub fn new(
rect: Rect,
corner_radius: impl Into<CornerRadius>,
fill_color: impl Into<Color32>,
stroke: impl Into<Stroke>,
stroke_kind: StrokeKind,
) -> Self {
Self {
rect,
corner_radius: corner_radius.into(),
fill: fill_color.into(),
stroke: stroke.into(),
stroke_kind,
round_to_pixels: None,
blur_width: 0.0,
brush: Default::default(),
angle: 0.0,
}
}
#[inline]
pub fn filled(
rect: Rect,
corner_radius: impl Into<CornerRadius>,
fill_color: impl Into<Color32>,
) -> Self {
Self::new(
rect,
corner_radius,
fill_color,
Stroke::NONE,
StrokeKind::Outside, // doesn't matter
)
}
#[inline]
pub fn stroke(
rect: Rect,
corner_radius: impl Into<CornerRadius>,
stroke: impl Into<Stroke>,
stroke_kind: StrokeKind,
) -> Self {
let fill = Color32::TRANSPARENT;
Self::new(rect, corner_radius, fill, stroke, stroke_kind)
}
/// Set if the stroke is on the inside, outside, or centered on the rectangle.
#[inline]
pub fn with_stroke_kind(mut self, stroke_kind: StrokeKind) -> Self {
self.stroke_kind = stroke_kind;
self
}
/// Snap the rectangle to pixels?
///
/// Rounding produces sharper rectangles.
///
/// If `None`, [`crate::TessellationOptions::round_rects_to_pixels`] will be used.
#[inline]
pub fn with_round_to_pixels(mut self, round_to_pixels: bool) -> Self {
self.round_to_pixels = Some(round_to_pixels);
self
}
/// If larger than zero, the edges of the rectangle
/// (for both fill and stroke) will be blurred.
///
/// This can be used to produce shadows and glow effects.
///
/// The blur is currently implemented using a simple linear blur in `sRGBA` gamma space.
#[inline]
pub fn with_blur_width(mut self, blur_width: f32) -> Self {
self.blur_width = blur_width;
self
}
/// Set the texture to use when painting this rectangle, if any.
#[inline]
pub fn with_texture(mut self, fill_texture_id: TextureId, uv: Rect) -> Self {
self.brush = Some(Arc::new(Brush {
fill_texture_id,
uv,
}));
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 {
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
Rect::NOTHING
} else {
let expand = match self.stroke_kind {
StrokeKind::Inside => 0.0,
StrokeKind::Middle => self.stroke.width / 2.0,
StrokeKind::Outside => self.stroke.width,
};
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())
}
}
}
/// The texture to use when painting this rectangle, if any.
///
/// If no texture is set, this will return [`TextureId::default`].
pub fn fill_texture_id(&self) -> TextureId {
self.brush
.as_ref()
.map_or_else(TextureId::default, |brush| brush.fill_texture_id)
}
}
impl From<RectShape> for Shape {
#[inline(always)]
fn from(shape: RectShape) -> Self {
Self::Rect(shape)
}
}