ferroui_base/
combined_geometry.rs1use crate::media::{Geometry, GeometryImpl, Transform};
2use crate::platform::{self, IGeometryImpl};
3use crate::reactive::IDisposable;
4use crate::{
5 ferro_class, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
6 FerroPropertyChangedEventArgs, Nullable, Ref, StyledProperty,
7};
8use std::cell::RefCell;
9use std::rc::Rc;
10use std::sync::Arc;
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
14#[repr(i32)]
15pub enum GeometryCombineMode {
16 #[default]
19 Union = 0,
20
21 Intersect = 1,
25
26 Xor = 2,
31
32 Exclude = 3,
36}
37
38#[repr(C)]
41pub struct CombinedGeometry {
42 base: Geometry,
43 geometry1_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
44 geometry2_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
45}
46
47ferro_class!(CombinedGeometry: Geometry);
48crate::ferro_class_info!(CombinedGeometry { new: CombinedGeometry::new });
49
50impl FerroObjectImpl for CombinedGeometry {
51 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
52 Self::parent_on_property_changed(this, change);
53
54 let slot = if change.property() == Self::geometry1_property().as_property() {
55 &this.geometry1_subscription
56 } else if change.property() == Self::geometry2_property().as_property() {
57 &this.geometry2_subscription
58 } else {
59 return;
60 };
61
62 if let Some(subscription) = slot.take() {
63 subscription.dispose();
64 }
65
66 if let Some(new_value) = change.get_new_value::<Option<Ref<Geometry>>>() {
67 let weak = this.to_ref().downgrade();
68 let subscription = new_value.changed(move || {
69 if let Some(this) = weak.upgrade() {
70 this.invalidate_geometry();
71 }
72 });
73 *slot.borrow_mut() = Some(subscription);
74 }
75 }
76}
77
78impl GeometryImpl for CombinedGeometry {
79 fn clone_geometry(this: &Self) -> Ref<Geometry> {
80 CombinedGeometry::with_mode_and_transform(
81 this.geometry_combine_mode(),
82 this.geometry1(),
83 this.geometry2(),
84 this.transform(),
85 )
86 .upcast()
87 }
88
89 fn create_defining_geometry(this: &Self) -> Option<Arc<dyn IGeometryImpl>> {
90 let g1 = this.geometry1().and_then(|g| g.platform_impl());
91 let g2 = this.geometry2().and_then(|g| g.platform_impl());
92
93 if let (Some(g1), Some(g2)) = (&g1, &g2) {
94 let factory = platform::render_interface();
95 return Some(factory.create_combined_geometry(this.geometry_combine_mode(), g1.clone(), g2.clone()));
96 }
97
98 if this.geometry_combine_mode() == GeometryCombineMode::Intersect {
99 return None;
100 }
101
102 g1.or(g2)
103 }
104}
105
106crate::ferro_properties! { impl CombinedGeometry {
107 ferro_property!(pub fn geometry1_property() -> StyledProperty<Option<Ref<Geometry>>> {
108 FerroProperty::register::<CombinedGeometry, _>("Geometry1", None)
109 });
110
111 ferro_property!(pub fn geometry2_property() -> StyledProperty<Option<Ref<Geometry>>> {
112 FerroProperty::register::<CombinedGeometry, _>("Geometry2", None)
113 });
114
115 ferro_property!(pub fn geometry_combine_mode_property() -> StyledProperty<GeometryCombineMode> {
116 FerroProperty::register::<CombinedGeometry, _>("GeometryCombineMode", GeometryCombineMode::Union)
117 });
118} }
119
120impl CombinedGeometry {
121 fn static_constructor() {
122 Geometry::affects_geometry(&[
123 Self::geometry1_property().as_property(),
124 Self::geometry2_property().as_property(),
125 Self::geometry_combine_mode_property().as_property(),
126 ]);
127 }
128
129 pub fn construct() -> Self {
131 Self {
132 base: Geometry::construct(),
133 geometry1_subscription: RefCell::new(None),
134 geometry2_subscription: RefCell::new(None),
135 }
136 }
137
138 pub fn new() -> Ref<Self> {
139 instantiate(Self::construct())
140 }
141
142 pub fn with_geometries(geometry1: Ref<Geometry>, geometry2: Ref<Geometry>) -> Ref<Self> {
144 let result = Self::new();
145 result.set_geometry1(geometry1);
146 result.set_geometry2(geometry2);
147 result
148 }
149
150 pub fn with_mode(
152 combine_mode: GeometryCombineMode,
153 geometry1: Option<Ref<Geometry>>,
154 geometry2: Option<Ref<Geometry>>,
155 ) -> Ref<Self> {
156 let result = Self::new();
157 result.set_geometry1(geometry1);
158 result.set_geometry2(geometry2);
159 result.set_geometry_combine_mode(combine_mode);
160 result
161 }
162
163 pub fn with_mode_and_transform(
166 combine_mode: GeometryCombineMode,
167 geometry1: Option<Ref<Geometry>>,
168 geometry2: Option<Ref<Geometry>>,
169 transform: impl Into<Nullable<Transform>>,
170 ) -> Ref<Self> {
171 let result = Self::new();
172 result.set_geometry1(geometry1);
173 result.set_geometry2(geometry2);
174 result.set_geometry_combine_mode(combine_mode);
175 result.set_transform(transform);
176 result
177 }
178
179 pub fn geometry1(&self) -> Option<Ref<Geometry>> {
181 self.get_value(Self::geometry1_property())
182 }
183
184 pub fn set_geometry1(&self, value: impl Into<Nullable<Geometry>>) {
185 self.set_value(Self::geometry1_property(), value.into().0)
186 }
187
188 pub fn geometry2(&self) -> Option<Ref<Geometry>> {
190 self.get_value(Self::geometry2_property())
191 }
192
193 pub fn set_geometry2(&self, value: impl Into<Nullable<Geometry>>) {
194 self.set_value(Self::geometry2_property(), value.into().0)
195 }
196
197 pub fn geometry_combine_mode(&self) -> GeometryCombineMode {
202 self.get_value(Self::geometry_combine_mode_property())
203 }
204
205 pub fn set_geometry_combine_mode(&self, value: GeometryCombineMode) {
206 self.set_value(Self::geometry_combine_mode_property(), value)
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use std::cell::Cell;
214 use crate::media::{GeometryGroup, PathGeometry};
215
216 #[test]
217 fn child_geometry_change_invalidates() {
218 let child = PathGeometry::new();
219 let target = CombinedGeometry::with_mode(GeometryCombineMode::Xor, Some(child.clone().upcast()), None);
220 let count = Rc::new(Cell::new(0));
221 let c = count.clone();
222 target.changed(move || c.set(c.get() + 1));
223
224 child.set_fill_rule(crate::media::FillRule::NonZero);
225 assert_eq!(0, count.get());
228
229 child.set_figures(None);
230 child.set_figures(Some(crate::media::PathFigures::new()));
231 child.figures().unwrap().add(crate::media::PathFigure::new());
232 assert_eq!(1, count.get());
233
234 target.set_geometry1(None);
235 assert_eq!(2, count.get());
236 child.figures().unwrap().add(crate::media::PathFigure::new());
237 assert_eq!(2, count.get());
238 }
239
240 #[test]
241 fn missing_operands() {
242 let target = CombinedGeometry::new();
243 assert!(target.platform_impl().is_none());
244 target.set_geometry_combine_mode(GeometryCombineMode::Intersect);
245 target.set_geometry1(GeometryGroup::new());
246 assert!(target.platform_impl().is_none());
247 assert_eq!(GeometryCombineMode::Intersect, target.geometry_combine_mode());
248 let clone = target.clone_geometry();
249 assert!(clone.is::<CombinedGeometry>());
250 }
251}