epaint/shapes/
shape.rs

1//! The different shapes that can be painted.
2
3use std::sync::Arc;
4
5use emath::{Align2, Pos2, Rangef, Rect, TSTransform, Vec2, pos2};
6
7use crate::{
8    Color32, CornerRadius, Direction, Mesh, Stroke, StrokeKind, TextureId, Vertex,
9    stroke::PathStroke,
10    text::{FontId, FontsView, Galley},
11};
12
13use super::{
14    CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PathShape, QuadraticBezierShape,
15    RectShape, TextShape,
16};
17
18/// A paint primitive such as a circle or a piece of text.
19/// Coordinates are all screen space points (not physical pixels).
20///
21/// You should generally recreate your [`Shape`]s each frame,
22/// but storing them should also be fine with one exception:
23/// [`Shape::Text`] depends on the current `pixels_per_point` (dpi scale)
24/// and so must be recreated every time `pixels_per_point` changes.
25#[must_use = "Add a Shape to a Painter"]
26#[derive(Clone, Debug, PartialEq)]
27pub enum Shape {
28    /// Paint nothing. This can be useful as a placeholder.
29    Noop,
30
31    /// Recursively nest more shapes - sometimes a convenience to be able to do.
32    /// For performance reasons it is better to avoid it.
33    Vec(Vec<Self>),
34
35    /// Circle with optional outline and fill.
36    Circle(CircleShape),
37
38    /// Ellipse with optional outline and fill.
39    Ellipse(EllipseShape),
40
41    /// A line between two points.
42    LineSegment { points: [Pos2; 2], stroke: Stroke },
43
44    /// A series of lines between points.
45    /// The path can have a stroke and/or fill (if closed).
46    Path(PathShape),
47
48    /// Rectangle with optional outline and fill.
49    Rect(RectShape),
50
51    /// Text.
52    ///
53    /// This needs to be recreated if `pixels_per_point` (dpi scale) changes.
54    Text(TextShape),
55
56    /// A general triangle mesh.
57    ///
58    /// Can be used to display images.
59    ///
60    /// Wrapped in an [`Arc`] to minimize the size of [`Shape`].
61    Mesh(Arc<Mesh>),
62
63    /// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
64    QuadraticBezier(QuadraticBezierShape),
65
66    /// A cubic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
67    CubicBezier(CubicBezierShape),
68
69    /// Backend-specific painting.
70    Callback(PaintCallback),
71}
72
73#[test]
74fn shape_size() {
75    assert_eq!(
76        std::mem::size_of::<Shape>(),
77        64,
78        "Shape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
79    );
80    assert!(
81        std::mem::size_of::<Shape>() <= 64,
82        "Shape is getting way too big!"
83    );
84}
85
86#[test]
87fn shape_impl_send_sync() {
88    fn assert_send_sync<T: Send + Sync>() {}
89    assert_send_sync::<Shape>();
90}
91
92impl From<Vec<Self>> for Shape {
93    #[inline(always)]
94    fn from(shapes: Vec<Self>) -> Self {
95        Self::Vec(shapes)
96    }
97}
98
99impl From<Mesh> for Shape {
100    #[inline(always)]
101    fn from(mesh: Mesh) -> Self {
102        Self::Mesh(mesh.into())
103    }
104}
105
106impl From<Arc<Mesh>> for Shape {
107    #[inline(always)]
108    fn from(mesh: Arc<Mesh>) -> Self {
109        Self::Mesh(mesh)
110    }
111}
112
113/// ## Constructors
114impl Shape {
115    /// A line between two points.
116    /// More efficient than calling [`Self::line`].
117    #[inline]
118    pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
119        Self::LineSegment {
120            points,
121            stroke: stroke.into(),
122        }
123    }
124
125    /// A horizontal line.
126    pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> Self {
127        let x = x.into();
128        Self::LineSegment {
129            points: [pos2(x.min, y), pos2(x.max, y)],
130            stroke: stroke.into(),
131        }
132    }
133
134    /// A vertical line.
135    pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> Self {
136        let y = y.into();
137        Self::LineSegment {
138            points: [pos2(x, y.min), pos2(x, y.max)],
139            stroke: stroke.into(),
140        }
141    }
142
143    /// A line through many points.
144    ///
145    /// Use [`Self::line_segment`] instead if your line only connects two points.
146    #[inline]
147    pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
148        Self::Path(PathShape::line(points, stroke))
149    }
150
151    /// A line that closes back to the start point again.
152    #[inline]
153    pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
154        Self::Path(PathShape::closed_line(points, stroke))
155    }
156
157    /// Turn a line into equally spaced dots.
158    pub fn dotted_line(
159        path: &[Pos2],
160        color: impl Into<Color32>,
161        spacing: f32,
162        radius: f32,
163    ) -> Vec<Self> {
164        let mut shapes = Vec::new();
165        points_from_line(path, spacing, radius, color.into(), &mut shapes);
166        shapes
167    }
168
169    /// Turn a line into dashes.
170    pub fn dashed_line(
171        path: &[Pos2],
172        stroke: impl Into<Stroke>,
173        dash_length: f32,
174        gap_length: f32,
175    ) -> Vec<Self> {
176        let mut shapes = Vec::new();
177        dashes_from_line(
178            path,
179            stroke.into(),
180            &[dash_length],
181            &[gap_length],
182            &mut shapes,
183            0.,
184        );
185        shapes
186    }
187
188    /// Turn a line into dashes with different dash/gap lengths and a start offset.
189    pub fn dashed_line_with_offset(
190        path: &[Pos2],
191        stroke: impl Into<Stroke>,
192        dash_lengths: &[f32],
193        gap_lengths: &[f32],
194        dash_offset: f32,
195    ) -> Vec<Self> {
196        let mut shapes = Vec::new();
197        dashes_from_line(
198            path,
199            stroke.into(),
200            dash_lengths,
201            gap_lengths,
202            &mut shapes,
203            dash_offset,
204        );
205        shapes
206    }
207
208    /// Turn a line into dashes. If you need to create many dashed lines use this instead of
209    /// [`Self::dashed_line`].
210    pub fn dashed_line_many(
211        points: &[Pos2],
212        stroke: impl Into<Stroke>,
213        dash_length: f32,
214        gap_length: f32,
215        shapes: &mut Vec<Self>,
216    ) {
217        dashes_from_line(
218            points,
219            stroke.into(),
220            &[dash_length],
221            &[gap_length],
222            shapes,
223            0.,
224        );
225    }
226
227    /// Turn a line into dashes with different dash/gap lengths and a start offset. If you need to
228    /// create many dashed lines use this instead of [`Self::dashed_line_with_offset`].
229    pub fn dashed_line_many_with_offset(
230        points: &[Pos2],
231        stroke: impl Into<Stroke>,
232        dash_lengths: &[f32],
233        gap_lengths: &[f32],
234        dash_offset: f32,
235        shapes: &mut Vec<Self>,
236    ) {
237        dashes_from_line(
238            points,
239            stroke.into(),
240            dash_lengths,
241            gap_lengths,
242            shapes,
243            dash_offset,
244        );
245    }
246
247    /// A convex polygon with a fill and optional stroke.
248    ///
249    /// The most performant winding order is clockwise.
250    #[inline]
251    pub fn convex_polygon(
252        points: Vec<Pos2>,
253        fill: impl Into<Color32>,
254        stroke: impl Into<PathStroke>,
255    ) -> Self {
256        Self::Path(PathShape::convex_polygon(points, fill, stroke))
257    }
258
259    #[inline]
260    pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
261        Self::Circle(CircleShape::filled(center, radius, fill_color))
262    }
263
264    #[inline]
265    pub fn circle_stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
266        Self::Circle(CircleShape::stroke(center, radius, stroke))
267    }
268
269    #[inline]
270    pub fn ellipse_filled(center: Pos2, radius: Vec2, fill_color: impl Into<Color32>) -> Self {
271        Self::Ellipse(EllipseShape::filled(center, radius, fill_color))
272    }
273
274    #[inline]
275    pub fn ellipse_stroke(center: Pos2, radius: Vec2, stroke: impl Into<Stroke>) -> Self {
276        Self::Ellipse(EllipseShape::stroke(center, radius, stroke))
277    }
278
279    /// See also [`Self::rect_stroke`].
280    #[inline]
281    pub fn rect_filled(
282        rect: Rect,
283        corner_radius: impl Into<CornerRadius>,
284        fill_color: impl Into<Color32>,
285    ) -> Self {
286        Self::Rect(RectShape::filled(rect, corner_radius, fill_color))
287    }
288
289    /// See also [`Self::rect_filled`].
290    #[inline]
291    pub fn rect_stroke(
292        rect: Rect,
293        corner_radius: impl Into<CornerRadius>,
294        stroke: impl Into<Stroke>,
295        stroke_kind: StrokeKind,
296    ) -> Self {
297        Self::Rect(RectShape::stroke(rect, corner_radius, stroke, stroke_kind))
298    }
299
300    /// Paints a gradient rectangle that transitions from `color_from` to `color_to`
301    /// along the given `direction`.
302    ///
303    /// For example, [`Direction::TopDown`] paints `color_from` at the top edge fading
304    /// to `color_to` at the bottom edge.
305    #[inline]
306    pub fn gradient_rect(rect: Rect, direction: Direction, [from, to]: [Color32; 2]) -> Self {
307        let (left_top, right_top, left_bottom, right_bottom) = match direction {
308            Direction::TopDown => (from, from, to, to),
309            Direction::BottomUp => (to, to, from, from),
310            Direction::LeftToRight => (from, to, from, to),
311            Direction::RightToLeft => (to, from, to, from),
312        };
313
314        Self::from(Mesh {
315            indices: vec![0, 1, 2, 2, 1, 3],
316            vertices: vec![
317                Vertex::untextured(rect.left_top(), left_top),
318                Vertex::untextured(rect.right_top(), right_top),
319                Vertex::untextured(rect.left_bottom(), left_bottom),
320                Vertex::untextured(rect.right_bottom(), right_bottom),
321            ],
322            texture_id: Default::default(),
323        })
324    }
325
326    #[expect(clippy::needless_pass_by_value)]
327    pub fn text(
328        fonts: &mut FontsView<'_>,
329        pos: Pos2,
330        anchor: Align2,
331        text: impl ToString,
332        font_id: FontId,
333        color: Color32,
334    ) -> Self {
335        let galley = fonts.layout_no_wrap(text.to_string(), font_id, color);
336        let rect = anchor.anchor_size(pos, galley.size());
337        Self::galley(rect.min, galley, color)
338    }
339
340    /// Any uncolored parts of the [`Galley`] (using [`Color32::PLACEHOLDER`]) will be replaced with the given color.
341    ///
342    /// Any non-placeholder color in the galley takes precedence over this fallback color.
343    #[inline]
344    pub fn galley(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
345        TextShape::new(pos, galley, fallback_color).into()
346    }
347
348    /// All text color in the [`Galley`] will be replaced with the given color.
349    #[inline]
350    pub fn galley_with_override_text_color(
351        pos: Pos2,
352        galley: Arc<Galley>,
353        text_color: Color32,
354    ) -> Self {
355        TextShape::new(pos, galley, text_color)
356            .with_override_text_color(text_color)
357            .into()
358    }
359
360    #[inline]
361    #[deprecated = "Use `Shape::galley` or `Shape::galley_with_override_text_color` instead"]
362    pub fn galley_with_color(pos: Pos2, galley: Arc<Galley>, text_color: Color32) -> Self {
363        Self::galley_with_override_text_color(pos, galley, text_color)
364    }
365
366    #[inline]
367    pub fn mesh(mesh: impl Into<Arc<Mesh>>) -> Self {
368        let mesh = mesh.into();
369        debug_assert!(mesh.is_valid(), "Invalid mesh: {mesh:#?}");
370        Self::Mesh(mesh)
371    }
372
373    /// An image at the given position.
374    ///
375    /// `uv` should normally be `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`
376    /// unless you want to crop or flip the image.
377    ///
378    /// `tint` is a color multiplier. Use [`Color32::WHITE`] if you don't want to tint the image.
379    pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self {
380        let mut mesh = Mesh::with_texture(texture_id);
381        mesh.add_rect_with_uv(rect, uv, tint);
382        Self::mesh(mesh)
383    }
384
385    /// The visual bounding rectangle (includes stroke widths)
386    pub fn visual_bounding_rect(&self) -> Rect {
387        match self {
388            Self::Noop => Rect::NOTHING,
389            Self::Vec(shapes) => {
390                let mut rect = Rect::NOTHING;
391                for shape in shapes {
392                    rect |= shape.visual_bounding_rect();
393                }
394                rect
395            }
396            Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(),
397            Self::Ellipse(ellipse_shape) => ellipse_shape.visual_bounding_rect(),
398            Self::LineSegment { points, stroke } => {
399                if stroke.is_empty() {
400                    Rect::NOTHING
401                } else {
402                    Rect::from_two_pos(points[0], points[1]).expand(stroke.width / 2.0)
403                }
404            }
405            Self::Path(path_shape) => path_shape.visual_bounding_rect(),
406            Self::Rect(rect_shape) => rect_shape.visual_bounding_rect(),
407            Self::Text(text_shape) => text_shape.visual_bounding_rect(),
408            Self::Mesh(mesh) => mesh.calc_bounds(),
409            Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(),
410            Self::CubicBezier(bezier) => bezier.visual_bounding_rect(),
411            Self::Callback(custom) => custom.rect,
412        }
413    }
414}
415
416/// ## Inspection and transforms
417impl Shape {
418    #[inline(always)]
419    pub fn texture_id(&self) -> crate::TextureId {
420        if let Self::Mesh(mesh) = self {
421            mesh.texture_id
422        } else if let Self::Rect(rect_shape) = self {
423            rect_shape.fill_texture_id()
424        } else {
425            crate::TextureId::default()
426        }
427    }
428
429    /// Scale the shape by `factor`, in-place.
430    ///
431    /// A wrapper around [`Self::transform`].
432    #[inline(always)]
433    pub fn scale(&mut self, factor: f32) {
434        self.transform(TSTransform::from_scaling(factor));
435    }
436
437    /// Move the shape by `delta`, in-place.
438    ///
439    /// A wrapper around [`Self::transform`].
440    #[inline(always)]
441    pub fn translate(&mut self, delta: Vec2) {
442        self.transform(TSTransform::from_translation(delta));
443    }
444
445    /// Transform (move/scale) the shape in-place.
446    ///
447    /// If using a [`PaintCallback`], note that only the rect is scaled as opposed
448    /// to other shapes where the stroke is also scaled.
449    pub fn transform(&mut self, transform: TSTransform) {
450        match self {
451            Self::Noop => {}
452            Self::Vec(shapes) => {
453                for shape in shapes {
454                    shape.transform(transform);
455                }
456            }
457            Self::Circle(circle_shape) => {
458                circle_shape.center = transform * circle_shape.center;
459                circle_shape.radius *= transform.scaling;
460                circle_shape.stroke.width *= transform.scaling;
461            }
462            Self::Ellipse(ellipse_shape) => {
463                ellipse_shape.center = transform * ellipse_shape.center;
464                ellipse_shape.radius *= transform.scaling;
465                ellipse_shape.stroke.width *= transform.scaling;
466            }
467            Self::LineSegment { points, stroke } => {
468                for p in points {
469                    *p = transform * *p;
470                }
471                stroke.width *= transform.scaling;
472            }
473            Self::Path(path_shape) => {
474                for p in &mut path_shape.points {
475                    *p = transform * *p;
476                }
477                path_shape.stroke.width *= transform.scaling;
478            }
479            Self::Rect(rect_shape) => {
480                rect_shape.rect = transform * rect_shape.rect;
481                rect_shape.corner_radius *= transform.scaling;
482                rect_shape.stroke.width *= transform.scaling;
483                rect_shape.blur_width *= transform.scaling;
484            }
485            Self::Text(text_shape) => {
486                text_shape.transform(transform);
487            }
488            Self::Mesh(mesh) => {
489                Arc::make_mut(mesh).transform(transform);
490            }
491            Self::QuadraticBezier(bezier) => {
492                for p in &mut bezier.points {
493                    *p = transform * *p;
494                }
495                bezier.stroke.width *= transform.scaling;
496            }
497            Self::CubicBezier(bezier) => {
498                for p in &mut bezier.points {
499                    *p = transform * *p;
500                }
501                bezier.stroke.width *= transform.scaling;
502            }
503            Self::Callback(shape) => {
504                shape.rect = transform * shape.rect;
505            }
506        }
507    }
508}
509
510// ----------------------------------------------------------------------------
511
512/// Creates equally spaced filled circles from a line.
513fn points_from_line(
514    path: &[Pos2],
515    spacing: f32,
516    radius: f32,
517    color: Color32,
518    shapes: &mut Vec<Shape>,
519) {
520    let mut position_on_segment = 0.0;
521    for window in path.windows(2) {
522        let (start, end) = (window[0], window[1]);
523        let vector = end - start;
524        let segment_length = vector.length();
525        while position_on_segment < segment_length {
526            let new_point = start + vector * (position_on_segment / segment_length);
527            shapes.push(Shape::circle_filled(new_point, radius, color));
528            position_on_segment += spacing;
529        }
530        position_on_segment -= segment_length;
531    }
532}
533
534/// Creates dashes from a line.
535fn dashes_from_line(
536    path: &[Pos2],
537    stroke: Stroke,
538    dash_lengths: &[f32],
539    gap_lengths: &[f32],
540    shapes: &mut Vec<Shape>,
541    dash_offset: f32,
542) {
543    assert_eq!(
544        dash_lengths.len(),
545        gap_lengths.len(),
546        "Mismatched dash and gap lengths, got dash_lengths: {}, gap_lengths: {}",
547        dash_lengths.len(),
548        gap_lengths.len()
549    );
550    let mut position_on_segment = dash_offset;
551    let mut drawing_dash = false;
552    let mut step = 0;
553    let steps = dash_lengths.len();
554    for window in path.windows(2) {
555        let (start, end) = (window[0], window[1]);
556        let vector = end - start;
557        let segment_length = vector.length();
558
559        let mut start_point = start;
560        while position_on_segment < segment_length {
561            let new_point = start + vector * (position_on_segment / segment_length);
562            if drawing_dash {
563                // This is the end point.
564                shapes.push(Shape::line_segment([start_point, new_point], stroke));
565                position_on_segment += gap_lengths[step];
566                // Increment step counter
567                step += 1;
568                if step >= steps {
569                    step = 0;
570                }
571            } else {
572                // Start a new dash.
573                start_point = new_point;
574                position_on_segment += dash_lengths[step];
575            }
576            drawing_dash = !drawing_dash;
577        }
578
579        // If the segment ends and the dash is not finished, add the segment's end point.
580        if drawing_dash {
581            shapes.push(Shape::line_segment([start_point, end], stroke));
582        }
583
584        position_on_segment -= segment_length;
585    }
586}