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}