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 stroke_text<'a>(
295        &mut self,
296        text: impl Into<geometry::Text>,
297        stroke: impl Into<Stroke<'a>>,
298    ) {
299        let text = text.into();
300        let stroke = stroke.into();
301
302        text.draw_with(|glyph, _color| self.stroke(&glyph, stroke));
303    }
304
305    fn fill_text(&mut self, text: impl Into<geometry::Text>) {
306        let text = text.into();
307
308        let (scale_x, scale_y) = self.transforms.current.scale();
309
310        if self.transforms.current.is_scale_translation()
311            && scale_x == scale_y
312            && scale_x > 0.0
313            && scale_y > 0.0
314        {
315            let (bounds, size, line_height) =
316                if self.transforms.current.is_identity() {
317                    (
318                        Rectangle::new(
319                            text.position,
320                            Size::new(text.max_width, f32::INFINITY),
321                        ),
322                        text.size,
323                        text.line_height,
324                    )
325                } else {
326                    let position =
327                        self.transforms.current.transform_point(text.position);
328
329                    let size = Pixels(text.size.0 * scale_y);
330
331                    let line_height = match text.line_height {
332                        LineHeight::Absolute(size) => {
333                            LineHeight::Absolute(Pixels(size.0 * scale_y))
334                        }
335                        LineHeight::Relative(factor) => {
336                            LineHeight::Relative(factor)
337                        }
338                    };
339
340                    (
341                        Rectangle::new(
342                            position,
343                            Size::new(text.max_width, f32::INFINITY),
344                        ),
345                        size,
346                        line_height,
347                    )
348                };
349
350            self.text.push(Text::Cached {
351                content: text.content,
352                bounds,
353                color: text.color,
354                size,
355                line_height: line_height.to_absolute(size),
356                font: text.font,
357                align_x: text.align_x,
358                align_y: text.align_y,
359                shaping: text.shaping,
360                clip_bounds: self.clip_bounds,
361            });
362        } else {
363            text.draw_with(|path, color| self.fill(&path, color));
364        }
365    }
366
367    #[inline]
368    fn translate(&mut self, translation: Vector) {
369        self.transforms.current.0 =
370            self.transforms
371                .current
372                .0
373                .pre_translate(lyon::math::Vector::new(
374                    translation.x,
375                    translation.y,
376                ));
377    }
378
379    #[inline]
380    fn rotate(&mut self, angle: impl Into<Radians>) {
381        self.transforms.current.0 = self
382            .transforms
383            .current
384            .0
385            .pre_rotate(lyon::math::Angle::radians(angle.into().0));
386    }
387
388    #[inline]
389    fn scale(&mut self, scale: impl Into<f32>) {
390        let scale = scale.into();
391
392        self.scale_nonuniform(Vector { x: scale, y: scale });
393    }
394
395    #[inline]
396    fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
397        let scale = scale.into();
398
399        self.transforms.current.0 =
400            self.transforms.current.0.pre_scale(scale.x, scale.y);
401    }
402
403    fn push_transform(&mut self) {
404        self.transforms.previous.push(self.transforms.current);
405    }
406
407    fn pop_transform(&mut self) {
408        self.transforms.current = self.transforms.previous.pop().unwrap();
409    }
410
411    fn draft(&mut self, clip_bounds: Rectangle) -> Frame {
412        Frame::with_clip(clip_bounds)
413    }
414
415    fn paste(&mut self, frame: Frame) {
416        self.meshes.extend(frame.meshes);
417        self.meshes
418            .extend(frame.buffers.into_meshes(frame.clip_bounds));
419
420        self.images.extend(frame.images);
421        self.text.extend(frame.text);
422    }
423
424    fn into_geometry(mut self) -> Self::Geometry {
425        self.meshes
426            .extend(self.buffers.into_meshes(self.clip_bounds));
427
428        Geometry::Live {
429            meshes: self.meshes,
430            images: self.images,
431            text: self.text,
432        }
433    }
434
435    fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
436        let mut image = image.into();
437
438        let (bounds, external_rotation) =
439            self.transforms.current.transform_rectangle(bounds);
440
441        image.rotation += external_rotation;
442
443        self.images.push(Image::Raster(image, bounds));
444    }
445
446    fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
447        let mut svg = svg.into();
448
449        let (bounds, external_rotation) =
450            self.transforms.current.transform_rectangle(bounds);
451
452        svg.rotation += external_rotation;
453
454        self.images.push(Image::Vector(svg, bounds));
455    }
456}
457
458enum Buffer {
459    Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
460    Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
461}
462
463struct BufferStack {
464    stack: Vec<Buffer>,
465}
466
467impl BufferStack {
468    fn new() -> Self {
469        Self { stack: Vec::new() }
470    }
471
472    fn get_mut(&mut self, style: &Style) -> &mut Buffer {
473        match style {
474            Style::Solid(_) => match self.stack.last() {
475                Some(Buffer::Solid(_)) => {}
476                _ => {
477                    self.stack.push(Buffer::Solid(
478                        tessellation::VertexBuffers::new(),
479                    ));
480                }
481            },
482            Style::Gradient(_) => match self.stack.last() {
483                Some(Buffer::Gradient(_)) => {}
484                _ => {
485                    self.stack.push(Buffer::Gradient(
486                        tessellation::VertexBuffers::new(),
487                    ));
488                }
489            },
490        }
491
492        self.stack.last_mut().unwrap()
493    }
494
495    fn get_fill<'a>(
496        &'a mut self,
497        style: &Style,
498    ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
499        match (style, self.get_mut(style)) {
500            (Style::Solid(color), Buffer::Solid(buffer)) => {
501                Box::new(tessellation::BuffersBuilder::new(
502                    buffer,
503                    TriangleVertex2DBuilder(color::pack(*color)),
504                ))
505            }
506            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
507                Box::new(tessellation::BuffersBuilder::new(
508                    buffer,
509                    GradientVertex2DBuilder {
510                        gradient: gradient.pack(),
511                    },
512                ))
513            }
514            _ => unreachable!(),
515        }
516    }
517
518    fn get_stroke<'a>(
519        &'a mut self,
520        style: &Style,
521    ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
522        match (style, self.get_mut(style)) {
523            (Style::Solid(color), Buffer::Solid(buffer)) => {
524                Box::new(tessellation::BuffersBuilder::new(
525                    buffer,
526                    TriangleVertex2DBuilder(color::pack(*color)),
527                ))
528            }
529            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
530                Box::new(tessellation::BuffersBuilder::new(
531                    buffer,
532                    GradientVertex2DBuilder {
533                        gradient: gradient.pack(),
534                    },
535                ))
536            }
537            _ => unreachable!(),
538        }
539    }
540
541    fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator<Item = Mesh> {
542        self.stack
543            .into_iter()
544            .filter_map(move |buffer| match buffer {
545                Buffer::Solid(buffer) if !buffer.indices.is_empty() => {
546                    Some(Mesh::Solid {
547                        buffers: mesh::Indexed {
548                            vertices: buffer.vertices,
549                            indices: buffer.indices,
550                        },
551                        clip_bounds,
552                        transformation: Transformation::IDENTITY,
553                    })
554                }
555                Buffer::Gradient(buffer) if !buffer.indices.is_empty() => {
556                    Some(Mesh::Gradient {
557                        buffers: mesh::Indexed {
558                            vertices: buffer.vertices,
559                            indices: buffer.indices,
560                        },
561                        clip_bounds,
562                        transformation: Transformation::IDENTITY,
563                    })
564                }
565                _ => None,
566            })
567    }
568}
569
570#[derive(Debug)]
571struct Transforms {
572    previous: Vec<Transform>,
573    current: Transform,
574}
575
576#[derive(Debug, Clone, Copy)]
577struct Transform(lyon::math::Transform);
578
579impl Transform {
580    fn is_identity(&self) -> bool {
581        self.0 == lyon::math::Transform::identity()
582    }
583
584    fn is_scale_translation(&self) -> bool {
585        self.0.m12.abs() < 2.0 * f32::EPSILON
586            && self.0.m21.abs() < 2.0 * f32::EPSILON
587    }
588
589    fn scale(&self) -> (f32, f32) {
590        (self.0.m11, self.0.m22)
591    }
592
593    fn transform_point(&self, point: Point) -> Point {
594        let transformed = self
595            .0
596            .transform_point(euclid::Point2D::new(point.x, point.y));
597
598        Point {
599            x: transformed.x,
600            y: transformed.y,
601        }
602    }
603
604    fn transform_style(&self, style: Style) -> Style {
605        match style {
606            Style::Solid(color) => Style::Solid(color),
607            Style::Gradient(gradient) => {
608                Style::Gradient(self.transform_gradient(gradient))
609            }
610        }
611    }
612
613    fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
614        match &mut gradient {
615            Gradient::Linear(linear) => {
616                linear.start = self.transform_point(linear.start);
617                linear.end = self.transform_point(linear.end);
618            }
619        }
620
621        gradient
622    }
623
624    fn transform_rectangle(
625        &self,
626        rectangle: Rectangle,
627    ) -> (Rectangle, Radians) {
628        let top_left = self.transform_point(rectangle.position());
629        let top_right = self.transform_point(
630            rectangle.position() + Vector::new(rectangle.width, 0.0),
631        );
632        let bottom_left = self.transform_point(
633            rectangle.position() + Vector::new(0.0, rectangle.height),
634        );
635
636        Rectangle::with_vertices(top_left, top_right, bottom_left)
637    }
638}
639struct GradientVertex2DBuilder {
640    gradient: gradient::Packed,
641}
642
643impl tessellation::FillVertexConstructor<mesh::GradientVertex2D>
644    for GradientVertex2DBuilder
645{
646    fn new_vertex(
647        &mut self,
648        vertex: tessellation::FillVertex<'_>,
649    ) -> mesh::GradientVertex2D {
650        let position = vertex.position();
651
652        mesh::GradientVertex2D {
653            position: [position.x, position.y],
654            gradient: self.gradient,
655        }
656    }
657}
658
659impl tessellation::StrokeVertexConstructor<mesh::GradientVertex2D>
660    for GradientVertex2DBuilder
661{
662    fn new_vertex(
663        &mut self,
664        vertex: tessellation::StrokeVertex<'_, '_>,
665    ) -> mesh::GradientVertex2D {
666        let position = vertex.position();
667
668        mesh::GradientVertex2D {
669            position: [position.x, position.y],
670            gradient: self.gradient,
671        }
672    }
673}
674
675struct TriangleVertex2DBuilder(color::Packed);
676
677impl tessellation::FillVertexConstructor<mesh::SolidVertex2D>
678    for TriangleVertex2DBuilder
679{
680    fn new_vertex(
681        &mut self,
682        vertex: tessellation::FillVertex<'_>,
683    ) -> mesh::SolidVertex2D {
684        let position = vertex.position();
685
686        mesh::SolidVertex2D {
687            position: [position.x, position.y],
688            color: self.0,
689        }
690    }
691}
692
693impl tessellation::StrokeVertexConstructor<mesh::SolidVertex2D>
694    for TriangleVertex2DBuilder
695{
696    fn new_vertex(
697        &mut self,
698        vertex: tessellation::StrokeVertex<'_, '_>,
699    ) -> mesh::SolidVertex2D {
700        let position = vertex.position();
701
702        mesh::SolidVertex2D {
703            position: [position.x, position.y],
704            color: self.0,
705        }
706    }
707}
708
709fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin {
710    match line_join {
711        LineJoin::Miter => lyon::tessellation::LineJoin::Miter,
712        LineJoin::Round => lyon::tessellation::LineJoin::Round,
713        LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel,
714    }
715}
716
717fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap {
718    match line_cap {
719        LineCap::Butt => lyon::tessellation::LineCap::Butt,
720        LineCap::Square => lyon::tessellation::LineCap::Square,
721        LineCap::Round => lyon::tessellation::LineCap::Round,
722    }
723}
724
725fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule {
726    match rule {
727        fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero,
728        fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd,
729    }
730}
731
732pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
733    use lyon::algorithms::walk::{
734        RepeatedPattern, WalkerEvent, walk_along_path,
735    };
736    use lyon::path::iterator::PathIterator;
737
738    Path::new(|builder| {
739        let segments_odd = (line_dash.segments.len() % 2 == 1)
740            .then(|| [line_dash.segments, line_dash.segments].concat());
741
742        let mut draw_line = false;
743
744        walk_along_path(
745            path.raw().iter().flattened(
746                lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
747            ),
748            0.0,
749            lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
750            &mut RepeatedPattern {
751                callback: |event: WalkerEvent<'_>| {
752                    let point = Point {
753                        x: event.position.x,
754                        y: event.position.y,
755                    };
756
757                    if draw_line {
758                        builder.line_to(point);
759                    } else {
760                        builder.move_to(point);
761                    }
762
763                    draw_line = !draw_line;
764
765                    true
766                },
767                index: line_dash.offset,
768                intervals: segments_odd
769                    .as_deref()
770                    .unwrap_or(line_dash.segments),
771            },
772        );
773    })
774}