1use crate::media::{
2 CombinedGeometry, GeometryCombineMode, IPen, ImmutableGeometry, IntersectionResult, PlatformGeometry,
3 RectangleGeometry, StreamGeometry, Transform,
4};
5use crate::media::ref_adapter::RefAdapter;
6use crate::platform::IGeometryImpl;
7use crate::reactive::{Disposable, IDisposable};
8use crate::rendering::composition::drawing::{CompositorResourceHolder, ICompositionRenderResource};
9use crate::rendering::composition::generated::ServerCompositionSimpleGeometryProps;
10use crate::rendering::composition::server::{IServerObject, ServerCompositionSimpleGeometry, ServerObjectId};
11use crate::rendering::composition::transport::BatchStreamWriter;
12use crate::rendering::composition::{Compositor, ICompositorSerializable};
13use crate::utilities::{FormatError, HandlerList};
14use crate::{
15 ferro_class, ferro_property, FerroObject, FerroObjectImpl, FerroProperty,
16 FerroPropertyChangedEventArgs, IntoRef, Matrix, Nullable, Point, Rect, Ref, StyledProperty, Upcast,
17};
18use std::cell::{Cell, RefCell};
19use std::rc::Rc;
20use std::sync::Arc;
21
22#[repr(C)]
24pub struct Geometry {
25 base: FerroObject,
26 is_dirty: Cell<bool>,
27 can_invalidate: bool,
28 platform_impl: RefCell<Option<Arc<dyn IGeometryImpl>>>,
29 changed: HandlerList<dyn Fn()>,
30 transform_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
31 resource: CompositorResourceHolder,
32}
33
34ferro_class! {
35 Geometry: FerroObject, virtuals GeometryImpl: FerroObjectImpl {
36 fn clone_geometry(this) -> Ref<Geometry>;
38
39 fn create_defining_geometry(this) -> Option<Arc<dyn IGeometryImpl>>;
42 }
43}
44
45crate::ferro_impl_classes!(Geometry: FerroObjectImpl);
46
47impl GeometryImpl for Geometry {
48 fn clone_geometry(_this: &Self) -> Ref<Geometry> {
49 panic!("Geometry is abstract: 'clone_geometry' must be implemented by the deriving class")
50 }
51
52 fn create_defining_geometry(_this: &Self) -> Option<Arc<dyn IGeometryImpl>> {
53 panic!("Geometry is abstract: 'create_defining_geometry' must be implemented by the deriving class")
54 }
55}
56
57crate::ferro_properties! { impl Geometry {
58 ferro_property!(pub fn transform_property() -> StyledProperty<Option<Ref<Transform>>> {
59 FerroProperty::register::<Geometry, _>("Transform", None)
60 });
61} }
62
63impl Geometry {
64 fn static_constructor() {
65 Self::transform_property().changed().add_class_handler::<Geometry>(|x, e| x.transform_property_changed(e));
66 }
67
68 pub fn construct() -> Self {
71 Self {
72 base: FerroObject::construct(),
73 is_dirty: Cell::new(true),
74 can_invalidate: true,
75 platform_impl: RefCell::new(None),
76 changed: HandlerList::new(),
77 transform_subscription: RefCell::new(None),
78 resource: CompositorResourceHolder::new(),
79 }
80 }
81
82 pub fn construct_with_impl(platform_impl: Option<Arc<dyn IGeometryImpl>>) -> Self {
85 Self {
86 base: FerroObject::construct(),
87 is_dirty: Cell::new(false),
88 can_invalidate: false,
89 platform_impl: RefCell::new(platform_impl),
90 changed: HandlerList::new(),
91 transform_subscription: RefCell::new(None),
92 resource: CompositorResourceHolder::new(),
93 }
94 }
95
96 pub fn changed_subscriber_count(&self) -> usize {
100 self.changed.len()
101 }
102
103 pub fn changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
106 let token = self.changed.add(Rc::new(handler));
107 let weak = self.to_ref().downgrade();
108 Disposable::create(move || {
109 if let Some(this) = weak.upgrade() {
110 this.changed.remove(token);
111 }
112 })
113 }
114
115 fn raise_changed(&self) {
116 if self.changed.is_empty() {
117 return;
118 }
119 for (_, handler) in self.changed.snapshot().iter() {
120 handler();
121 }
122 }
123
124 pub fn bounds(&self) -> Rect {
126 self.platform_impl().map(|g| g.bounds()).unwrap_or_default()
127 }
128
129 pub fn platform_impl(&self) -> Option<Arc<dyn IGeometryImpl>> {
131 if self.is_dirty.get() {
132 let mut geometry = self.create_defining_geometry();
133 let transform = self.transform();
134
135 if let (Some(defining), Some(transform)) = (&geometry, &transform) {
136 let value = transform.value();
137 if value != Matrix::IDENTITY {
138 let transformed: Arc<dyn IGeometryImpl> = defining.with_transform(value);
139 geometry = Some(transformed);
140 }
141 }
142
143 *self.platform_impl.borrow_mut() = geometry;
144 self.is_dirty.set(false);
145 }
146
147 self.platform_impl.borrow().clone()
148 }
149
150 pub fn transform(&self) -> Option<Ref<Transform>> {
152 self.get_value(Self::transform_property())
153 }
154
155 pub fn set_transform(&self, value: impl Into<Nullable<Transform>>) {
156 self.set_value(Self::transform_property(), value.into().0)
157 }
158
159 pub fn parse(s: &str) -> Result<Ref<Geometry>, FormatError> {
161 Ok(StreamGeometry::parse(s)?.upcast())
162 }
163
164 pub fn try_get_path_data(&self) -> Option<crate::media::GeometryPathData> {
168 self.platform_impl()?.try_get_path_data()
169 }
170
171 pub fn get_render_bounds(&self, pen: &dyn IPen) -> Rect {
173 self.platform_impl().map(|g| g.get_render_bounds(Some(pen))).unwrap_or_default()
174 }
175
176 pub fn fill_contains(&self, point: Point) -> bool {
178 self.platform_impl().is_some_and(|g| g.fill_contains(point))
179 }
180
181 pub fn get_fill_intersection_result(&self, geometry: &Geometry) -> Option<IntersectionResult> {
183 match geometry.platform_impl() {
184 None => Some(IntersectionResult::Empty),
185 Some(other) => self.platform_impl().map(|g| g.get_fill_intersection_result(&*other)),
186 }
187 }
188
189 pub fn stroke_contains(&self, pen: &dyn IPen, point: Point) -> bool {
191 self.platform_impl().is_some_and(|g| g.stroke_contains(Some(pen), point))
192 }
193
194 pub fn get_widened_geometry(&self, pen: &dyn IPen) -> Ref<Geometry> {
197 ImmutableGeometry::new(self.platform_impl().map(|g| g.get_widened_geometry(pen))).upcast()
198 }
199
200 pub fn affects_geometry(properties: &[&'static FerroProperty]) {
204 for property in properties {
205 property.changed().subscribe(Self::affects_geometry_invalidate);
206 }
207 }
208
209 pub fn invalidate_geometry(&self) {
211 if !self.can_invalidate {
212 return;
213 }
214 self.is_dirty.set(true);
215 *self.platform_impl.borrow_mut() = None;
216 self.register_for_serialization();
217 self.raise_changed();
218 }
219
220 fn transform_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
221 let new_value = e.get_new_value::<Option<Ref<Transform>>>();
222
223 if let Some(subscription) = self.transform_subscription.take() {
224 subscription.dispose();
225 }
226
227 if let Some(new_value) = &new_value {
228 let weak = self.to_ref().downgrade();
229 let weak_transform = new_value.downgrade();
230 let subscription = new_value.changed(move || {
231 if let (Some(this), Some(transform)) = (weak.upgrade(), weak_transform.upgrade()) {
232 this.transform_changed(Some(&transform));
233 }
234 });
235 *self.transform_subscription.borrow_mut() = Some(subscription);
236 }
237
238 self.transform_changed(new_value.as_ref());
239 }
240
241 fn transform_changed(&self, sender: Option<&Ref<Transform>>) {
242 let transform = sender.map(|t| t.value());
243 let current = self.platform_impl.borrow().clone();
244
245 if let Some(current) = current {
246 let replacement: Option<Arc<dyn IGeometryImpl>> = match current.as_transformed_geometry() {
247 Some(t) => match transform {
248 None => Some(t.source_geometry()),
249 Some(transform) if transform == Matrix::IDENTITY => Some(t.source_geometry()),
250 Some(transform) if transform != t.transform() => {
251 let transformed: Arc<dyn IGeometryImpl> = t.source_geometry().with_transform(transform);
252 Some(transformed)
253 }
254 Some(_) => None,
255 },
256 None => match transform {
257 Some(transform) if transform != Matrix::IDENTITY => {
258 let transformed: Arc<dyn IGeometryImpl> = current.with_transform(transform);
259 Some(transformed)
260 }
261 _ => None,
262 },
263 };
264
265 if let Some(replacement) = replacement {
266 *self.platform_impl.borrow_mut() = Some(replacement);
267 }
268 }
269
270 self.register_for_serialization();
271 self.raise_changed();
272 }
273
274 pub(crate) fn as_compositor_serializable(&self) -> Rc<dyn ICompositorSerializable> {
277 Rc::new(RefAdapter(self.to_ref()))
278 }
279
280 fn register_for_serialization(&self) {
281 if self.resource.is_attached() {
282 let serializable = self.as_compositor_serializable();
283 self.resource.register_for_invalidation_on_all_compositors(&serializable);
284 }
285 }
286
287 fn affects_geometry_invalidate(e: &FerroPropertyChangedEventArgs<'_>) {
288 if let Some(control) = e.sender().downcast_ref::<Geometry>() {
289 control.invalidate_geometry();
290 }
291 }
292
293 pub fn contour_length(&self) -> f64 {
296 self.platform_impl().map(|g| g.contour_length()).unwrap_or(0.0)
297 }
298
299 pub fn combine(
302 geometry1: impl IntoRef<Geometry>,
303 geometry2: &Ref<RectangleGeometry>,
304 combine_mode: GeometryCombineMode,
305 transform: impl Into<Nullable<Transform>>,
306 ) -> Ref<Geometry> {
307 CombinedGeometry::with_mode_and_transform(
308 combine_mode,
309 Some(geometry1.into_ref()),
310 Some(geometry2.clone().upcast()),
311 transform,
312 )
313 .upcast()
314 }
315
316 pub fn try_get_point_at_distance(&self, distance: f64) -> Option<Point> {
318 self.platform_impl()?.try_get_point_at_distance(distance)
319 }
320
321 pub fn try_get_point_and_tangent_at_distance(&self, distance: f64) -> Option<(Point, Point)> {
324 self.platform_impl()?.try_get_point_and_tangent_at_distance(distance)
325 }
326
327 pub fn try_get_segment(
331 &self,
332 start_distance: f64,
333 stop_distance: f64,
334 start_on_begin_figure: bool,
335 ) -> Option<Ref<Geometry>> {
336 let segment = self.platform_impl()?.try_get_segment(start_distance, stop_distance, start_on_begin_figure)?;
337 Some(PlatformGeometry::new(segment).upcast())
338 }
339
340 #[allow(dead_code)]
341 #[inline]
342 fn object(&self) -> &FerroObject {
343 self.upcast()
344 }
345}
346
347impl ICompositionRenderResource for Geometry {
348 fn add_ref_on_compositor(&self, c: &Rc<Compositor>) {
349 let owner = self.as_compositor_serializable();
350 self.resource.create_or_add_ref(c, Some(owner), |c| {
351 c.create_server_object(|server, _| ServerCompositionSimpleGeometry::new(server) as Rc<dyn IServerObject>)
352 });
353 }
354
355 fn release_on_compositor(&self, c: &Rc<Compositor>) {
356 self.resource.release(c);
357 }
358
359 fn get_for_compositor(&self, c: &Compositor) -> ServerObjectId {
360 self.resource.get_for_compositor(c)
361 }
362}
363
364impl ICompositorSerializable for RefAdapter<Geometry> {
365 fn try_get_server(&self, c: &Compositor) -> Option<ServerObjectId> {
366 self.0.resource.try_get_for_compositor(c)
367 }
368
369 fn serialization_key(&self) -> *const () {
370 RefAdapter::reference_id(self)
371 }
372
373 fn serialize_changes(&self, _c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
374 ServerCompositionSimpleGeometryProps::serialize_all_changes(writer, self.0.platform_impl());
375 }
376}
377
378#[cfg(test)]
379pub(crate) mod tests {
380 use super::*;
381 use crate::media::{RotateTransform, GeometryImplExt};
382 use crate::platform::{IStreamGeometryBuilder, ITransformedGeometryImpl};
383 use crate::{ferro_impl_classes, instantiate};
384
385 pub(crate) struct MockGeometryImpl;
387
388 impl MockGeometryImpl {
389 pub fn create() -> Arc<dyn IGeometryImpl> {
390 Arc::new(MockGeometryImpl)
391 }
392 }
393
394 pub(crate) struct MockTransformedGeometryImpl {
395 source: Arc<dyn IGeometryImpl>,
396 transform: Matrix,
397 }
398
399 macro_rules! mock_geometry_members {
400 () => {
401 fn bounds(&self) -> Rect {
402 Rect::default()
403 }
404 fn contour_length(&self) -> f64 {
405 0.0
406 }
407 fn get_render_bounds(&self, _pen: Option<&dyn IPen>) -> Rect {
408 Rect::default()
409 }
410 fn get_widened_geometry(&self, _pen: &dyn IPen) -> Arc<dyn IGeometryImpl> {
411 MockGeometryImpl::create()
412 }
413 fn fill_contains(&self, _point: Point) -> bool {
414 false
415 }
416 fn get_fill_intersection_result(&self, _geometry: &dyn IGeometryImpl) -> IntersectionResult {
417 IntersectionResult::Empty
418 }
419 fn intersect(&self, _geometry: &dyn IGeometryImpl) -> Option<Arc<dyn IGeometryImpl>> {
420 None
421 }
422 fn stroke_contains(&self, _pen: Option<&dyn IPen>, _point: Point) -> bool {
423 false
424 }
425 fn try_get_point_at_distance(&self, _distance: f64) -> Option<Point> {
426 None
427 }
428 fn try_get_point_and_tangent_at_distance(&self, _distance: f64) -> Option<(Point, Point)> {
429 None
430 }
431 fn as_any(&self) -> &dyn std::any::Any {
432 self
433 }
434 fn try_get_segment(&self, _start: f64, _stop: f64, _begin: bool) -> Option<Arc<dyn IGeometryImpl>> {
435 None
436 }
437 };
438 }
439 pub(crate) use mock_geometry_members;
440
441 impl IGeometryImpl for MockGeometryImpl {
442 mock_geometry_members!();
443
444 fn with_transform(&self, transform: Matrix) -> Arc<dyn ITransformedGeometryImpl> {
445 Arc::new(MockTransformedGeometryImpl { source: MockGeometryImpl::create(), transform })
448 }
449 }
450
451 impl IGeometryImpl for MockTransformedGeometryImpl {
452 mock_geometry_members!();
453
454 fn with_transform(&self, transform: Matrix) -> Arc<dyn ITransformedGeometryImpl> {
455 Arc::new(MockTransformedGeometryImpl { source: self.source.clone(), transform })
456 }
457
458 fn as_transformed_geometry(&self) -> Option<&dyn ITransformedGeometryImpl> {
459 Some(self)
460 }
461 }
462
463 impl ITransformedGeometryImpl for MockTransformedGeometryImpl {
464 fn source_geometry(&self) -> Arc<dyn IGeometryImpl> {
465 self.source.clone()
466 }
467
468 fn transform(&self) -> Matrix {
469 self.transform
470 }
471 }
472
473 pub(crate) type CommandLog = Arc<std::sync::Mutex<Vec<String>>>;
474
475 struct MockStreamGeometryBuilder {
478 log: CommandLog,
479 }
480
481 impl crate::platform::IGeometryContext for MockStreamGeometryBuilder {
482 fn arc_to(
483 &mut self,
484 point: Point,
485 size: crate::Size,
486 rotation_angle: f64,
487 is_large_arc: bool,
488 sweep_direction: crate::media::SweepDirection,
489 is_stroked: bool,
490 ) {
491 self.log.lock().unwrap().push(format!(
492 "arc {point} {size} {rotation_angle} {is_large_arc} {sweep_direction:?} {is_stroked}"
493 ));
494 }
495 fn begin_figure(&mut self, start_point: Point, is_filled: bool) {
496 self.log.lock().unwrap().push(format!("begin {start_point} {is_filled}"));
497 }
498 fn cubic_bezier_to(&mut self, p1: Point, p2: Point, p3: Point, is_stroked: bool) {
499 self.log.lock().unwrap().push(format!("cubic {p1} {p2} {p3} {is_stroked}"));
500 }
501 fn quadratic_bezier_to(&mut self, p1: Point, p2: Point, is_stroked: bool) {
502 self.log.lock().unwrap().push(format!("quad {p1} {p2} {is_stroked}"));
503 }
504 fn line_to(&mut self, point: Point, is_stroked: bool) {
505 self.log.lock().unwrap().push(format!("line {point} {is_stroked}"));
506 }
507 fn end_figure(&mut self, is_closed: bool) {
508 self.log.lock().unwrap().push(format!("end {is_closed}"));
509 }
510 fn set_fill_rule(&mut self, fill_rule: crate::media::FillRule) {
511 self.log.lock().unwrap().push(format!("fill {fill_rule:?}"));
512 }
513 fn dispose(&mut self) {
514 self.log.lock().unwrap().push("dispose".to_string());
515 }
516 }
517
518 impl IStreamGeometryBuilder for MockStreamGeometryBuilder {
519 fn to_geometry(&mut self) -> Arc<dyn IGeometryImpl> {
520 MockGeometryImpl::create()
521 }
522 }
523
524 #[derive(Default)]
526 pub(crate) struct MockFactory {
527 pub created: RefCell<Vec<String>>,
528 pub streams: RefCell<Vec<CommandLog>>,
529 }
530
531 impl crate::platform::IPlatformRenderInterface for MockFactory {
532 fn build_glyph_run_geometry(&self, _glyph_run: &crate::media::GlyphRun) -> Arc<dyn IGeometryImpl> {
533 unimplemented!()
534 }
535 fn create_glyph_run(
536 &self,
537 _glyph_typeface: &Rc<crate::media::GlyphTypeface>,
538 _font_rendering_em_size: f64,
539 _glyph_infos: &[crate::media::text_formatting::GlyphInfo],
540 _baseline_origin: Point,
541 ) -> std::sync::Arc<dyn crate::platform::IGlyphRunImpl> {
542 unimplemented!()
543 }
544 fn create_ellipse_geometry(&self, rect: Rect) -> Arc<dyn IGeometryImpl> {
545 self.created.borrow_mut().push(format!("ellipse {rect}"));
546 MockGeometryImpl::create()
547 }
548 fn create_line_geometry(&self, p1: Point, p2: Point) -> Arc<dyn IGeometryImpl> {
549 self.created.borrow_mut().push(format!("line {p1} {p2}"));
550 MockGeometryImpl::create()
551 }
552 fn create_rectangle_geometry(&self, rect: Rect) -> Arc<dyn IGeometryImpl> {
553 self.created.borrow_mut().push(format!("rectangle {rect}"));
554 MockGeometryImpl::create()
555 }
556 fn create_stream_geometry_builder(&self, _source: Option<&dyn IGeometryImpl>) -> Box<dyn IStreamGeometryBuilder> {
557 self.created.borrow_mut().push("stream".to_string());
558 let log = Arc::new(std::sync::Mutex::new(Vec::new()));
559 self.streams.borrow_mut().push(log.clone());
560 Box::new(MockStreamGeometryBuilder { log })
561 }
562 fn create_geometry_group(
563 &self,
564 fill_rule: crate::media::FillRule,
565 children: &[Arc<dyn IGeometryImpl>],
566 ) -> Arc<dyn IGeometryImpl> {
567 self.created.borrow_mut().push(format!("group {fill_rule:?} {}", children.len()));
568 MockGeometryImpl::create()
569 }
570 fn create_combined_geometry(
571 &self,
572 combine_mode: GeometryCombineMode,
573 _g1: Arc<dyn IGeometryImpl>,
574 _g2: Arc<dyn IGeometryImpl>,
575 ) -> Arc<dyn IGeometryImpl> {
576 self.created.borrow_mut().push(format!("combined {combine_mode:?}"));
577 MockGeometryImpl::create()
578 }
579
580 fn create_render_target_bitmap(
582 &self,
583 _: crate::PixelSize,
584 _: crate::Vector,
585 ) -> std::sync::Arc<dyn crate::platform::IRenderTargetBitmapImpl> {
586 unimplemented!()
587 }
588 fn create_writeable_bitmap(
589 &self,
590 _: crate::PixelSize,
591 _: crate::Vector,
592 _: crate::platform::PixelFormat,
593 _: crate::platform::AlphaFormat,
594 ) -> std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl> {
595 unimplemented!()
596 }
597 fn load_bitmap_from_file(&self, _: &str) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
598 unimplemented!()
599 }
600 fn load_bitmap(&self, _: &mut dyn std::io::Read) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
601 unimplemented!()
602 }
603 fn load_writeable_bitmap_to_width(
604 &self,
605 _: &mut dyn std::io::Read,
606 _: i32,
607 _: crate::media::imaging::BitmapInterpolationMode,
608 ) -> std::io::Result<std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl>> {
609 unimplemented!()
610 }
611 fn load_writeable_bitmap_to_height(
612 &self,
613 _: &mut dyn std::io::Read,
614 _: i32,
615 _: crate::media::imaging::BitmapInterpolationMode,
616 ) -> std::io::Result<std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl>> {
617 unimplemented!()
618 }
619 fn load_writeable_bitmap_from_file(
620 &self,
621 _: &str,
622 ) -> std::io::Result<std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl>> {
623 unimplemented!()
624 }
625 fn load_writeable_bitmap(
626 &self,
627 _: &mut dyn std::io::Read,
628 ) -> std::io::Result<std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl>> {
629 unimplemented!()
630 }
631 fn load_bitmap_to_width(
632 &self,
633 _: &mut dyn std::io::Read,
634 _: i32,
635 _: crate::media::imaging::BitmapInterpolationMode,
636 ) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
637 unimplemented!()
638 }
639 fn load_bitmap_to_height(
640 &self,
641 _: &mut dyn std::io::Read,
642 _: i32,
643 _: crate::media::imaging::BitmapInterpolationMode,
644 ) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
645 unimplemented!()
646 }
647 fn resize_bitmap(
648 &self,
649 _: &dyn crate::platform::IBitmapImpl,
650 _: crate::PixelSize,
651 _: crate::media::imaging::BitmapInterpolationMode,
652 ) -> std::sync::Arc<crate::platform::SharedBitmapImpl> {
653 unimplemented!()
654 }
655 fn load_bitmap_from_pixels(
656 &self,
657 _: crate::platform::PixelFormat,
658 _: crate::platform::AlphaFormat,
659 _: &[u8],
660 _: crate::PixelSize,
661 _: crate::Vector,
662 _: i32,
663 ) -> std::sync::Arc<crate::platform::SharedBitmapImpl> {
664 unimplemented!()
665 }
666 fn create_backend_context(
667 &self,
668 _: Option<Rc<dyn crate::platform::IPlatformGraphicsContext>>,
669 ) -> Rc<dyn crate::platform::IPlatformRenderInterfaceContext> {
670 unimplemented!()
671 }
672 fn supports_individual_round_rects(&self) -> bool {
673 false
674 }
675 fn default_alpha_format(&self) -> crate::platform::AlphaFormat {
676 crate::platform::AlphaFormat::Premul
677 }
678 fn default_pixel_format(&self) -> crate::platform::PixelFormat {
679 crate::platform::PixelFormats::RGBA8888
680 }
681 fn is_supported_bitmap_pixel_format(&self, _: crate::platform::PixelFormat) -> bool {
682 true
683 }
684 fn supports_regions(&self) -> bool {
685 false
686 }
687 fn create_region(&self) -> Rc<dyn crate::platform::IPlatformRenderInterfaceRegion> {
688 unimplemented!()
689 }
690 }
691
692 pub(crate) fn with_mock_render_interface(f: impl FnOnce(&MockFactory)) {
694 let scope = crate::FerroLocator::enter_scope();
695 let factory = Rc::new(MockFactory::default());
696 crate::FerroLocator::current_mutable()
697 .bind::<dyn crate::platform::IPlatformRenderInterface>()
698 .to_constant(factory.clone());
699 f(&factory);
700 scope.dispose();
701 }
702
703 #[repr(C)]
704 struct TestGeometry {
705 base: Geometry,
706 }
707
708 ferro_class!(TestGeometry: Geometry);
709 ferro_impl_classes!(TestGeometry: FerroObjectImpl);
710
711 impl GeometryImpl for TestGeometry {
712 fn clone_geometry(this: &Self) -> Ref<Geometry> {
713 Self::parent_clone_geometry(this)
714 }
715
716 fn create_defining_geometry(_this: &Self) -> Option<Arc<dyn IGeometryImpl>> {
717 Some(MockGeometryImpl::create())
718 }
719 }
720
721 impl TestGeometry {
722 ferro_property!(pub fn foo_property() -> StyledProperty<bool> {
723 FerroProperty::register::<TestGeometry, _>("Foo", false)
724 });
725
726 fn new() -> Ref<Self> {
727 crate::ferro_thread_local! {
728 static DONE: Cell<bool> = const { Cell::new(false) };
729 }
730 if !DONE.replace(true) {
731 Geometry::affects_geometry(&[Self::foo_property().as_property()]);
732 }
733 instantiate(Self { base: Geometry::construct() })
734 }
735
736 fn set_foo(&self, value: bool) {
737 self.set_value(Self::foo_property(), value)
738 }
739 }
740
741 fn is_transformed(target: &TestGeometry) -> bool {
742 assert!(target.to_ref().get_type() == TestGeometry::TYPE);
743 target.platform_impl().unwrap().as_transformed_geometry().is_some()
744 }
745
746 fn track_changed(target: &Geometry) -> Rc<Cell<bool>> {
747 let raised = Rc::new(Cell::new(false));
748 let r = raised.clone();
749 target.changed(move || r.set(true));
750 raised
751 }
752
753 #[test]
754 fn changing_affects_geometry_property_causes_platform_impl_to_be_updated() {
755 let target = TestGeometry::new();
756 let platform_impl = target.platform_impl().unwrap();
757
758 target.set_foo(true);
759
760 assert!(!Arc::ptr_eq(&platform_impl, &target.platform_impl().unwrap()));
761 }
762
763 #[test]
764 fn changing_affects_geometry_property_causes_changed_to_be_raised() {
765 let target = TestGeometry::new();
766 let raised = track_changed(&target);
767
768 target.set_foo(true);
769
770 assert!(raised.get());
771 }
772
773 #[test]
774 fn setting_transform_causes_changed_to_be_raised() {
775 let target = TestGeometry::new();
776 let raised = track_changed(&target);
777
778 target.set_transform(RotateTransform::with_angle(45.0));
779
780 assert!(raised.get());
781 }
782
783 #[test]
784 fn changing_transform_causes_changed_to_be_raised() {
785 let transform = RotateTransform::with_angle(45.0);
786 let target = TestGeometry::new();
787 target.set_transform(&transform);
788 let raised = track_changed(&target);
789
790 transform.set_angle(90.0);
791
792 assert!(raised.get());
793 }
794
795 #[test]
796 fn removing_transform_causes_changed_to_be_raised() {
797 let transform = RotateTransform::with_angle(45.0);
798 let target = TestGeometry::new();
799 target.set_transform(&transform);
800 let raised = track_changed(&target);
801
802 target.set_transform(None);
803
804 assert!(raised.get());
805 }
806
807 #[test]
808 fn transform_produces_transformed_platform_impl() {
809 let target = TestGeometry::new();
810 let rotate = RotateTransform::with_angle(45.0);
811
812 assert!(!is_transformed(&target));
813
814 target.set_transform(&rotate);
815
816 assert!(is_transformed(&target));
817
818 rotate.set_angle(0.0);
819
820 assert!(!is_transformed(&target));
821 }
822
823 #[test]
824 fn geometries_create_their_platform_implementation() {
825 use crate::media::{
826 EllipseGeometry, FillRule, GeometryGroup, LineGeometry, PathGeometry, PolylineGeometry, StreamGeometry,
827 };
828 use crate::platform::IGeometryContext;
829
830 with_mock_render_interface(|factory| {
831 let ellipse = EllipseGeometry::with_rect(Rect::new(1.0, 2.0, 3.0, 4.0));
832 assert!(ellipse.platform_impl().is_some());
833 let ellipse = EllipseGeometry::new();
834 ellipse.set_center(Point::new(10.0, 10.0));
835 ellipse.set_radius_x(2.0);
836 ellipse.set_radius_y(3.0);
837 assert!(ellipse.platform_impl().is_some());
838 let clone = ellipse.clone_geometry().cast::<EllipseGeometry>().unwrap();
839 assert_eq!((Point::new(10.0, 10.0), 2.0, 3.0), (clone.center(), clone.radius_x(), clone.radius_y()));
840
841 let line = LineGeometry::with_points(Point::new(0.0, 0.0), Point::new(5.0, 5.0));
842 assert!(line.platform_impl().is_some());
843 assert!(line.clone_geometry().is::<LineGeometry>());
844
845 let polyline = PolylineGeometry::with_points_and_fill_rule(
846 [Point::new(0.0, 0.0), Point::new(1.0, 0.0), Point::new(1.0, 1.0)],
847 true,
848 FillRule::NonZero,
849 );
850 assert!(polyline.platform_impl().is_some());
851 let clone = polyline.clone_geometry().cast::<PolylineGeometry>().unwrap();
852 assert_eq!((3, true, FillRule::NonZero), (clone.points().len(), clone.is_filled(), clone.fill_rule()));
853
854 let path = PathGeometry::parse("F1 M0,0 L1,1 Q1,2 3,4 Z").unwrap();
855 assert!(path.platform_impl().is_some());
856
857 let group = GeometryGroup::new();
858 group.children().add(line.clone().upcast());
859 group.children().add(GeometryGroup::new().upcast());
860 assert!(group.platform_impl().is_some());
861 let clone = group.clone_geometry().cast::<GeometryGroup>().unwrap();
862 assert_eq!(2, clone.children().len());
863
864 let combined = CombinedGeometry::with_geometries(line.upcast(), ellipse.upcast());
865 assert!(combined.platform_impl().is_some());
866
867 let stream = StreamGeometry::new();
868 {
869 let mut context = stream.open();
870 context.begin_figure(Point::new(1.0, 1.0), true);
871 context.line_to(Point::new(2.0, 2.0), true);
872 context.end_figure(false);
873 }
874 assert!(stream.clone_geometry().is::<StreamGeometry>());
875 let parsed = StreamGeometry::parse("M1,1 L2,2").unwrap();
876 assert!(parsed.platform_impl().is_some());
877 assert!(StreamGeometry::parse("M1,1 X").is_err());
878
879 assert_eq!(
880 vec![
881 "ellipse 1, 2, 3, 4",
882 "ellipse 8, 7, 4, 6",
883 "line 0, 0 5, 5",
884 "stream",
885 "stream",
886 "group EvenOdd 1",
887 "combined Union",
888 "stream",
889 "stream",
890 "stream",
891 ],
892 *factory.created.borrow()
893 );
894 let streams = factory.streams.borrow();
895 assert_eq!(
896 vec!["fill NonZero", "begin 0, 0 true", "line 1, 0 true", "line 1, 1 true", "end true", "dispose"],
897 *streams[0].lock().unwrap()
898 );
899 assert_eq!(
900 vec![
901 "fill NonZero",
902 "begin 0, 0 true",
903 "line 1, 1 true",
904 "quad 1, 2 3, 4 true",
905 "end true",
906 "dispose"
907 ],
908 *streams[1].lock().unwrap()
909 );
910 assert_eq!(vec!["begin 1, 1 true", "line 2, 2 true", "end false", "dispose"], *streams[2].lock().unwrap());
911 assert_eq!(vec!["begin 1, 1 true", "line 2, 2 true", "end false"], *streams[3].lock().unwrap());
913 });
914 }
915
916 #[test]
917 fn replaced_transform_no_longer_raises_changed() {
918 let transform = RotateTransform::with_angle(45.0);
919 let target = TestGeometry::new();
920 target.set_transform(&transform);
921 target.set_transform(None);
922 let raised = track_changed(&target);
923
924 transform.set_angle(90.0);
925
926 assert!(!raised.get());
927 }
928}