Skip to main content

astrelis_geometry/
fill.rs

1//! Fill rules and fill options.
2//!
3//! Fill rules determine how to decide which areas are "inside" a path.
4
5use crate::Paint;
6use astrelis_render::Color;
7
8/// Fill rule for determining interior of a path.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum FillRule {
11    /// Non-zero winding rule (default).
12    ///
13    /// A point is inside if the winding number is non-zero.
14    #[default]
15    NonZero,
16    /// Even-odd (parity) rule.
17    ///
18    /// A point is inside if the number of crossings is odd.
19    EvenOdd,
20}
21
22/// Fill properties for geometry.
23#[derive(Debug, Clone, PartialEq)]
24pub struct Fill {
25    /// The paint to use for filling
26    pub paint: Paint,
27    /// Fill rule for determining interior
28    pub rule: FillRule,
29    /// Opacity multiplier (0.0 to 1.0)
30    pub opacity: f32,
31}
32
33impl Fill {
34    /// Create a solid color fill.
35    pub fn solid(color: Color) -> Self {
36        Self {
37            paint: Paint::Solid(color),
38            rule: FillRule::NonZero,
39            opacity: 1.0,
40        }
41    }
42
43    /// Create a fill from a paint.
44    pub fn from_paint(paint: Paint) -> Self {
45        Self {
46            paint,
47            rule: FillRule::NonZero,
48            opacity: 1.0,
49        }
50    }
51
52    /// Set the fill rule.
53    pub fn with_rule(mut self, rule: FillRule) -> Self {
54        self.rule = rule;
55        self
56    }
57
58    /// Set the opacity.
59    pub fn with_opacity(mut self, opacity: f32) -> Self {
60        self.opacity = opacity.clamp(0.0, 1.0);
61        self
62    }
63
64    /// Get the effective color (for solid fills).
65    pub fn effective_color(&self) -> Option<Color> {
66        match &self.paint {
67            Paint::Solid(color) => Some(Color::rgba(
68                color.r,
69                color.g,
70                color.b,
71                color.a * self.opacity,
72            )),
73            _ => None,
74        }
75    }
76}
77
78impl Default for Fill {
79    fn default() -> Self {
80        Self::solid(Color::BLACK)
81    }
82}
83
84impl From<Color> for Fill {
85    fn from(color: Color) -> Self {
86        Self::solid(color)
87    }
88}
89
90impl From<Paint> for Fill {
91    fn from(paint: Paint) -> Self {
92        Self::from_paint(paint)
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_solid_fill() {
102        let fill = Fill::solid(Color::RED);
103        assert_eq!(fill.opacity, 1.0);
104        assert!(matches!(fill.paint, Paint::Solid(_)));
105    }
106
107    #[test]
108    fn test_fill_opacity() {
109        let fill = Fill::solid(Color::RED).with_opacity(0.5);
110        let effective = fill.effective_color().unwrap();
111        assert!((effective.a - 0.5).abs() < 0.01);
112    }
113}