Skip to main content

ferroui_base/media/
stream_geometry.rs

1use crate::media::{FillRule, Geometry, GeometryImpl, PathMarkupParser, StreamGeometryContext, SweepDirection};
2use crate::platform::{self, IGeometryContext, IGeometryImpl, IStreamGeometryBuilder};
3use crate::utilities::FormatError;
4use crate::{ferro_class, ferro_impl_classes, instantiate, FerroObjectImpl, Point, Ref, Size};
5use std::cell::RefCell;
6use std::sync::Arc;
7
8/// Represents the geometry of an arbitrarily complex shape.
9#[repr(C)]
10pub struct StreamGeometry {
11    base: Geometry,
12    impl_: RefCell<Option<Arc<dyn IGeometryImpl>>>,
13}
14
15ferro_class! {
16    StreamGeometry: Geometry, virtuals StreamGeometryImpl: GeometryImpl {
17        /// Creates the context that [`StreamGeometry::open`] wraps.
18        fn open_core(this) -> Box<dyn IGeometryContext>;
19    }
20}
21crate::ferro_class_info!(StreamGeometry { new: StreamGeometry::new });
22ferro_impl_classes!(StreamGeometry: FerroObjectImpl);
23
24impl GeometryImpl for StreamGeometry {
25    fn clone_geometry(this: &Self) -> Ref<Geometry> {
26        let result = StreamGeometry::with_impl(this.create_defining_geometry());
27        result.set_transform(this.transform());
28        result.upcast()
29    }
30
31    fn create_defining_geometry(this: &Self) -> Option<Arc<dyn IGeometryImpl>> {
32        let mut impl_ = this.impl_.borrow_mut();
33        if impl_.is_none() {
34            let mut builder = Self::create_builder(None);
35            *impl_ = Some(builder.to_geometry());
36            builder.dispose();
37        }
38
39        impl_.clone()
40    }
41}
42
43impl StreamGeometryImpl for StreamGeometry {
44    fn open_core(this: &Self) -> Box<dyn IGeometryContext> {
45        let source = this.impl_.borrow().clone();
46        Box::new(BuilderWrapper { builder: Self::create_builder(source.as_deref()), owner: this.to_ref() })
47    }
48}
49
50impl StreamGeometry {
51    /// Creates the class data.
52    pub fn construct() -> Self {
53        Self { base: Geometry::construct(), impl_: RefCell::new(None) }
54    }
55
56    pub fn new() -> Ref<Self> {
57        instantiate(Self::construct())
58    }
59
60    /// Creates a stream geometry over a platform implementation.
61    fn with_impl(impl_: Option<Arc<dyn IGeometryImpl>>) -> Ref<Self> {
62        instantiate(Self { base: Geometry::construct(), impl_: RefCell::new(impl_) })
63    }
64
65    /// Creates a stream geometry from a string of path data.
66    pub fn parse(s: &str) -> Result<Ref<StreamGeometry>, FormatError> {
67        let stream_geometry = StreamGeometry::new();
68
69        let mut builder = Self::create_builder(None);
70        {
71            let mut parser = PathMarkupParser::new(&mut *builder);
72            let result = parser.parse(s);
73            parser.dispose();
74            result?;
75        }
76
77        *stream_geometry.impl_.borrow_mut() = Some(builder.to_geometry());
78
79        Ok(stream_geometry)
80    }
81
82    /// Opens the geometry to start defining it.
83    ///
84    /// Returns a [`StreamGeometryContext`] which can be used to define the
85    /// geometry.
86    ///
87    /// The new figures are added to the existing ones. The geometry is
88    /// updated when the context is disposed.
89    pub fn open(&self) -> StreamGeometryContext {
90        StreamGeometryContext::from_context(self.open_core())
91    }
92
93    fn set_impl(&self, impl_: Arc<dyn IGeometryImpl>) {
94        *self.impl_.borrow_mut() = Some(impl_);
95        self.invalidate_geometry();
96    }
97
98    fn create_builder(source: Option<&dyn IGeometryImpl>) -> Box<dyn IStreamGeometryBuilder> {
99        let factory = platform::render_interface();
100        factory.create_stream_geometry_builder(source)
101    }
102}
103
104/// Wraps an [`IStreamGeometryBuilder`] and sets the [`StreamGeometry`]
105/// geometry when disposed.
106struct BuilderWrapper {
107    builder: Box<dyn IStreamGeometryBuilder>,
108    owner: Ref<StreamGeometry>,
109}
110
111impl IGeometryContext for BuilderWrapper {
112    fn arc_to(
113        &mut self,
114        point: Point,
115        size: Size,
116        rotation_angle: f64,
117        is_large_arc: bool,
118        sweep_direction: SweepDirection,
119        is_stroked: bool,
120    ) {
121        self.builder.arc_to(point, size, rotation_angle, is_large_arc, sweep_direction, is_stroked);
122    }
123
124    fn begin_figure(&mut self, start_point: Point, is_filled: bool) {
125        self.builder.begin_figure(start_point, is_filled);
126    }
127
128    fn cubic_bezier_to(&mut self, control_point1: Point, control_point2: Point, end_point: Point, is_stroked: bool) {
129        self.builder.cubic_bezier_to(control_point1, control_point2, end_point, is_stroked);
130    }
131
132    fn quadratic_bezier_to(&mut self, control_point: Point, end_point: Point, is_stroked: bool) {
133        self.builder.quadratic_bezier_to(control_point, end_point, is_stroked);
134    }
135
136    fn line_to(&mut self, point: Point, is_stroked: bool) {
137        self.builder.line_to(point, is_stroked);
138    }
139
140    fn end_figure(&mut self, is_closed: bool) {
141        self.builder.end_figure(is_closed);
142    }
143
144    fn set_fill_rule(&mut self, fill_rule: FillRule) {
145        self.builder.set_fill_rule(fill_rule);
146    }
147
148    fn dispose(&mut self) {
149        self.owner.set_impl(self.builder.to_geometry());
150        self.builder.dispose();
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::media::geometry::tests::with_mock_render_interface;
158    use crate::media::TranslateTransform;
159    use std::cell::Cell;
160    use std::rc::Rc;
161
162    fn count_changed(target: &StreamGeometry) -> Rc<Cell<i32>> {
163        let changed = Rc::new(Cell::new(0));
164        let c = changed.clone();
165        target.changed(move || c.set(c.get() + 1));
166        changed
167    }
168
169    #[test]
170    fn changed_is_raised_when_the_drawing_is_finished() {
171        with_mock_render_interface(|_| {
172            let target = StreamGeometry::new();
173            let changed = count_changed(&target);
174
175            {
176                let mut context = target.open();
177                assert_eq!(0, changed.get());
178
179                context.begin_figure(Point::new(0.0, 0.0), true);
180                context.line_to(Point::new(10.0, 0.0), true);
181                context.end_figure(true);
182
183                assert_eq!(0, changed.get());
184            }
185
186            assert_eq!(1, changed.get());
187        });
188    }
189
190    #[test]
191    fn changed_is_raised_when_the_drawing_is_finished_and_a_transform_is_set() {
192        with_mock_render_interface(|_| {
193            let target = StreamGeometry::new();
194            target.set_transform(TranslateTransform::with_offset(10.0, 20.0));
195            let changed = count_changed(&target);
196
197            {
198                let mut context = target.open();
199                assert_eq!(0, changed.get());
200
201                context.begin_figure(Point::new(0.0, 0.0), true);
202                context.line_to(Point::new(10.0, 0.0), true);
203                context.end_figure(true);
204
205                assert_eq!(0, changed.get());
206            }
207
208            assert_eq!(1, changed.get());
209        });
210    }
211}