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