iced_wgpu/
geometry.rs

1//! Build and draw geometry.
2use crate::core::text::LineHeight;
3use crate::core::{
4    self, Pixels, Point, Radians, Rectangle, Size, Svg, Transformation, Vector,
5};
6use crate::graphics::cache::{self, Cached};
7use crate::graphics::color;
8use crate::graphics::geometry::fill::{self, Fill};
9use crate::graphics::geometry::{
10    self, LineCap, LineDash, LineJoin, Path, Stroke, Style,
11};
12use crate::graphics::gradient::{self, Gradient};
13use crate::graphics::mesh::{self, Mesh};
14use crate::graphics::{Image, Text};
15use crate::text;
16use crate::triangle;
17
18use lyon::geom::euclid;
19use lyon::tessellation;
20
21use std::borrow::Cow;
22use std::sync::Arc;
23
24#[derive(Debug)]
25pub enum Geometry {
26    Live {
27        meshes: Vec<Mesh>,
28        images: Vec<Image>,
29        text: Vec<Text>,
30    },
31    Cached(Cache),
32}
33
34#[derive(Debug, Clone, Default)]
35pub struct Cache {
36    pub meshes: Option<triangle::Cache>,
37    pub images: Option<Arc<[Image]>>,
38    pub text: Option<text::Cache>,
39}
40
41impl Cached for Geometry {
42    type Cache = Cache;
43
44    fn load(cache: &Self::Cache) -> Self {
45        Geometry::Cached(cache.clone())
46    }
47
48    fn cache(
49        self,
50        group: cache::Group,
51        previous: Option<Self::Cache>,
52    ) -> Self::Cache {
53        match self {
54            Self::Live {
55                meshes,
56                images,
57                text,
58            } => {
59                let images = if images.is_empty() {
60                    None
61                } else {
62                    Some(Arc::from(images))
63                };
64
65                if let Some(mut previous) = previous {
66                    if let Some(cache) = &mut previous.meshes {
67                        cache.update(meshes);
68                    } else {
69                        previous.meshes = triangle::Cache::new(meshes);
70                    }
71
72                    if let Some(cache) = &mut previous.text {
73                        cache.update(text);
74                    } else {
75                        previous.text = text::Cache::new(group, text);
76                    }
77
78                    previous.images = images;
79
80                    previous
81                } else {
82                    Cache {
83                        meshes: triangle::Cache::new(meshes),
84                        images,
85                        text: text::Cache::new(group, text),
86                    }
87                }
88            }
89            Self::Cached(cache) => cache,
90        }
91    }
92}
93
94/// A frame for drawing some geometry.
95#[allow(missing_debug_implementations)]
96pub struct Frame {
97    clip_bounds: Rectangle,
98    buffers: BufferStack,
99    meshes: Vec<Mesh>,
100    images: Vec<Image>,
101    text: Vec<Text>,
102    transforms: Transforms,
103    fill_tessellator: tessellation::FillTessellator,
104    stroke_tessellator: tessellation::StrokeTessellator,
105}
106
107impl Frame {
108    /// Creates a new [`Frame`] with the given [`Size`].
109    pub fn new(size: Size) -> Frame {
110        Self::with_clip(Rectangle::with_size(size))
111    }
112
113    /// Creates a new [`Frame`] with the given clip bounds.
114    pub fn with_clip(bounds: Rectangle) -> Frame {
115        Frame {
116            clip_bounds: bounds,
117            buffers: BufferStack::new(),
118            meshes: Vec::new(),
119            images: Vec::new(),
120            text: Vec::new(),
121            transforms: Transforms {
122                previous: Vec::new(),
123                current: Transform(lyon::math::Transform::translation(
124                    bounds.x, bounds.y,
125                )),
126            },
127            fill_tessellator: tessellation::FillTessellator::new(),
128            stroke_tessellator: tessellation::StrokeTessellator::new(),
129        }
130    }
131}
132
133impl geometry::frame::Backend for Frame {
134    type Geometry = Geometry;
135
136    #[inline]
137    fn width(&self) -> f32 {
138        self.clip_bounds.width
139    }
140
141    #[inline]
142    fn height(&self) -> f32 {
143        self.clip_bounds.height
144    }
145
146    #[inline]
147    fn size(&self) -> Size {
148        self.clip_bounds.size()
149    }
150
151    #[inline]
152    fn center(&self) -> Point {
153        Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
154    }
155
156    fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
157        let Fill { style, rule } = fill.into();
158
159        let mut buffer = self
160            .buffers
161            .get_fill(&self.transforms.current.transform_style(style));
162
163        let options = tessellation::FillOptions::default()
164            .with_fill_rule(into_fill_rule(rule));
165
166        if self.transforms.current.is_identity() {
167            self.fill_tessellator.tessellate_path(
168                path.raw(),
169                &options,
170                buffer.as_mut(),
171            )
172        } else {
173            let path = path.transform(&self.transforms.current.0);
174
175            self.fill_tessellator.tessellate_path(
176                path.raw(),
177                &options,
178                buffer.as_mut(),
179            )
180        }
181        .expect("Tessellate path.");
182    }
183
184    fn fill_rectangle(
185        &mut self,
186        top_left: Point,
187        size: Size,
188        fill: impl Into<Fill>,
189    ) {
190        let Fill { style, rule } = fill.into();
191
192        let mut buffer = self
193            .buffers
194            .get_fill(&self.transforms.current.transform_style(style));
195
196        let top_left = self
197            .transforms
198            .current
199            .0
200            .transform_point(lyon::math::Point::new(top_left.x, top_left.y));
201
202        let size =
203            self.transforms.current.0.transform_vector(
204                lyon::math::Vector::new(size.width, size.height),
205            );
206
207        let options = tessellation::FillOptions::default()
208            .with_fill_rule(into_fill_rule(rule));
209
210        self.fill_tessellator
211            .tessellate_rectangle(
212                &lyon::math::Box2D::new(top_left, top_left + size),
213                &options,
214                buffer.as_mut(),
215            )
216            .expect("Fill rectangle");
217    }
218
219    fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
220        let stroke = stroke.into();
221
222        let mut buffer = self
223            .buffers
224            .get_stroke(&self.transforms.current.transform_style(stroke.style));
225
226        let mut options = tessellation::StrokeOptions::default();
227        options.line_width = stroke.width;
228        options.start_cap = into_line_cap(stroke.line_cap);
229        options.end_cap = into_line_cap(stroke.line_cap);
230        options.line_join = into_line_join(stroke.line_join);
231
232        let path = if stroke.line_dash.segments.is_empty() {
233            Cow::Borrowed(path)
234        } else {
235            Cow::Owned(dashed(path, stroke.line_dash))
236        };
237
238        if self.transforms.current.is_identity() {
239            self.stroke_tessellator.tessellate_path(
240                path.raw(),
241                &options,
242                buffer.as_mut(),
243            )
244        } else {
245            let path = path.transform(&self.transforms.current.0);
246
247            self.stroke_tessellator.tessellate_path(
248                path.raw(),
249                &options,
250                buffer.as_mut(),
251            )
252        }
253        .expect("Stroke path");
254    }
255
256    fn stroke_rectangle<'a>(
257        &mut self,
258        top_left: Point,
259        size: Size,
260        stroke: impl Into<Stroke<'a>>,
261    ) {
262        let stroke = stroke.into();
263
264        let mut buffer = self
265            .buffers
266            .get_stroke(&self.transforms.current.transform_style(stroke.style));
267
268        let top_left = self
269            .transforms
270            .current
271            .0
272            .transform_point(lyon::math::Point::new(top_left.x, top_left.y));
273
274        let size =
275            self.transforms.current.0.transform_vector(
276                lyon::math::Vector::new(size.width, size.height),
277            );
278
279        let mut options = tessellation::StrokeOptions::default();
280        options.line_width = stroke.width;
281        options.start_cap = into_line_cap(stroke.line_cap);
282        options.end_cap = into_line_cap(stroke.line_cap);
283        options.line_join = into_line_join(stroke.line_join);
284
285        self.stroke_tessellator
286            .tessellate_rectangle(
287                &lyon::math::Box2D::new(top_left, top_left + size),
288                &options,
289                buffer.as_mut(),
290            )
291            .expect("Stroke rectangle");
292    }
293
294    fn fill_text(&mut self, text: impl Into<geometry::Text>) {
295        let text = text.into();
296
297        let (scale_x, scale_y) = self.transforms.current.scale();
298
299        if self.transforms.current.is_scale_translation()
300            && scale_x == scale_y
301            && scale_x > 0.0
302            && scale_y > 0.0
303        {
304            let (position, size, line_height) =
305                if self.transforms.current.is_identity() {
306                    (text.position, text.size, text.line_height)
307                } else {
308                    let position =
309                        self.transforms.current.transform_point(text.position);
310
311                    let size = Pixels(text.size.0 * scale_y);
312
313                    let line_height = match text.line_height {
314                        LineHeight::Absolute(size) => {
315                            LineHeight::Absolute(Pixels(size.0 * scale_y))
316                        }
317                        LineHeight::Relative(factor) => {
318                            LineHeight::Relative(factor)
319                        }
320                    };
321
322                    (position, size, line_height)
323                };
324
325            let bounds = Rectangle {
326                x: position.x,
327                y: position.y,
328                width: f32::INFINITY,
329                height: f32::INFINITY,
330            };
331
332            self.text.push(Text::Cached {
333                content: text.content,
334                bounds,
335                color: text.color,
336                size,
337                line_height: line_height.to_absolute(size),
338                font: text.font,
339                horizontal_alignment: text.horizontal_alignment,
340                vertical_alignment: text.vertical_alignment,
341                shaping: text.shaping,
342                clip_bounds: self.clip_bounds,
343            });
344        } else {
345            text.draw_with(|path, color| self.fill(&path, color));
346        }
347    }
348
349    #[inline]
350    fn translate(&mut self, translation: Vector) {
351        self.transforms.current.0 =
352            self.transforms
353                .current
354                .0
355                .pre_translate(lyon::math::Vector::new(
356                    translation.x,
357                    translation.y,
358                ));
359    }
360
361    #[inline]
362    fn rotate(&mut self, angle: impl Into<Radians>) {
363        self.transforms.current.0 = self
364            .transforms
365            .current
366            .0
367            .pre_rotate(lyon::math::Angle::radians(angle.into().0));
368    }
369
370    #[inline]
371    fn scale(&mut self, scale: impl Into<f32>) {
372        let scale = scale.into();
373
374        self.scale_nonuniform(Vector { x: scale, y: scale });
375    }
376
377    #[inline]
378    fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
379        let scale = scale.into();
380
381        self.transforms.current.0 =
382            self.transforms.current.0.pre_scale(scale.x, scale.y);
383    }
384
385    fn push_transform(&mut self) {
386        self.transforms.previous.push(self.transforms.current);
387    }
388
389    fn pop_transform(&mut self) {
390        self.transforms.current = self.transforms.previous.pop().unwrap();
391    }
392
393    fn draft(&mut self, clip_bounds: Rectangle) -> Frame {
394        Frame::with_clip(clip_bounds)
395    }
396
397    fn paste(&mut self, frame: Frame) {
398        self.meshes.extend(frame.meshes);
399        self.meshes
400            .extend(frame.buffers.into_meshes(frame.clip_bounds));
401
402        self.images.extend(frame.images);
403        self.text.extend(frame.text);
404    }
405
406    fn into_geometry(mut self) -> Self::Geometry {
407        self.meshes
408            .extend(self.buffers.into_meshes(self.clip_bounds));
409
410        Geometry::Live {
411            meshes: self.meshes,
412            images: self.images,
413            text: self.text,
414        }
415    }
416
417    fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
418        let mut image = image.into();
419
420        let (bounds, external_rotation) =
421            self.transforms.current.transform_rectangle(bounds);
422
423        image.rotation += external_rotation;
424
425        self.images.push(Image::Raster(image, bounds));
426    }
427
428    fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
429        let mut svg = svg.into();
430
431        let (bounds, external_rotation) =
432            self.transforms.current.transform_rectangle(bounds);
433
434        svg.rotation += external_rotation;
435
436        self.images.push(Image::Vector(svg, bounds));
437    }
438}
439
440enum Buffer {
441    Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
442    Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
443}
444
445struct BufferStack {
446    stack: Vec<Buffer>,
447}
448
449impl BufferStack {
450    fn new() -> Self {
451        Self { stack: Vec::new() }
452    }
453
454    fn get_mut(&mut self, style: &Style) -> &mut Buffer {
455        match style {
456            Style::Solid(_) => match self.stack.last() {
457                Some(Buffer::Solid(_)) => {}
458                _ => {
459                    self.stack.push(Buffer::Solid(
460                        tessellation::VertexBuffers::new(),
461                    ));
462                }
463            },
464            Style::Gradient(_) => match self.stack.last() {
465                Some(Buffer::Gradient(_)) => {}
466                _ => {
467                    self.stack.push(Buffer::Gradient(
468                        tessellation::VertexBuffers::new(),
469                    ));
470                }
471            },
472        }
473
474        self.stack.last_mut().unwrap()
475    }
476
477    fn get_fill<'a>(
478        &'a mut self,
479        style: &Style,
480    ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
481        match (style, self.get_mut(style)) {
482            (Style::Solid(color), Buffer::Solid(buffer)) => {
483                Box::new(tessellation::BuffersBuilder::new(
484                    buffer,
485                    TriangleVertex2DBuilder(color::pack(*color)),
486                ))
487            }
488            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
489                Box::new(tessellation::BuffersBuilder::new(
490                    buffer,
491                    GradientVertex2DBuilder {
492                        gradient: gradient.pack(),
493                    },
494                ))
495            }
496            _ => unreachable!(),
497        }
498    }
499
500    fn get_stroke<'a>(
501        &'a mut self,
502        style: &Style,
503    ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
504        match (style, self.get_mut(style)) {
505            (Style::Solid(color), Buffer::Solid(buffer)) => {
506                Box::new(tessellation::BuffersBuilder::new(
507                    buffer,
508                    TriangleVertex2DBuilder(color::pack(*color)),
509                ))
510            }
511            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
512                Box::new(tessellation::BuffersBuilder::new(
513                    buffer,
514                    GradientVertex2DBuilder {
515                        gradient: gradient.pack(),
516                    },
517                ))
518            }
519            _ => unreachable!(),
520        }
521    }
522
523    fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator<Item = Mesh> {
524        self.stack
525            .into_iter()
526            .filter_map(move |buffer| match buffer {
527                Buffer::Solid(buffer) if !buffer.indices.is_empty() => {
528                    Some(Mesh::Solid {
529                        buffers: mesh::Indexed {
530                            vertices: buffer.vertices,
531                            indices: buffer.indices,
532                        },
533                        clip_bounds,
534                        transformation: Transformation::IDENTITY,
535                    })
536                }
537                Buffer::Gradient(buffer) if !buffer.indices.is_empty() => {
538                    Some(Mesh::Gradient {
539                        buffers: mesh::Indexed {
540                            vertices: buffer.vertices,
541                            indices: buffer.indices,
542                        },
543                        clip_bounds,
544                        transformation: Transformation::IDENTITY,
545                    })
546                }
547                _ => None,
548            })
549    }
550}
551
552#[derive(Debug)]
553struct Transforms {
554    previous: Vec<Transform>,
555    current: Transform,
556}
557
558#[derive(Debug, Clone, Copy)]
559struct Transform(lyon::math::Transform);
560
561impl Transform {
562    fn is_identity(&self) -> bool {
563        self.0 == lyon::math::Transform::identity()
564    }
565
566    fn is_scale_translation(&self) -> bool {
567        self.0.m12.abs() < 2.0 * f32::EPSILON
568            && self.0.m21.abs() < 2.0 * f32::EPSILON
569    }
570
571    fn scale(&self) -> (f32, f32) {
572        (self.0.m11, self.0.m22)
573    }
574
575    fn transform_point(&self, point: Point) -> Point {
576        let transformed = self
577            .0
578            .transform_point(euclid::Point2D::new(point.x, point.y));
579
580        Point {
581            x: transformed.x,
582            y: transformed.y,
583        }
584    }
585
586    fn transform_style(&self, style: Style) -> Style {
587        match style {
588            Style::Solid(color) => Style::Solid(color),
589            Style::Gradient(gradient) => {
590                Style::Gradient(self.transform_gradient(gradient))
591            }
592        }
593    }
594
595    fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
596        match &mut gradient {
597            Gradient::Linear(linear) => {
598                linear.start = self.transform_point(linear.start);
599                linear.end = self.transform_point(linear.end);
600            }
601        }
602
603        gradient
604    }
605
606    fn transform_rectangle(
607        &self,
608        rectangle: Rectangle,
609    ) -> (Rectangle, Radians) {
610        let top_left = self.transform_point(rectangle.position());
611        let top_right = self.transform_point(
612            rectangle.position() + Vector::new(rectangle.width, 0.0),
613        );
614        let bottom_left = self.transform_point(
615            rectangle.position() + Vector::new(0.0, rectangle.height),
616        );
617
618        Rectangle::with_vertices(top_left, top_right, bottom_left)
619    }
620}
621struct GradientVertex2DBuilder {
622    gradient: gradient::Packed,
623}
624
625impl tessellation::FillVertexConstructor<mesh::GradientVertex2D>
626    for GradientVertex2DBuilder
627{
628    fn new_vertex(
629        &mut self,
630        vertex: tessellation::FillVertex<'_>,
631    ) -> mesh::GradientVertex2D {
632        let position = vertex.position();
633
634        mesh::GradientVertex2D {
635            position: [position.x, position.y],
636            gradient: self.gradient,
637        }
638    }
639}
640
641impl tessellation::StrokeVertexConstructor<mesh::GradientVertex2D>
642    for GradientVertex2DBuilder
643{
644    fn new_vertex(
645        &mut self,
646        vertex: tessellation::StrokeVertex<'_, '_>,
647    ) -> mesh::GradientVertex2D {
648        let position = vertex.position();
649
650        mesh::GradientVertex2D {
651            position: [position.x, position.y],
652            gradient: self.gradient,
653        }
654    }
655}
656
657struct TriangleVertex2DBuilder(color::Packed);
658
659impl tessellation::FillVertexConstructor<mesh::SolidVertex2D>
660    for TriangleVertex2DBuilder
661{
662    fn new_vertex(
663        &mut self,
664        vertex: tessellation::FillVertex<'_>,
665    ) -> mesh::SolidVertex2D {
666        let position = vertex.position();
667
668        mesh::SolidVertex2D {
669            position: [position.x, position.y],
670            color: self.0,
671        }
672    }
673}
674
675impl tessellation::StrokeVertexConstructor<mesh::SolidVertex2D>
676    for TriangleVertex2DBuilder
677{
678    fn new_vertex(
679        &mut self,
680        vertex: tessellation::StrokeVertex<'_, '_>,
681    ) -> mesh::SolidVertex2D {
682        let position = vertex.position();
683
684        mesh::SolidVertex2D {
685            position: [position.x, position.y],
686            color: self.0,
687        }
688    }
689}
690
691fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin {
692    match line_join {
693        LineJoin::Miter => lyon::tessellation::LineJoin::Miter,
694        LineJoin::Round => lyon::tessellation::LineJoin::Round,
695        LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel,
696    }
697}
698
699fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap {
700    match line_cap {
701        LineCap::Butt => lyon::tessellation::LineCap::Butt,
702        LineCap::Square => lyon::tessellation::LineCap::Square,
703        LineCap::Round => lyon::tessellation::LineCap::Round,
704    }
705}
706
707fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule {
708    match rule {
709        fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero,
710        fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd,
711    }
712}
713
714pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
715    use lyon::algorithms::walk::{
716        walk_along_path, RepeatedPattern, WalkerEvent,
717    };
718    use lyon::path::iterator::PathIterator;
719
720    Path::new(|builder| {
721        let segments_odd = (line_dash.segments.len() % 2 == 1)
722            .then(|| [line_dash.segments, line_dash.segments].concat());
723
724        let mut draw_line = false;
725
726        walk_along_path(
727            path.raw().iter().flattened(
728                lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
729            ),
730            0.0,
731            lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
732            &mut RepeatedPattern {
733                callback: |event: WalkerEvent<'_>| {
734                    let point = Point {
735                        x: event.position.x,
736                        y: event.position.y,
737                    };
738
739                    if draw_line {
740                        builder.line_to(point);
741                    } else {
742                        builder.move_to(point);
743                    }
744
745                    draw_line = !draw_line;
746
747                    true
748                },
749                index: line_dash.offset,
750                intervals: segments_odd
751                    .as_deref()
752                    .unwrap_or(line_dash.segments),
753            },
754        );
755    })
756}