grafo/
shape.rs

1//! The `shape` module provides structures and methods for creating and managing graphical shapes
2//! within the Grafo library. It supports both simple and complex shapes, including rectangles and
3//! custom paths with stroke properties. Fill color is per-instance and set via the renderer.
4//!
5//! # Examples
6//!
7//! Creating and using different shapes:
8//!
9//! ```rust
10//! use grafo::Stroke;
11//! use grafo::{Shape, ShapeBuilder, BorderRadii};
12//! use grafo::Color;
13//!
14//! // Create a simple rectangle
15//! let rect = Shape::rect(
16//!     [(0.0, 0.0), (100.0, 50.0)],
17//!     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
18//! );
19//!
20//! // Create a rounded rectangle
21//! let rounded_rect = Shape::rounded_rect(
22//!     [(0.0, 0.0), (100.0, 50.0)],
23//!     BorderRadii::new(10.0),
24//!     Stroke::new(1.5, Color::BLACK), // Black stroke with width 1.5
25//! );
26//!
27//! // Build a custom shape using ShapeBuilder
28//! let custom_shape = Shape::builder()
29//!     .stroke(Stroke::new(3.0, Color::BLACK)) // Black stroke with width 3.0
30//!     .begin((0.0, 0.0))
31//!     .line_to((50.0, 10.0))
32//!     .line_to((50.0, 50.0))
33//!     .close()
34//!     .build();
35//! ```
36
37use crate::util::PoolManager;
38use crate::vertex::{CustomVertex, InstanceTransform};
39use crate::{Color, Stroke};
40use lyon::lyon_tessellation::{
41    BuffersBuilder, FillOptions, FillTessellator, FillVertex, VertexBuffers,
42};
43use lyon::path::Winding;
44use lyon::tessellation::FillVertexConstructor;
45
46pub(crate) struct CachedShape {
47    pub depth: f32,
48    pub vertex_buffers: VertexBuffers<CustomVertex, u16>,
49}
50
51impl CachedShape {
52    /// Creates a new `CachedShape` with the specified shape and depth.
53    /// Note that tessellator_cache_key is different from the shape cache key; a Shape cache key is
54    /// the shape identifier, while tesselator_cache_key is used to cache the tessellation of the
55    /// shape and should be based on the shape properties, and not the shape identifier
56    pub fn new(
57        shape: &Shape,
58        depth: f32,
59        tessellator: &mut FillTessellator,
60        pool: &mut PoolManager,
61        tessellator_cache_key: Option<u64>,
62    ) -> Self {
63        let vertices = shape.tessellate(depth, tessellator, pool, tessellator_cache_key);
64        Self {
65            depth,
66            vertex_buffers: vertices,
67        }
68    }
69
70    pub fn set_depth(&mut self, depth: f32) {
71        self.depth = depth;
72        for vertex in self.vertex_buffers.vertices.iter_mut() {
73            vertex.order = depth;
74        }
75    }
76}
77/// Represents a graphical shape, which can be either a custom path or a simple rectangle.
78///
79/// # Variants
80///
81/// - `Path(PathShape)`: A custom path shape defined using Bézier curves and lines.
82/// - `Rect(RectShape)`: A simple rectangular shape with optional rounded corners.
83///
84/// # Examples
85///
86/// ```rust
87/// use grafo::Stroke;
88/// use grafo::{Shape, BorderRadii};
89/// use grafo::Color;
90///
91/// // Create a simple rectangle
92/// let rect = Shape::rect(
93///     [(0.0, 0.0), (100.0, 50.0)],
94///     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
95/// );
96///
97/// // Create a custom path shape
98/// let custom_path = Shape::builder()
99///     .stroke(Stroke::new(1.0, Color::BLACK))
100///     .begin((0.0, 0.0))
101///     .line_to((50.0, 10.0))
102///     .line_to((50.0, 50.0))
103///     .close()
104///     .build();
105/// ```
106#[derive(Debug, Clone)]
107pub enum Shape {
108    /// A custom path shape defined using Bézier curves and lines.
109    Path(PathShape),
110    /// A simple rectangular shape.
111    Rect(RectShape),
112}
113
114impl Shape {
115    /// Creates a new [`ShapeBuilder`] for constructing complex shapes.
116    ///
117    /// # Examples
118    ///
119    /// ```rust
120    /// use grafo::Shape;
121    ///
122    /// let builder = Shape::builder();
123    /// ```
124    pub fn builder() -> ShapeBuilder {
125        ShapeBuilder::new()
126    }
127
128    /// Creates a simple rectangle shape with the specified coordinates and stroke.
129    ///
130    /// # Parameters
131    ///
132    /// - `rect`: An array containing two tuples representing the top-left and bottom-right
133    ///   coordinates of the rectangle.
134    /// - `stroke`: The stroke properties of the rectangle.
135    ///
136    /// # Examples
137    ///
138    /// ```rust
139    /// use grafo::Color;
140    /// use grafo::Stroke;
141    /// use grafo::Shape;
142    ///
143    /// let rect = Shape::rect(
144    ///     [(0.0, 0.0), (100.0, 50.0)],
145    ///     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
146    /// );
147    /// ```
148    pub fn rect(rect: [(f32, f32); 2], stroke: Stroke) -> Shape {
149        let rect_shape = RectShape::new(rect, stroke);
150        Shape::Rect(rect_shape)
151    }
152
153    /// Creates a rectangle shape with rounded corners.
154    ///
155    /// # Parameters
156    ///
157    /// - `rect`: An array containing two tuples representing the top-left and bottom-right
158    ///   coordinates of the rectangle.
159    /// - `border_radii`: The radii for each corner of the rectangle.
160    /// - `stroke`: The stroke properties of the rectangle.
161    ///
162    /// # Examples
163    ///
164    /// ```rust
165    /// use grafo::Color;
166    /// use grafo::Stroke;
167    /// use grafo::{Shape, BorderRadii};
168    ///
169    /// let rounded_rect = Shape::rounded_rect(
170    ///     [(0.0, 0.0), (100.0, 50.0)],
171    ///     BorderRadii::new(10.0),
172    ///     Stroke::new(1.5, Color::BLACK), // Black stroke with width 1.5
173    /// );
174    /// ```
175    pub fn rounded_rect(rect: [(f32, f32); 2], border_radii: BorderRadii, stroke: Stroke) -> Shape {
176        let mut path_builder = lyon::path::Path::builder();
177        let box2d = lyon::math::Box2D::new(rect[0].into(), rect[1].into());
178
179        path_builder.add_rounded_rectangle(&box2d, &border_radii.into(), Winding::Positive);
180        let path = path_builder.build();
181
182        let path_shape = PathShape { path, stroke };
183        Shape::Path(path_shape)
184    }
185
186    pub(crate) fn tessellate(
187        &self,
188        depth: f32,
189        tessellator: &mut FillTessellator,
190        buffers_pool: &mut PoolManager,
191        tesselation_cache_key: Option<u64>,
192    ) -> VertexBuffers<CustomVertex, u16> {
193        match &self {
194            Shape::Path(path_shape) => {
195                path_shape.tessellate(depth, tessellator, buffers_pool, tesselation_cache_key)
196            }
197            Shape::Rect(rect_shape) => {
198                let min_width = rect_shape.rect[0].0;
199                let min_height = rect_shape.rect[0].1;
200                let max_width = rect_shape.rect[1].0;
201                let max_height = rect_shape.rect[1].1;
202
203                // Compute UVs mapping the rectangle to [0,1] in local space
204                let w = (max_width - min_width).max(1e-6);
205                let h = (max_height - min_height).max(1e-6);
206                let uv =
207                    |x: f32, y: f32| -> [f32; 2] { [(x - min_width) / w, (y - min_height) / h] };
208
209                let quad = [
210                    CustomVertex {
211                        position: [min_width, min_height],
212                        tex_coords: uv(min_width, min_height),
213                        order: depth,
214                    },
215                    CustomVertex {
216                        position: [max_width, min_height],
217                        tex_coords: uv(max_width, min_height),
218                        order: depth,
219                    },
220                    CustomVertex {
221                        position: [min_width, max_height],
222                        tex_coords: uv(min_width, max_height),
223                        order: depth,
224                    },
225                    CustomVertex {
226                        position: [min_width, max_height],
227                        tex_coords: uv(min_width, max_height),
228                        order: depth,
229                    },
230                    CustomVertex {
231                        position: [max_width, min_height],
232                        tex_coords: uv(max_width, min_height),
233                        order: depth,
234                    },
235                    CustomVertex {
236                        position: [max_width, max_height],
237                        tex_coords: uv(max_width, max_height),
238                        order: depth,
239                    },
240                ];
241                let indices = [0u16, 1, 2, 3, 4, 5];
242
243                let mut vertex_buffers = buffers_pool.lyon_vertex_buffers_pool.get_vertex_buffers();
244
245                vertex_buffers.vertices.extend(quad);
246                vertex_buffers.indices.extend(indices);
247
248                vertex_buffers
249            }
250        }
251    }
252}
253
254impl From<PathShape> for Shape {
255    fn from(value: PathShape) -> Self {
256        Shape::Path(value)
257    }
258}
259
260impl From<RectShape> for Shape {
261    fn from(value: RectShape) -> Self {
262        Shape::Rect(value)
263    }
264}
265
266/// Represents a simple rectangular shape with a fill color and stroke.
267///
268/// You typically do not need to use `RectShape` directly; instead, use the [`Shape::rect`] method.
269///
270/// # Fields
271///
272/// - `rect`: An array containing two tuples representing the top-left and bottom-right
273///   coordinates of the rectangle.
274/// - `stroke`: The stroke properties of the rectangle.
275///
276/// # Examples
277///
278/// ```rust
279/// use grafo::RectShape;
280/// use grafo::Stroke;
281/// use grafo::Color;
282///
283/// let rect_shape = RectShape::new(
284///     [(0.0, 0.0), (100.0, 50.0)],
285///     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
286/// );
287/// ```
288#[derive(Debug, Clone)]
289pub struct RectShape {
290    /// An array containing two tuples representing the top-left and bottom-right coordinates
291    /// of the rectangle.
292    pub(crate) rect: [(f32, f32); 2],
293    /// The stroke properties of the rectangle.
294    #[allow(unused)]
295    pub(crate) stroke: Stroke,
296}
297
298impl RectShape {
299    /// Creates a new `RectShape` with the specified coordinates and stroke.
300    ///
301    /// # Parameters
302    ///
303    /// - `rect`: An array containing two tuples representing the top-left and bottom-right
304    ///   coordinates of the rectangle.
305    /// - `stroke`: The stroke properties of the rectangle.
306    ///
307    /// # Examples
308    ///
309    /// ```rust
310    /// use grafo::RectShape;
311    /// use grafo::Stroke;
312    /// use grafo::Color;
313    ///
314    /// let rect_shape = RectShape::new(
315    ///     [(0.0, 0.0), (100.0, 50.0)],
316    ///     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
317    /// );
318    /// ```
319    pub fn new(rect: [(f32, f32); 2], stroke: Stroke) -> Self {
320        Self { rect, stroke }
321    }
322}
323
324/// Represents a custom path shape with stroke.
325///
326/// You typically do not need to use `PathShape` directly; instead, use the [`Shape::builder`]
327/// method to construct complex shapes.
328///
329/// # Fields
330///
331/// - `path`: The geometric path defining the shape.
332/// - `stroke`: The stroke properties of the shape.
333///
334/// # Examples
335///
336/// ```rust
337/// use grafo::{Shape, PathShape};
338/// use grafo::Stroke;
339/// use grafo::Color;
340///
341/// // Replace this with your own path
342/// let path = lyon::path::Path::builder().build();
343///
344/// let path_shape = PathShape::new(
345///     path,
346///     Stroke::new(1.0, Color::BLACK), // Black stroke with width 1.0
347/// );
348///
349/// let shape = Shape::Path(path_shape);
350/// ```
351#[derive(Clone, Debug)]
352pub struct PathShape {
353    /// The geometric path defining the shape.
354    pub(crate) path: lyon::path::Path,
355    /// The stroke properties of the shape.
356    #[allow(unused)]
357    pub(crate) stroke: Stroke,
358}
359
360struct VertexConverter {
361    depth: f32,
362}
363
364impl VertexConverter {
365    fn new(depth: f32) -> Self {
366        Self { depth }
367    }
368}
369
370impl FillVertexConstructor<CustomVertex> for VertexConverter {
371    fn new_vertex(&mut self, vertex: FillVertex) -> CustomVertex {
372        CustomVertex {
373            position: vertex.position().to_array(),
374            order: self.depth,
375            tex_coords: [0.0, 0.0],
376        }
377    }
378}
379
380impl PathShape {
381    /// Creates a new `PathShape` with the specified path and stroke.
382    ///
383    /// # Parameters
384    ///
385    /// - `path`: The geometric path defining the shape.
386    /// - `stroke`: The stroke properties of the shape.
387    ///
388    /// # Examples
389    ///
390    /// ```rust
391    /// use grafo::PathShape;
392    /// use grafo::Stroke;
393    /// use lyon::path::Path;
394    ///
395    /// let path = Path::builder().build();
396    /// let path_shape = PathShape::new(path, Stroke::default());
397    /// ```
398    pub fn new(path: lyon::path::Path, stroke: Stroke) -> Self {
399        Self { path, stroke }
400    }
401
402    /// Tessellates the path shape into vertex and index buffers for rendering.
403    ///
404    /// # Parameters
405    ///
406    /// - `depth`: The depth value used for rendering order.
407    ///
408    /// # Returns
409    ///
410    /// A `VertexBuffers` structure containing the tessellated vertices and indices.
411    /// ```
412    pub(crate) fn tessellate(
413        &self,
414        depth: f32,
415        tessellator: &mut FillTessellator,
416        buffers_pool: &mut PoolManager,
417        tesselation_cache_key: Option<u64>,
418    ) -> VertexBuffers<CustomVertex, u16> {
419        let mut buffers = if let Some(cache_key) = tesselation_cache_key {
420            if let Some(buffers) = buffers_pool
421                .tessellation_cache
422                .get_vertex_buffers(&cache_key)
423            {
424                buffers
425            } else {
426                let mut buffers: VertexBuffers<CustomVertex, u16> =
427                    buffers_pool.lyon_vertex_buffers_pool.get_vertex_buffers();
428
429                self.tesselate_into_buffers(&mut buffers, depth, tessellator);
430                buffers_pool
431                    .tessellation_cache
432                    .insert_vertex_buffers(cache_key, buffers.clone());
433
434                buffers
435            }
436        } else {
437            let mut buffers: VertexBuffers<CustomVertex, u16> =
438                buffers_pool.lyon_vertex_buffers_pool.get_vertex_buffers();
439
440            self.tesselate_into_buffers(&mut buffers, depth, tessellator);
441
442            buffers
443        };
444
445        if buffers.indices.len() % 2 != 0 {
446            buffers.indices.push(0);
447        }
448
449        buffers
450    }
451
452    fn tesselate_into_buffers(
453        &self,
454        buffers: &mut VertexBuffers<CustomVertex, u16>,
455        depth: f32,
456        tessellator: &mut FillTessellator,
457    ) {
458        let options = FillOptions::default();
459
460        let vertex_converter = VertexConverter::new(depth);
461
462        tessellator
463            .tessellate_path(
464                &self.path,
465                &options,
466                &mut BuffersBuilder::new(buffers, vertex_converter),
467            )
468            .unwrap();
469
470        // Generate UVs for the tessellated path using its axis-aligned bounding box in local space
471        if !buffers.vertices.is_empty() {
472            // Compute AABB of positions before offset translation
473            let mut min_x = f32::INFINITY;
474            let mut min_y = f32::INFINITY;
475            let mut max_x = f32::NEG_INFINITY;
476            let mut max_y = f32::NEG_INFINITY;
477            for v in buffers.vertices.iter() {
478                let x = v.position[0];
479                let y = v.position[1];
480                if x < min_x {
481                    min_x = x;
482                }
483                if y < min_y {
484                    min_y = y;
485                }
486                if x > max_x {
487                    max_x = x;
488                }
489                if y > max_y {
490                    max_y = y;
491                }
492            }
493            let w = (max_x - min_x).max(1e-6);
494            let h = (max_y - min_y).max(1e-6);
495            for v in buffers.vertices.iter_mut() {
496                let u = (v.position[0] - min_x) / w;
497                let vcoord = (v.position[1] - min_y) / h;
498                v.tex_coords = [u, vcoord];
499            }
500        }
501    }
502}
503
504/// Contains the data required to draw a shape, including vertex and index buffers.
505///
506/// This struct is used internally by the renderer and typically does not need to be used
507/// directly by library users.
508#[derive(Debug)]
509pub(crate) struct ShapeDrawData {
510    /// The shape associated with this draw data.
511    pub(crate) shape: Shape,
512    /// Optional cache key for the shape, used for caching tessellated buffers.
513    pub(crate) cache_key: Option<u64>,
514    /// Range in the aggregated index buffer (start_index, count)  
515    pub(crate) index_buffer_range: Option<(usize, usize)>,
516    /// Indicates whether the shape is empty (no vertices or indices).
517    pub(crate) is_empty: bool,
518    /// Stencil reference assigned during render traversal (parent + 1). Cleared after frame.
519    pub(crate) stencil_ref: Option<u32>,
520    /// Index into the per-frame instance transform buffer
521    pub(crate) instance_index: Option<usize>,
522    /// Optional per-shape transform applied in clip-space (post-normalization)
523    pub(crate) transform: Option<InstanceTransform>,
524    /// Optional texture ids associated with this shape for multi-texturing layers.
525    /// Layer 0: background/base
526    /// Layer 1: foreground/overlay (e.g. text or decals) blended on top
527    pub(crate) texture_ids: [Option<u64>; 2],
528    /// Optional per-instance color override (normalized [0,1]). If None, use shape's fill.
529    pub(crate) color_override: Option<[f32; 4]>,
530}
531
532impl ShapeDrawData {
533    pub fn new(shape: impl Into<Shape>, cache_key: Option<u64>) -> Self {
534        let shape = shape.into();
535
536        ShapeDrawData {
537            shape,
538            cache_key,
539            index_buffer_range: None,
540            is_empty: false,
541            stencil_ref: None,
542            instance_index: None,
543            transform: None,
544            texture_ids: [None, None],
545            color_override: None,
546        }
547    }
548
549    /// Tessellates complex shapes and stores the resulting buffers.
550    #[inline(always)]
551    pub(crate) fn tessellate(
552        &mut self,
553        depth: f32,
554        tessellator: &mut FillTessellator,
555        buffers_pool: &mut PoolManager,
556    ) -> VertexBuffers<CustomVertex, u16> {
557        self.shape
558            .tessellate(depth, tessellator, buffers_pool, self.cache_key)
559    }
560}
561
562#[derive(Debug)]
563pub(crate) struct CachedShapeDrawData {
564    pub(crate) id: u64,
565    pub(crate) index_buffer_range: Option<(usize, usize)>,
566    pub(crate) is_empty: bool,
567    /// Stencil reference assigned during render traversal (parent + 1). Cleared after frame.
568    pub(crate) stencil_ref: Option<u32>,
569    /// Index into the per-frame instance transform buffer
570    pub(crate) instance_index: Option<usize>,
571    /// Optional per-shape transform applied in clip-space (post-normalization)
572    pub(crate) transform: Option<InstanceTransform>,
573    /// Optional texture ids associated with this cached shape
574    pub(crate) texture_ids: [Option<u64>; 2],
575    /// Optional per-instance color override (normalized [0,1]). If None, use cached shape default.
576    pub(crate) color_override: Option<[f32; 4]>,
577}
578
579impl CachedShapeDrawData {
580    pub fn new(id: u64) -> Self {
581        Self {
582            id,
583            index_buffer_range: None,
584            is_empty: false,
585            stencil_ref: None,
586            instance_index: None,
587            transform: None,
588            texture_ids: [None, None],
589            color_override: None,
590        }
591    }
592}
593
594/// A builder for creating complex shapes using a fluent interface.
595///
596/// The `ShapeBuilder` allows you to define the fill color, stroke, and path of a shape
597/// using method chaining. You also can get it from the [`Shape::builder`] method.
598///
599/// # Examples
600///
601/// ```rust
602/// use grafo::Color;
603/// use grafo::Stroke;
604/// use grafo::ShapeBuilder;
605///
606/// let custom_shape = ShapeBuilder::new()
607///     // Fill is set per-instance via the renderer (renderer.set_shape_color)
608///     .stroke(Stroke::new(3.0, Color::BLACK)) // Black stroke with width 3.0
609///     .begin((0.0, 0.0))
610///     .line_to((50.0, 10.0))
611///     .line_to((50.0, 50.0))
612///     .close()
613///     .build();
614/// ```
615#[derive(Clone)]
616pub struct ShapeBuilder {
617    /// The stroke properties of the shape.
618    stroke: Stroke,
619    /// The path builder used to construct the shape's geometric path.
620    path_builder: lyon::path::Builder,
621}
622
623impl Default for ShapeBuilder {
624    /// Creates a default `ShapeBuilder` with black fill and stroke.
625    ///
626    /// # Examples
627    ///
628    /// ```rust
629    /// use grafo::ShapeBuilder;    ///
630    /// let builder = ShapeBuilder::default();
631    /// ```
632    fn default() -> Self {
633        Self::new()
634    }
635}
636
637impl ShapeBuilder {
638    /// Creates a new `ShapeBuilder` with default fill color (black) and stroke.
639    ///
640    /// # Examples
641    ///
642    /// ```rust
643    /// use grafo::ShapeBuilder;
644    ///
645    /// let builder = ShapeBuilder::new();
646    /// ```
647    pub fn new() -> Self {
648        Self {
649            stroke: Stroke::new(1.0, Color::rgb(0, 0, 0)),
650            path_builder: lyon::path::Path::builder(),
651        }
652    }
653
654    /// Sets the stroke properties of the shape.
655    ///
656    /// # Parameters
657    ///
658    /// - `stroke`: The desired stroke properties.
659    ///
660    /// # Returns
661    ///
662    /// The updated `ShapeBuilder` instance.
663    ///
664    /// # Examples
665    ///
666    /// ```rust
667    /// use grafo::Stroke;
668    /// use grafo::Color;
669    /// use grafo::ShapeBuilder;
670    ///
671    /// let builder = ShapeBuilder::new().stroke(Stroke::new(2.0, Color::BLACK)); // Black stroke with width 2.0
672    /// ```
673    pub fn stroke(mut self, stroke: Stroke) -> Self {
674        self.stroke = stroke;
675        self
676    }
677
678    /// Begin path at point
679    ///
680    /// # Parameters
681    ///
682    /// - `point`: The start point of the shape.
683    ///
684    /// # Returns
685    ///
686    /// The updated `ShapeBuilder` instance.
687    ///
688    /// # Examples
689    ///
690    /// ```rust
691    /// use grafo::ShapeBuilder;
692    ///
693    /// let builder = ShapeBuilder::new().begin((0.0, 0.0));
694    /// ```
695    pub fn begin(mut self, point: (f32, f32)) -> Self {
696        self.path_builder.begin(point.into());
697        self
698    }
699
700    /// Draws a line from the current point to the specified point.
701    ///
702    /// # Parameters
703    ///
704    /// - `point`: The end point of the line.
705    ///
706    /// # Returns
707    ///
708    /// The updated `ShapeBuilder` instance.
709    ///
710    /// # Examples
711    ///
712    /// ```rust
713    /// use grafo::ShapeBuilder;
714    ///
715    /// let builder = ShapeBuilder::new().begin((0.0, 0.0)).line_to((50.0, 10.0));
716    /// ```
717    pub fn line_to(mut self, point: (f32, f32)) -> Self {
718        self.path_builder.line_to(point.into());
719        self
720    }
721
722    /// Draws a cubic Bézier curve from the current point to the specified end point.
723    ///
724    /// # Parameters
725    ///
726    /// - `ctrl`: The first control point.
727    /// - `ctrl2`: The second control point.
728    /// - `to`: The end point of the curve.
729    ///
730    /// # Returns
731    ///
732    /// The updated `ShapeBuilder` instance.
733    ///
734    /// # Examples
735    ///
736    /// ```rust
737    /// use grafo::ShapeBuilder;
738    ///
739    /// let builder = ShapeBuilder::new()
740    ///     .begin((0.0, 0.0))
741    ///     .cubic_bezier_to((20.0, 30.0), (40.0, 30.0), (50.0, 10.0));
742    /// ```
743    pub fn cubic_bezier_to(mut self, ctrl: (f32, f32), ctrl2: (f32, f32), to: (f32, f32)) -> Self {
744        self.path_builder
745            .cubic_bezier_to(ctrl.into(), ctrl2.into(), to.into());
746        self
747    }
748
749    /// Draws a quadratic Bézier curve from the current point to the specified end point.
750    ///
751    /// # Parameters
752    ///
753    /// - `ctrl`: The control point.
754    /// - `to`: The end point of the curve.
755    ///
756    /// # Returns
757    ///
758    /// The updated `ShapeBuilder` instance.
759    ///
760    /// # Examples
761    ///
762    /// ```rust
763    /// use grafo::ShapeBuilder;
764    ///
765    /// let builder = ShapeBuilder::new()
766    ///     .begin((0.0, 0.0))
767    ///     .quadratic_bezier_to((25.0, 40.0), (50.0, 10.0));
768    /// ```
769    pub fn quadratic_bezier_to(mut self, ctrl: (f32, f32), to: (f32, f32)) -> Self {
770        self.path_builder
771            .quadratic_bezier_to(ctrl.into(), to.into());
772        self
773    }
774
775    /// Closes the current sub-path by drawing a line back to the starting point.
776    ///
777    /// # Returns
778    ///
779    /// The updated `ShapeBuilder` instance.
780    ///
781    /// # Examples
782    ///
783    /// ```rust
784    /// use grafo::ShapeBuilder;
785    ///
786    /// let builder = ShapeBuilder::new().begin((0.0, 0.0)).close();
787    /// ```
788    pub fn close(mut self) -> Self {
789        self.path_builder.close();
790        self
791    }
792
793    /// Builds the [`Shape`] from the accumulated path, fill color, and stroke.
794    ///
795    /// # Returns
796    ///
797    /// A `Shape` instance representing the constructed shape.
798    ///
799    /// # Examples
800    ///
801    /// ```rust
802    /// use grafo::ShapeBuilder;
803    ///
804    /// let shape = ShapeBuilder::new()
805    ///     .begin((0.0, 0.0))
806    ///     .line_to((50.0, 10.0))
807    ///     .line_to((50.0, 50.0))
808    ///     .close()
809    ///     .build();
810    /// ```
811    pub fn build(self) -> Shape {
812        let path = self.path_builder.build();
813        Shape::Path(PathShape {
814            path,
815            stroke: self.stroke,
816        })
817    }
818}
819
820impl From<ShapeBuilder> for Shape {
821    fn from(value: ShapeBuilder) -> Self {
822        value.build()
823    }
824}
825
826/// A set of border radii for a rounded rectangle
827#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default)]
828pub struct BorderRadii {
829    pub top_left: f32,
830    pub top_right: f32,
831    pub bottom_left: f32,
832    pub bottom_right: f32,
833}
834
835/// Represents the radii of each corner for a rounded rectangle.
836///
837/// # Fields
838///
839/// - `top_left`: Radius of the top-left corner.
840/// - `top_right`: Radius of the top-right corner.
841/// - `bottom_left`: Radius of the bottom-left corner.
842/// - `bottom_right`: Radius of the bottom-right corner.
843///
844/// # Examples
845///
846/// Creating uniform and non-uniform border radii:
847///
848/// ```rust
849/// use grafo::BorderRadii;
850///
851/// // Uniform border radii
852/// let uniform_radii = BorderRadii::new(10.0);
853///
854/// // Custom border radii
855/// let custom_radii = BorderRadii {
856///     top_left: 5.0,
857///     top_right: 10.0,
858///     bottom_left: 15.0,
859///     bottom_right: 20.0,
860/// };
861/// ```
862impl BorderRadii {
863    /// Creates a new `BorderRadii` with the same radius for all corners.
864    ///
865    /// # Parameters
866    ///
867    /// - `radius`: The radius to apply to all corners.
868    ///
869    /// # Returns
870    ///
871    /// A `BorderRadii` instance with uniform corner radii.
872    ///
873    /// # Examples
874    ///
875    /// ```rust
876    /// use grafo::BorderRadii;
877    ///
878    /// let radii = BorderRadii::new(10.0);
879    /// ```
880    pub fn new(radius: f32) -> Self {
881        let r = radius.abs();
882        BorderRadii {
883            top_left: r,
884            top_right: r,
885            bottom_left: r,
886            bottom_right: r,
887        }
888    }
889}
890
891impl core::fmt::Display for BorderRadii {
892    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
893        // In the order of a well known convention (CSS) clockwise from top left
894        write!(
895            f,
896            "BorderRadii({}, {}, {}, {})",
897            self.top_left, self.top_right, self.bottom_left, self.bottom_right
898        )
899    }
900}
901
902impl From<BorderRadii> for lyon::path::builder::BorderRadii {
903    fn from(val: BorderRadii) -> Self {
904        lyon::path::builder::BorderRadii {
905            top_left: val.top_left,
906            top_right: val.top_right,
907            bottom_left: val.bottom_left,
908            bottom_right: val.bottom_right,
909        }
910    }
911}
912
913pub(crate) trait DrawShapeCommand {
914    fn index_buffer_range(&self) -> Option<(usize, usize)>; // (start_index, index_count)
915    fn is_empty(&self) -> bool;
916    fn stencil_ref_mut(&mut self) -> &mut Option<u32>;
917    fn instance_index_mut(&mut self) -> &mut Option<usize>;
918    fn instance_index(&self) -> Option<usize>;
919    fn transform(&self) -> Option<InstanceTransform>;
920    fn set_transform(&mut self, t: InstanceTransform);
921    fn texture_id(&self, layer: usize) -> Option<u64>;
922    fn set_texture_id(&mut self, layer: usize, id: Option<u64>);
923    fn instance_color_override(&self) -> Option<[f32; 4]>;
924    fn set_instance_color_override(&mut self, color: Option<[f32; 4]>);
925}
926
927impl DrawShapeCommand for ShapeDrawData {
928    #[inline]
929    fn index_buffer_range(&self) -> Option<(usize, usize)> {
930        self.index_buffer_range
931    }
932
933    #[inline]
934    fn is_empty(&self) -> bool {
935        self.is_empty
936    }
937
938    #[inline]
939    fn stencil_ref_mut(&mut self) -> &mut Option<u32> {
940        &mut self.stencil_ref
941    }
942
943    #[inline]
944    fn instance_index_mut(&mut self) -> &mut Option<usize> {
945        &mut self.instance_index
946    }
947
948    #[inline]
949    fn instance_index(&self) -> Option<usize> {
950        self.instance_index
951    }
952
953    #[inline]
954    fn transform(&self) -> Option<InstanceTransform> {
955        self.transform
956    }
957
958    #[inline]
959    fn set_transform(&mut self, t: InstanceTransform) {
960        self.transform = Some(t);
961    }
962
963    #[inline]
964    fn texture_id(&self, layer: usize) -> Option<u64> {
965        self.texture_ids.get(layer).copied().unwrap_or(None)
966    }
967
968    #[inline]
969    fn set_texture_id(&mut self, layer: usize, id: Option<u64>) {
970        if let Some(slot) = self.texture_ids.get_mut(layer) {
971            *slot = id;
972        }
973    }
974
975    #[inline]
976    fn instance_color_override(&self) -> Option<[f32; 4]> {
977        self.color_override
978    }
979
980    #[inline]
981    fn set_instance_color_override(&mut self, color: Option<[f32; 4]>) {
982        self.color_override = color;
983    }
984}
985
986impl DrawShapeCommand for CachedShapeDrawData {
987    #[inline]
988    fn index_buffer_range(&self) -> Option<(usize, usize)> {
989        self.index_buffer_range
990    }
991
992    #[inline]
993    fn is_empty(&self) -> bool {
994        self.is_empty
995    }
996
997    #[inline]
998    fn stencil_ref_mut(&mut self) -> &mut Option<u32> {
999        &mut self.stencil_ref
1000    }
1001
1002    #[inline]
1003    fn instance_index_mut(&mut self) -> &mut Option<usize> {
1004        &mut self.instance_index
1005    }
1006
1007    #[inline]
1008    fn instance_index(&self) -> Option<usize> {
1009        self.instance_index
1010    }
1011
1012    #[inline]
1013    fn transform(&self) -> Option<InstanceTransform> {
1014        self.transform
1015    }
1016
1017    #[inline]
1018    fn set_transform(&mut self, t: InstanceTransform) {
1019        self.transform = Some(t);
1020    }
1021
1022    #[inline]
1023    fn texture_id(&self, layer: usize) -> Option<u64> {
1024        self.texture_ids.get(layer).copied().unwrap_or(None)
1025    }
1026
1027    #[inline]
1028    fn set_texture_id(&mut self, layer: usize, id: Option<u64>) {
1029        if let Some(slot) = self.texture_ids.get_mut(layer) {
1030            *slot = id;
1031        }
1032    }
1033
1034    #[inline]
1035    fn instance_color_override(&self) -> Option<[f32; 4]> {
1036        self.color_override
1037    }
1038
1039    #[inline]
1040    fn set_instance_color_override(&mut self, color: Option<[f32; 4]>) {
1041        self.color_override = color;
1042    }
1043}