Skip to main content

ferroui_base/media/
geometry_group.rs

1use crate::media::{FillRule, Geometry, GeometryCollection, GeometryImpl};
2use crate::platform::{self, IGeometryImpl};
3use crate::{
4    ferro_class, ferro_property, instantiate, DirectProperty, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
5    FerroPropertyChangedEventArgs, Ref, StyledProperty,
6};
7use std::cell::RefCell;
8use std::sync::Arc;
9
10/// Represents a composite geometry, composed of other [`Geometry`] objects.
11#[repr(C)]
12pub struct GeometryGroup {
13    base: Geometry,
14    children: RefCell<Option<GeometryCollection>>,
15}
16
17ferro_class!(GeometryGroup: Geometry);
18crate::ferro_class_info!(GeometryGroup { new: GeometryGroup::new });
19
20impl FerroObjectImpl for GeometryGroup {
21    fn constructed(this: &Self) {
22        Self::parent_constructed(this);
23        this.children_or_panic().set_parent(Some(&this.to_ref()));
24    }
25
26    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
27        Self::parent_on_property_changed(this, change);
28
29        match change.property().name() {
30            "FillRule" | "Children" => this.invalidate_geometry(),
31            _ => {}
32        }
33    }
34}
35
36impl GeometryImpl for GeometryGroup {
37    fn clone_geometry(this: &Self) -> Ref<Geometry> {
38        let result = GeometryGroup::new();
39        result.set_fill_rule(this.fill_rule());
40        result.set_transform(this.transform());
41        let children = this.children_or_panic();
42        if !children.is_empty() {
43            result.set_children(GeometryCollection::from_items(children.iter()));
44        }
45        result.upcast()
46    }
47
48    fn create_defining_geometry(this: &Self) -> Option<Arc<dyn IGeometryImpl>> {
49        let children = this.children_or_panic();
50        if children.is_empty() {
51            return None;
52        }
53
54        let factory = platform::render_interface();
55        // Children without a platform implementation contribute nothing.
56        let children: Vec<Arc<dyn IGeometryImpl>> = children.iter().filter_map(|child| child.platform_impl()).collect();
57
58        Some(factory.create_geometry_group(this.fill_rule(), &children))
59    }
60}
61
62crate::ferro_properties! { impl GeometryGroup {
63    ferro_property!(pub fn children_property() -> DirectProperty<GeometryGroup, Option<GeometryCollection>> {
64        FerroProperty::register_direct::<GeometryGroup, _>(
65            "Children",
66            |o| o.children.borrow().clone(),
67            Some(|o, v| o.set_children_core(v)),
68            None,
69        )
70    });
71
72    ferro_property!(pub fn fill_rule_property() -> StyledProperty<FillRule> {
73        FerroProperty::register::<GeometryGroup, _>("FillRule", FillRule::EvenOdd)
74    });
75} }
76
77impl GeometryGroup {
78    /// Creates the class data.
79    pub fn construct() -> Self {
80        Self { base: Geometry::construct(), children: RefCell::new(Some(GeometryCollection::new())) }
81    }
82
83    pub fn new() -> Ref<Self> {
84        instantiate(Self::construct())
85    }
86
87    fn children_or_panic(&self) -> GeometryCollection {
88        self.children.borrow().clone().expect("Children is not set")
89    }
90
91    /// The collection that contains the child geometries. Panics if the
92    /// property has been cleared.
93    pub fn children(&self) -> GeometryCollection {
94        self.children_or_panic()
95    }
96
97    pub fn set_children(&self, value: GeometryCollection) {
98        self.set_children_core(Some(value));
99    }
100
101    fn set_children_core(&self, value: Option<GeometryCollection>) {
102        let old = self.children.borrow().clone();
103        self.on_children_changed(old.as_ref(), value.as_ref());
104        self.set_and_raise(Self::children_property(), &self.children, value);
105    }
106
107    /// How the intersecting areas of the children are combined. The default
108    /// is [`FillRule::EvenOdd`].
109    pub fn fill_rule(&self) -> FillRule {
110        self.get_value(Self::fill_rule_property())
111    }
112
113    pub fn set_fill_rule(&self, value: FillRule) {
114        self.set_value(Self::fill_rule_property(), value)
115    }
116
117    fn on_children_changed(&self, old_children: Option<&GeometryCollection>, new_children: Option<&GeometryCollection>) {
118        if let Some(old_children) = old_children {
119            old_children.set_parent(None);
120        }
121        if let Some(new_children) = new_children {
122            new_children.set_parent(Some(&self.to_ref()));
123        }
124    }
125
126    pub(crate) fn invalidate(&self) {
127        self.invalidate_geometry();
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use std::rc::Rc;
134    use super::*;
135    use crate::media::StreamGeometry;
136    use std::cell::Cell;
137
138    #[test]
139    fn children_should_have_initial_collection() {
140        let target = GeometryGroup::new();
141        assert!(target.children().is_empty());
142        assert!(target.children().parent().unwrap().ptr_eq(&target));
143    }
144
145    #[test]
146    fn children_change_should_raise_changed() {
147        let target = GeometryGroup::new();
148
149        let children = GeometryCollection::new();
150
151        target.set_children(children.clone());
152
153        let is_called = Rc::new(Cell::new(false));
154        let c = is_called.clone();
155        target.changed(move || c.set(true));
156
157        children.add(StreamGeometry::new().upcast());
158
159        assert!(is_called.get());
160    }
161
162    #[test]
163    fn replaced_children_no_longer_raise_changed() {
164        let target = GeometryGroup::new();
165        let old = target.children();
166        target.set_children(GeometryCollection::new());
167        assert!(old.parent().is_none());
168
169        let is_called = Rc::new(Cell::new(false));
170        let c = is_called.clone();
171        target.changed(move || c.set(true));
172
173        old.add(StreamGeometry::new().upcast());
174
175        assert!(!is_called.get());
176    }
177
178    #[test]
179    fn empty_group_has_no_platform_impl() {
180        let target = GeometryGroup::new();
181        assert!(target.platform_impl().is_none());
182        assert_eq!(crate::Rect::default(), target.bounds());
183    }
184}