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}