epaint/shapes/
shape.rs

1//! The different shapes that can be painted.
2
3use std::sync::Arc;
4
5use emath::{pos2, Align2, Pos2, Rangef, Rect, TSTransform, Vec2};
6
7use crate::{
8    stroke::PathStroke,
9    text::{FontId, Fonts, Galley},
10    Color32, CornerRadius, Mesh, Stroke, StrokeKind, TextureId,
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<Shape>),
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>(), 64,
77        "Shape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
78    );
79    assert!(
80        std::mem::size_of::<Shape>() <= 64,
81        "Shape is getting way too big!"
82    );
83}
84
85#[test]
86fn shape_impl_send_sync() {
87    fn assert_send_sync<T: Send + Sync>() {}
88    assert_send_sync::<Shape>();
89}
90
91impl From<Vec<Self>> for Shape {
92    #[inline(always)]
93    fn from(shapes: Vec<Self>) -> Self {
94        Self::Vec(shapes)
95    }
96}
97
98impl From<Mesh> for Shape {
99    #[inline(always)]
100    fn from(mesh: Mesh) -> Self {
101        Self::Mesh(mesh.into())
102    }
103}
104
105impl From<Arc<Mesh>> for Shape {
106    #[inline(always)]
107    fn from(mesh: Arc<Mesh>) -> Self {
108        Self::Mesh(mesh)
109    }
110}
111
112/// ## Constructors
113impl Shape {
114    /// A line between two points.
115    /// More efficient than calling [`Self::line`].
116    #[inline]
117    pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
118        Self::LineSegment {
119            points,
120            stroke: stroke.into(),
121        }
122    }
123
124    /// A horizontal line.
125    pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> Self {
126        let x = x.into();
127        Self::LineSegment {
128            points: [pos2(x.min, y), pos2(x.max, y)],
129            stroke: stroke.into(),
130        }
131    }
132
133    /// A vertical line.
134    pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> Self {
135        let y = y.into();
136        Self::LineSegment {
137            points: [pos2(x, y.min), pos2(x, y.max)],
138            stroke: stroke.into(),
139        }
140    }
141
142    /// A line through many points.
143    ///
144    /// Use [`Self::line_segment`] instead if your line only connects two points.
145    #[inline]
146    pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
147        Self::Path(PathShape::line(points, stroke))
148    }
149
150    /// A line that closes back to the start point again.
151    #[inline]
152    pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
153        Self::Path(PathShape::closed_line(points, stroke))
154    }
155
156    /// Turn a line into equally spaced dots.
157    pub fn dotted_line(
158        path: &[Pos2],
159        color: impl Into<Color32>,
160        spacing: f32,
161        radius: f32,
162    ) -> Vec<Self> {
163        let mut shapes = Vec::new();
164        points_from_line(path, spacing, radius, color.into(), &mut shapes);
165        shapes
166    }
167
168    /// Turn a line into dashes.
169    pub fn dashed_line(
170        path: &[Pos2],
171        stroke: impl Into<Stroke>,
172        dash_length: f32,
173        gap_length: f32,
174    ) -> Vec<Self> {
175        let mut shapes = Vec::new();
176        dashes_from_line(
177            path,
178            stroke.into(),
179            &[dash_length],
180            &[gap_length],
181            &mut shapes,
182            0.,
183        );
184        shapes
185    }
186
187    /// Turn a line into dashes with different dash/gap lengths and a start offset.
188    pub fn dashed_line_with_offset(
189        path: &[Pos2],
190        stroke: impl Into<Stroke>,
191        dash_lengths: &[f32],
192        gap_lengths: &[f32],
193        dash_offset: f32,
194    ) -> Vec<Self> {
195        let mut shapes = Vec::new();
196        dashes_from_line(
197            path,
198            stroke.into(),
199            dash_lengths,
200            gap_lengths,
201            &mut shapes,
202            dash_offset,
203        );
204        shapes
205    }
206
207    /// Turn a line into dashes. If you need to create many dashed lines use this instead of
208    /// [`Self::dashed_line`].
209    pub fn dashed_line_many(
210        points: &[Pos2],
211        stroke: impl Into<Stroke>,
212        dash_length: f32,
213        gap_length: f32,
214        shapes: &mut Vec<Self>,
215    ) {
216        dashes_from_line(
217            points,
218            stroke.into(),
219            &[dash_length],
220            &[gap_length],
221            shapes,
222            0.,
223        );
224    }
225
226    /// Turn a line into dashes with different dash/gap lengths and a start offset. If you need to
227    /// create many dashed lines use this instead of [`Self::dashed_line_with_offset`].
228    pub fn dashed_line_many_with_offset(
229        points: &[Pos2],
230        stroke: impl Into<Stroke>,
231        dash_lengths: &[f32],
232        gap_lengths: &[f32],
233        dash_offset: f32,
234        shapes: &mut Vec<Self>,
235    ) {
236        dashes_from_line(
237            points,
238            stroke.into(),
239            dash_lengths,
240            gap_lengths,
241            shapes,
242            dash_offset,
243        );
244    }
245
246    /// A convex polygon with a fill and optional stroke.
247    ///
248    /// The most performant winding order is clockwise.
249    #[inline]
250    pub fn convex_polygon(
251        points: Vec<Pos2>,
252        fill: impl Into<Color32>,
253        stroke: impl Into<PathStroke>,
254    ) -> Self {
255        Self::Path(PathShape::convex_polygon(points, fill, stroke))
256    }
257
258    #[inline]
259    pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
260        Self::Circle(CircleShape::filled(center, radius, fill_color))
261    }
262
263    #[inline]
264    pub fn circle_stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
265        Self::Circle(CircleShape::stroke(center, radius, stroke))
266    }
267
268    #[inline]
269    pub fn ellipse_filled(center: Pos2, radius: Vec2, fill_color: impl Into<Color32>) -> Self {
270        Self::Ellipse(EllipseShape::filled(center, radius, fill_color))
271    }
272
273    #[inline]
274    pub fn ellipse_stroke(center: Pos2, radius: Vec2, stroke: impl Into<Stroke>) -> Self {
275        Self::Ellipse(EllipseShape::stroke(center, radius, stroke))
276    }
277
278    /// See also [`Self::rect_stroke`].
279    #[inline]
280    pub fn rect_filled(
281        rect: Rect,
282        corner_radius: impl Into<CornerRadius>,
283        fill_color: impl Into<Color32>,
284    ) -> Self {
285        Self::Rect(RectShape::filled(rect, corner_radius, fill_color))
286    }
287
288    /// See also [`Self::rect_filled`].
289    #[inline]
290    pub fn rect_stroke(
291        rect: Rect,
292        corner_radius: impl Into<CornerRadius>,
293        stroke: impl Into<Stroke>,
294        stroke_kind: StrokeKind,
295    ) -> Self {
296        Self::Rect(RectShape::stroke(rect, corner_radius, stroke, stroke_kind))
297    }
298
299    #[allow(clippy::needless_pass_by_value)]
300    pub fn text(
301        fonts: &Fonts,
302        pos: Pos2,
303        anchor: Align2,
304        text: impl ToString,
305        font_id: FontId,
306        color: Color32,
307    ) -> Self {
308        let galley = fonts.layout_no_wrap(text.to_string(), font_id, color);
309        let rect = anchor.anchor_size(pos, galley.size());
310        Self::galley(rect.min, galley, color)
311    }
312
313    /// Any uncolored parts of the [`Galley`] (using [`Color32::PLACEHOLDER`]) will be replaced with the given color.
314    ///
315    /// Any non-placeholder color in the galley takes precedence over this fallback color.
316    #[inline]
317    pub fn galley(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
318        TextShape::new(pos, galley, fallback_color).into()
319    }
320
321    /// All text color in the [`Galley`] will be replaced with the given color.
322    #[inline]
323    pub fn galley_with_override_text_color(
324        pos: Pos2,
325        galley: Arc<Galley>,
326        text_color: Color32,
327    ) -> Self {
328        TextShape::new(pos, galley, text_color)
329            .with_override_text_color(text_color)
330            .into()
331    }
332
333    #[inline]
334    #[deprecated = "Use `Shape::galley` or `Shape::galley_with_override_text_color` instead"]
335    pub fn galley_with_color(pos: Pos2, galley: Arc<Galley>, text_color: Color32) -> Self {
336        Self::galley_with_override_text_color(pos, galley, text_color)
337    }
338
339    #[inline]
340    pub fn mesh(mesh: impl Into<Arc<Mesh>>) -> Self {
341        let mesh = mesh.into();
342        debug_assert!(mesh.is_valid());
343        Self::Mesh(mesh)
344    }
345
346    /// An image at the given position.
347    ///
348    /// `uv` should normally be `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`
349    /// unless you want to crop or flip the image.
350    ///
351    /// `tint` is a color multiplier. Use [`Color32::WHITE`] if you don't want to tint the image.
352    pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self {
353        let mut mesh = Mesh::with_texture(texture_id);
354        mesh.add_rect_with_uv(rect, uv, tint);
355        Self::mesh(mesh)
356    }
357
358    /// The visual bounding rectangle (includes stroke widths)
359    pub fn visual_bounding_rect(&self) -> Rect {
360        match self {
361            Self::Noop => Rect::NOTHING,
362            Self::Vec(shapes) => {
363                let mut rect = Rect::NOTHING;
364                for shape in shapes {
365                    rect = rect.union(shape.visual_bounding_rect());
366                }
367                rect
368            }
369            Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(),
370            Self::Ellipse(ellipse_shape) => ellipse_shape.visual_bounding_rect(),
371            Self::LineSegment { points, stroke } => {
372                if stroke.is_empty() {
373                    Rect::NOTHING
374                } else {
375                    Rect::from_two_pos(points[0], points[1]).expand(stroke.width / 2.0)
376                }
377            }
378            Self::Path(path_shape) => path_shape.visual_bounding_rect(),
379            Self::Rect(rect_shape) => rect_shape.visual_bounding_rect(),
380            Self::Text(text_shape) => text_shape.visual_bounding_rect(),
381            Self::Mesh(mesh) => mesh.calc_bounds(),
382            Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(),
383            Self::CubicBezier(bezier) => bezier.visual_bounding_rect(),
384            Self::Callback(custom) => custom.rect,
385        }
386    }
387}
388
389/// ## Inspection and transforms
390impl Shape {
391    #[inline(always)]
392    pub fn texture_id(&self) -> crate::TextureId {
393        if let Self::Mesh(mesh) = self {
394            mesh.texture_id
395        } else if let Self::Rect(rect_shape) = self {
396            rect_shape.fill_texture_id()
397        } else {
398            crate::TextureId::default()
399        }
400    }
401
402    /// Scale the shape by `factor`, in-place.
403    ///
404    /// A wrapper around [`Self::transform`].
405    #[inline(always)]
406    pub fn scale(&mut self, factor: f32) {
407        self.transform(TSTransform::from_scaling(factor));
408    }
409
410    /// Move the shape by `delta`, in-place.
411    ///
412    /// A wrapper around [`Self::transform`].
413    #[inline(always)]
414    pub fn translate(&mut self, delta: Vec2) {
415        self.transform(TSTransform::from_translation(delta));
416    }
417
418    /// Move the shape by this many points, in-place.
419    ///
420    /// If using a [`PaintCallback`], note that only the rect is scaled as opposed
421    /// to other shapes where the stroke is also scaled.
422    pub fn transform(&mut self, transform: TSTransform) {
423        match self {
424            Self::Noop => {}
425            Self::Vec(shapes) => {
426                for shape in shapes {
427                    shape.transform(transform);
428                }
429            }
430            Self::Circle(circle_shape) => {
431                circle_shape.center = transform * circle_shape.center;
432                circle_shape.radius *= transform.scaling;
433                circle_shape.stroke.width *= transform.scaling;
434            }
435            Self::Ellipse(ellipse_shape) => {
436                ellipse_shape.center = transform * ellipse_shape.center;
437                ellipse_shape.radius *= transform.scaling;
438                ellipse_shape.stroke.width *= transform.scaling;
439            }
440            Self::LineSegment { points, stroke } => {
441                for p in points {
442                    *p = transform * *p;
443                }
444                stroke.width *= transform.scaling;
445            }
446            Self::Path(path_shape) => {
447                for p in &mut path_shape.points {
448                    *p = transform * *p;
449                }
450                path_shape.stroke.width *= transform.scaling;
451            }
452            Self::Rect(rect_shape) => {
453                rect_shape.rect = transform * rect_shape.rect;
454                rect_shape.corner_radius *= transform.scaling;
455                rect_shape.stroke.width *= transform.scaling;
456                rect_shape.blur_width *= transform.scaling;
457            }
458            Self::Text(text_shape) => {
459                text_shape.pos = transform * text_shape.pos;
460
461                // Scale text:
462                let galley = Arc::make_mut(&mut text_shape.galley);
463                for row in &mut galley.rows {
464                    row.visuals.mesh_bounds = transform.scaling * row.visuals.mesh_bounds;
465                    for v in &mut row.visuals.mesh.vertices {
466                        v.pos = Pos2::new(transform.scaling * v.pos.x, transform.scaling * v.pos.y);
467                    }
468                }
469
470                galley.mesh_bounds = transform.scaling * galley.mesh_bounds;
471                galley.rect = transform.scaling * galley.rect;
472            }
473            Self::Mesh(mesh) => {
474                Arc::make_mut(mesh).transform(transform);
475            }
476            Self::QuadraticBezier(bezier) => {
477                for p in &mut bezier.points {
478                    *p = transform * *p;
479                }
480                bezier.stroke.width *= transform.scaling;
481            }
482            Self::CubicBezier(bezier) => {
483                for p in &mut bezier.points {
484                    *p = transform * *p;
485                }
486                bezier.stroke.width *= transform.scaling;
487            }
488            Self::Callback(shape) => {
489                shape.rect = transform * shape.rect;
490            }
491        }
492    }
493}
494
495// ----------------------------------------------------------------------------
496
497/// Creates equally spaced filled circles from a line.
498fn points_from_line(
499    path: &[Pos2],
500    spacing: f32,
501    radius: f32,
502    color: Color32,
503    shapes: &mut Vec<Shape>,
504) {
505    let mut position_on_segment = 0.0;
506    for window in path.windows(2) {
507        let (start, end) = (window[0], window[1]);
508        let vector = end - start;
509        let segment_length = vector.length();
510        while position_on_segment < segment_length {
511            let new_point = start + vector * (position_on_segment / segment_length);
512            shapes.push(Shape::circle_filled(new_point, radius, color));
513            position_on_segment += spacing;
514        }
515        position_on_segment -= segment_length;
516    }
517}
518
519/// Creates dashes from a line.
520fn dashes_from_line(
521    path: &[Pos2],
522    stroke: Stroke,
523    dash_lengths: &[f32],
524    gap_lengths: &[f32],
525    shapes: &mut Vec<Shape>,
526    dash_offset: f32,
527) {
528    assert_eq!(dash_lengths.len(), gap_lengths.len());
529    let mut position_on_segment = dash_offset;
530    let mut drawing_dash = false;
531    let mut step = 0;
532    let steps = dash_lengths.len();
533    for window in path.windows(2) {
534        let (start, end) = (window[0], window[1]);
535        let vector = end - start;
536        let segment_length = vector.length();
537
538        let mut start_point = start;
539        while position_on_segment < segment_length {
540            let new_point = start + vector * (position_on_segment / segment_length);
541            if drawing_dash {
542                // This is the end point.
543                shapes.push(Shape::line_segment([start_point, new_point], stroke));
544                position_on_segment += gap_lengths[step];
545                // Increment step counter
546                step += 1;
547                if step >= steps {
548                    step = 0;
549                }
550            } else {
551                // Start a new dash.
552                start_point = new_point;
553                position_on_segment += dash_lengths[step];
554            }
555            drawing_dash = !drawing_dash;
556        }
557
558        // If the segment ends and the dash is not finished, add the segment's end point.
559        if drawing_dash {
560            shapes.push(Shape::line_segment([start_point, end], stroke));
561        }
562
563        position_on_segment -= segment_length;
564    }
565}