kozan_core/paint/property_state.rs
1//! Paint property state — the visual context for display items.
2//!
3//! Chrome equivalent: `PropertyTreeState` — a 4-tuple pointing to nodes
4//! in the transform, clip, effect, and scroll property trees.
5//!
6//! For Kozan's initial implementation, we use a flat state rather than
7//! full property trees. Property trees will be added when compositing
8//! and incremental paint invalidation are needed.
9//!
10//! # Chrome mapping
11//!
12//! | Chrome | Kozan |
13//! |--------|-------|
14//! | `PropertyTreeState` | `PropertyState` |
15//! | `TransformPaintPropertyNode` | `transform` field |
16//! | `ClipPaintPropertyNode` | `clip` field |
17//! | `EffectPaintPropertyNode` | `opacity` field |
18
19use kozan_primitives::geometry::Rect;
20
21/// The visual property state affecting a group of display items.
22///
23/// Chrome equivalent: `PropertyTreeState`.
24/// Adjacent display items sharing the same `PropertyState` are grouped
25/// into a `PaintChunk`.
26///
27/// # Future
28///
29/// When compositing is added, this will reference nodes in separate
30/// transform/clip/effect property trees for incremental updates.
31#[derive(Debug, Clone, PartialEq)]
32pub struct PropertyState {
33 /// Cumulative transform (translation only for now).
34 /// Chrome: reference to `TransformPaintPropertyNode`.
35 pub transform_x: f32,
36 pub transform_y: f32,
37
38 /// Current clip rectangle. `None` = no clip.
39 /// Chrome: reference to `ClipPaintPropertyNode`.
40 pub clip: Option<Rect>,
41
42 /// Current opacity (1.0 = fully opaque).
43 /// Chrome: reference to `EffectPaintPropertyNode`.
44 pub opacity: f32,
45}
46
47impl Default for PropertyState {
48 fn default() -> Self {
49 Self {
50 transform_x: 0.0,
51 transform_y: 0.0,
52 clip: None,
53 opacity: 1.0,
54 }
55 }
56}
57
58impl PropertyState {
59 /// Create a root property state (no transform, no clip, full opacity).
60 #[must_use]
61 pub fn root() -> Self {
62 Self::default()
63 }
64
65 /// Whether this state is the identity (no visual effects applied).
66 #[must_use]
67 pub fn is_identity(&self) -> bool {
68 self.transform_x == 0.0
69 && self.transform_y == 0.0
70 && self.clip.is_none()
71 && self.opacity == 1.0
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn root_is_identity() {
81 let state = PropertyState::root();
82 assert!(state.is_identity());
83 }
84
85 #[test]
86 fn non_identity_transform() {
87 let state = PropertyState {
88 transform_x: 10.0,
89 ..PropertyState::default()
90 };
91 assert!(!state.is_identity());
92 }
93
94 #[test]
95 fn non_identity_opacity() {
96 let state = PropertyState {
97 opacity: 0.5,
98 ..PropertyState::default()
99 };
100 assert!(!state.is_identity());
101 }
102
103 #[test]
104 fn non_identity_clip() {
105 let state = PropertyState {
106 clip: Some(Rect::new(0.0, 0.0, 100.0, 100.0)),
107 ..PropertyState::default()
108 };
109 assert!(!state.is_identity());
110 }
111
112 #[test]
113 fn equality() {
114 let a = PropertyState::root();
115 let b = PropertyState::root();
116 assert_eq!(a, b);
117 }
118}