epaint/shapes/
rect_shape.rs

1use std::sync::Arc;
2
3use crate::*;
4
5/// How to paint a rectangle.
6#[derive(Clone, Debug, PartialEq)]
7#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
8pub struct RectShape {
9    pub rect: Rect,
10
11    /// How rounded the corners of the rectangle are.
12    ///
13    /// Use [`CornerRadius::ZERO`] for for sharp corners.
14    ///
15    /// This is the corner radii of the rectangle.
16    /// If there is a stroke, then the stroke will have an inner and outer corner radius,
17    /// and those will depend on [`StrokeKind`] and the stroke width.
18    ///
19    /// For [`StrokeKind::Inside`], the outside of the stroke coincides with the rectangle,
20    /// so the rounding will in this case specify the outer corner radius.
21    pub corner_radius: CornerRadius,
22
23    /// How to fill the rectangle.
24    pub fill: Color32,
25
26    /// The thickness and color of the outline.
27    ///
28    /// Whether or not the stroke is inside or outside the edge of [`Self::rect`],
29    /// is controlled by [`Self::stroke_kind`].
30    pub stroke: Stroke,
31
32    /// Is the stroke on the inside, outside, or centered on the rectangle?
33    ///
34    /// If you want to perfectly tile rectangles, use [`StrokeKind::Inside`].
35    pub stroke_kind: StrokeKind,
36
37    /// Snap the rectangle to pixels?
38    ///
39    /// Rounding produces sharper rectangles.
40    ///
41    /// If `None`, [`crate::TessellationOptions::round_rects_to_pixels`] will be used.
42    pub round_to_pixels: Option<bool>,
43
44    /// If larger than zero, the edges of the rectangle
45    /// (for both fill and stroke) will be blurred.
46    ///
47    /// This can be used to produce shadows and glow effects.
48    ///
49    /// The blur is currently implemented using a simple linear blur in sRGBA gamma space.
50    pub blur_width: f32,
51
52    /// Controls texturing, if any.
53    ///
54    /// Since most rectangles do not have a texture, this is optional and in an `Arc`,
55    /// so that [`RectShape`] is kept small..
56    pub brush: Option<Arc<Brush>>,
57
58    /// Rotate rectangle by this many radians clockwise around its center.
59    pub angle: f32,
60}
61
62#[test]
63fn rect_shape_size() {
64    assert_eq!(
65        std::mem::size_of::<RectShape>(),
66        56,
67        "RectShape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
68    );
69    assert!(
70        std::mem::size_of::<RectShape>() <= 64,
71        "RectShape is getting way too big!"
72    );
73}
74
75impl RectShape {
76    /// See also [`Self::filled`] and [`Self::stroke`].
77    #[inline]
78    pub fn new(
79        rect: Rect,
80        corner_radius: impl Into<CornerRadius>,
81        fill_color: impl Into<Color32>,
82        stroke: impl Into<Stroke>,
83        stroke_kind: StrokeKind,
84    ) -> Self {
85        Self {
86            rect,
87            corner_radius: corner_radius.into(),
88            fill: fill_color.into(),
89            stroke: stroke.into(),
90            stroke_kind,
91            round_to_pixels: None,
92            blur_width: 0.0,
93            brush: Default::default(),
94            angle: 0.0,
95        }
96    }
97
98    #[inline]
99    pub fn filled(
100        rect: Rect,
101        corner_radius: impl Into<CornerRadius>,
102        fill_color: impl Into<Color32>,
103    ) -> Self {
104        Self::new(
105            rect,
106            corner_radius,
107            fill_color,
108            Stroke::NONE,
109            StrokeKind::Outside, // doesn't matter
110        )
111    }
112
113    #[inline]
114    pub fn stroke(
115        rect: Rect,
116        corner_radius: impl Into<CornerRadius>,
117        stroke: impl Into<Stroke>,
118        stroke_kind: StrokeKind,
119    ) -> Self {
120        let fill = Color32::TRANSPARENT;
121        Self::new(rect, corner_radius, fill, stroke, stroke_kind)
122    }
123
124    /// Set if the stroke is on the inside, outside, or centered on the rectangle.
125    #[inline]
126    pub fn with_stroke_kind(mut self, stroke_kind: StrokeKind) -> Self {
127        self.stroke_kind = stroke_kind;
128        self
129    }
130
131    /// Snap the rectangle to pixels?
132    ///
133    /// Rounding produces sharper rectangles.
134    ///
135    /// If `None`, [`crate::TessellationOptions::round_rects_to_pixels`] will be used.
136    #[inline]
137    pub fn with_round_to_pixels(mut self, round_to_pixels: bool) -> Self {
138        self.round_to_pixels = Some(round_to_pixels);
139        self
140    }
141
142    /// If larger than zero, the edges of the rectangle
143    /// (for both fill and stroke) will be blurred.
144    ///
145    /// This can be used to produce shadows and glow effects.
146    ///
147    /// The blur is currently implemented using a simple linear blur in `sRGBA` gamma space.
148    #[inline]
149    pub fn with_blur_width(mut self, blur_width: f32) -> Self {
150        self.blur_width = blur_width;
151        self
152    }
153
154    /// Set the texture to use when painting this rectangle, if any.
155    #[inline]
156    pub fn with_texture(mut self, fill_texture_id: TextureId, uv: Rect) -> Self {
157        self.brush = Some(Arc::new(Brush {
158            fill_texture_id,
159            uv,
160        }));
161        self
162    }
163
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
183    /// The visual bounding rectangle (includes stroke width)
184    #[inline]
185    pub fn visual_bounding_rect(&self) -> Rect {
186        if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
187            Rect::NOTHING
188        } else {
189            let expand = match self.stroke_kind {
190                StrokeKind::Inside => 0.0,
191                StrokeKind::Middle => self.stroke.width / 2.0,
192                StrokeKind::Outside => self.stroke.width,
193            };
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            }
205        }
206    }
207
208    /// The texture to use when painting this rectangle, if any.
209    ///
210    /// If no texture is set, this will return [`TextureId::default`].
211    pub fn fill_texture_id(&self) -> TextureId {
212        self.brush
213            .as_ref()
214            .map_or_else(TextureId::default, |brush| brush.fill_texture_id)
215    }
216}
217
218impl From<RectShape> for Shape {
219    #[inline(always)]
220    fn from(shape: RectShape) -> Self {
221        Self::Rect(shape)
222    }
223}