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