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