1use crate::collections::NotifyCollectionChangedAction;
2use crate::media::effects::{EffectExtensions, IEffect};
3use crate::media::{
4 BoxShadows, Drawing, DrawingCollection, DrawingContext, DrawingImpl, Geometry, GeometryDrawing, GlyphRun,
5 GlyphRunDrawing, IBrush, IDrawingContextCore, IPen, MatrixTransform, PlatformGeometry, PushedState,
6 RectangleGeometry, RenderOptions, TextOptions, Transform,
7};
8use crate::platform::{self, IGeometryImpl, IPlatformRenderInterface};
9use crate::reactive::IDisposable;
10use crate::rendering::scene_graph::ICustomDrawOperation;
11use crate::{
12 ferro_class, ferro_property, instantiate, DirectProperty, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
13 Matrix, Nullable, Point, Rect, Ref, RoundedRect, StyledProperty,
14};
15use std::cell::{Cell, RefCell};
16use std::rc::Rc;
17use std::sync::Arc;
18
19type ChildSubscription = (Ref<Drawing>, Rc<dyn IDisposable>);
20
21#[repr(C)]
24pub struct DrawingGroup {
25 base: Drawing,
26 children: RefCell<DrawingCollection>,
27 children_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
28 child_subscriptions: RefCell<Vec<ChildSubscription>>,
29 render_options: Cell<Option<RenderOptions>>,
30 text_options: Cell<Option<TextOptions>>,
31 effect_bounds: Cell<Option<Rect>>,
32}
33
34ferro_class!(DrawingGroup: Drawing);
35crate::ferro_class_info!(DrawingGroup { new: DrawingGroup::new });
36
37impl FerroObjectImpl for DrawingGroup {
38 fn constructed(this: &Self) {
39 Self::parent_constructed(this);
40 let children = this.children();
41 this.subscribe_collection(&children);
42 }
43}
44
45impl DrawingImpl for DrawingGroup {
46 fn draw_core(this: &Self, context: &mut DrawingContext) {
47 let children = this.children();
51 let content_bounds = match this.effect_bounds() {
52 Some(bounds) => bounds,
53 None => {
54 let mut bounds = Rect::default();
55 for drawing in children.iter() {
56 bounds = bounds.union(drawing.get_bounds());
57 }
58 bounds
59 }
60 };
61
62 let effect = this.effect();
63 let effect_bounds = match &effect {
64 Some(effect) => {
65 content_bounds.inflate_thickness(EffectExtensions::get_effect_output_padding(Some(&**effect)))
66 }
67 None => content_bounds,
68 };
69
70 let s_transform =
71 context.push_transform(this.transform().map(|transform| transform.value()).unwrap_or(Matrix::IDENTITY));
72 let s_opacity = context.push_opacity(this.opacity());
73 let s_clip = match this.clip_geometry() {
74 Some(clip) => context.push_geometry_clip(&clip),
75 None => PushedState::NONE,
76 };
77 let s_opacity_mask = match this.opacity_mask() {
78 Some(mask) => context.push_opacity_mask(&mask, effect_bounds),
79 None => PushedState::NONE,
80 };
81 let s_render_options = match this.render_options() {
82 Some(render_options) => context.push_render_options(render_options),
83 None => PushedState::NONE,
84 };
85 let s_text_options = match this.text_options() {
86 Some(text_options) => context.push_text_options(text_options),
87 None => PushedState::NONE,
88 };
89 let s_effect = match &effect {
90 Some(effect) => context.push_effect(effect, content_bounds),
91 None => PushedState::NONE,
92 };
93
94 for drawing in children.iter() {
95 drawing.draw(context);
96 }
97
98 context.pop(s_effect);
99 context.pop(s_text_options);
100 context.pop(s_render_options);
101 context.pop(s_opacity_mask);
102 context.pop(s_clip);
103 context.pop(s_opacity);
104 context.pop(s_transform);
105 }
106
107 fn get_bounds(this: &Self) -> Rect {
108 let mut rect = Rect::default();
109
110 for drawing in this.children().iter() {
111 rect = rect.union(drawing.get_bounds());
112 }
113
114 if let Some(transform) = this.transform() {
119 rect = rect.transform_to_aabb(transform.value());
120 }
121
122 rect
123 }
124}
125
126crate::ferro_properties! { impl DrawingGroup {
127 ferro_property!(
128 pub fn opacity_property() -> StyledProperty<f64> {
130 FerroProperty::register::<DrawingGroup, _>("Opacity", 1.0)
131 }
132 );
133
134 ferro_property!(
135 pub fn transform_property() -> StyledProperty<Option<Ref<Transform>>> {
137 FerroProperty::register::<DrawingGroup, _>("Transform", None)
138 }
139 );
140
141 ferro_property!(
142 pub fn clip_geometry_property() -> StyledProperty<Option<Ref<Geometry>>> {
144 FerroProperty::register::<DrawingGroup, _>("ClipGeometry", None)
145 }
146 );
147
148 ferro_property!(
149 pub fn opacity_mask_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
151 FerroProperty::register::<DrawingGroup, _>("OpacityMask", None)
152 }
153 );
154
155 ferro_property!(
156 pub fn effect_property() -> StyledProperty<Option<Rc<dyn IEffect>>> {
158 FerroProperty::register::<DrawingGroup, _>("Effect", None)
159 }
160 );
161
162 ferro_property!(
163 pub fn children_property() -> DirectProperty<DrawingGroup, DrawingCollection> {
165 FerroProperty::register_direct::<DrawingGroup, _>(
166 "Children",
167 |o| o.children(),
168 Some(|o, v| o.set_children(v)),
169 DrawingCollection::new(),
170 )
171 }
172 );
173} }
174
175impl DrawingGroup {
176 pub fn construct() -> Self {
178 Self {
179 base: Drawing::construct(),
180 children: RefCell::new(DrawingCollection::new()),
181 children_subscription: RefCell::new(None),
182 child_subscriptions: RefCell::new(Vec::new()),
183 render_options: Cell::new(None),
184 text_options: Cell::new(None),
185 effect_bounds: Cell::new(None),
186 }
187 }
188
189 pub fn new() -> Ref<Self> {
190 instantiate(Self::construct())
191 }
192
193 pub fn opacity(&self) -> f64 {
195 self.get_value(Self::opacity_property())
196 }
197
198 pub fn set_opacity(&self, value: f64) {
199 self.set_value(Self::opacity_property(), value)
200 }
201
202 pub fn transform(&self) -> Option<Ref<Transform>> {
204 self.get_value(Self::transform_property())
205 }
206
207 pub fn set_transform(&self, value: impl Into<Nullable<Transform>>) {
208 self.set_value(Self::transform_property(), value.into().0)
209 }
210
211 pub fn clip_geometry(&self) -> Option<Ref<Geometry>> {
213 self.get_value(Self::clip_geometry_property())
214 }
215
216 pub fn set_clip_geometry(&self, value: impl Into<Nullable<Geometry>>) {
217 self.set_value(Self::clip_geometry_property(), value.into().0)
218 }
219
220 pub fn opacity_mask(&self) -> Option<Rc<dyn IBrush>> {
222 self.get_value(Self::opacity_mask_property())
223 }
224
225 pub fn set_opacity_mask(&self, value: Option<Rc<dyn IBrush>>) {
226 self.set_value(Self::opacity_mask_property(), value)
227 }
228
229 pub fn effect(&self) -> Option<Rc<dyn IEffect>> {
231 self.get_value(Self::effect_property())
232 }
233
234 pub fn set_effect(&self, value: Option<Rc<dyn IEffect>>) {
235 self.set_value(Self::effect_property(), value)
236 }
237
238 pub(crate) fn render_options(&self) -> Option<RenderOptions> {
241 self.render_options.get()
242 }
243
244 pub(crate) fn set_render_options(&self, value: Option<RenderOptions>) {
245 self.render_options.set(value)
246 }
247
248 pub(crate) fn text_options(&self) -> Option<TextOptions> {
251 self.text_options.get()
252 }
253
254 pub(crate) fn set_text_options(&self, value: Option<TextOptions>) {
255 self.text_options.set(value)
256 }
257
258 pub(crate) fn effect_bounds(&self) -> Option<Rect> {
262 self.effect_bounds.get()
263 }
264
265 pub(crate) fn set_effect_bounds(&self, value: Option<Rect>) {
266 self.effect_bounds.set(value)
267 }
268
269 pub fn children(&self) -> DrawingCollection {
271 self.children.borrow().clone()
272 }
273
274 pub fn set_children(&self, value: DrawingCollection) {
275 if self.children.borrow().ptr_eq(&value) {
276 return;
277 }
278
279 if let Some(subscription) = self.children_subscription.take() {
280 subscription.dispose();
281 }
282 let old_subscriptions = std::mem::take(&mut *self.child_subscriptions.borrow_mut());
283 for (_, subscription) in old_subscriptions {
284 subscription.dispose();
285 }
286
287 self.set_and_raise(Self::children_property(), &self.children, value.clone());
288
289 self.subscribe_collection(&value);
290 for child in value.iter() {
291 self.subscribe_child(&child);
292 }
293 }
294
295 pub fn open(&self) -> DrawingContext<'static> {
301 DrawingContext::owned(Box::new(DrawingGroupDrawingContext::new(self.to_ref())))
302 }
303
304 fn subscribe_collection(&self, children: &DrawingCollection) {
305 let weak = self.to_ref().downgrade();
306 let subscription = children.collection_changed(move |e| {
307 let Some(this) = weak.upgrade() else { return };
308
309 if e.action == NotifyCollectionChangedAction::Reset {
310 panic!("Collection reset is not supported.");
311 }
312
313 for child in e.old_items {
314 this.unsubscribe_child(child);
315 }
316
317 for child in e.new_items {
318 this.subscribe_child(child);
319 }
320
321 this.raise_invalidated();
322 });
323 *self.children_subscription.borrow_mut() = Some(subscription);
324 }
325
326 fn subscribe_child(&self, child: &Ref<Drawing>) {
327 let weak = self.to_ref().downgrade();
328 let subscription = child.invalidated(move || {
329 if let Some(this) = weak.upgrade() {
330 this.raise_invalidated();
331 }
332 });
333 self.child_subscriptions.borrow_mut().push((child.clone(), subscription));
334 }
335
336 fn unsubscribe_child(&self, child: &Ref<Drawing>) {
337 let position = self.child_subscriptions.borrow().iter().position(|(c, _)| c.ptr_eq(child));
338 if let Some(position) = position {
339 let (_, subscription) = self.child_subscriptions.borrow_mut().remove(position);
340 subscription.dispose();
341 }
342 }
343}
344
345struct DrawingGroupDrawingContext {
348 drawing_group: Ref<DrawingGroup>,
349 platform_render_interface: Rc<dyn IPlatformRenderInterface>,
350
351 disposed: bool,
352
353 root_drawing: Option<Ref<Drawing>>,
363
364 current_drawing_group: Option<Ref<DrawingGroup>>,
366
367 previous_drawing_group_stack: Option<Vec<Option<Ref<DrawingGroup>>>>,
369}
370
371impl DrawingGroupDrawingContext {
372 fn new(drawing_group: Ref<DrawingGroup>) -> Self {
373 Self {
374 drawing_group,
375 platform_render_interface: platform::render_interface(),
376 disposed: false,
377 root_drawing: None,
378 current_drawing_group: None,
379 previous_drawing_group_stack: None,
380 }
381 }
382
383 fn pop(&mut self) {
384 let Some(previous) = self.previous_drawing_group_stack.as_mut().and_then(Vec::pop) else {
386 panic!("DrawingGroupStack count missmatch.");
387 };
388
389 self.current_drawing_group = previous;
391 }
392
393 fn push_new_drawing_group(&mut self) -> Ref<DrawingGroup> {
394 let drawing_group = DrawingGroup::new();
396
397 self.add_drawing(drawing_group.clone().upcast());
399
400 let previous = self.current_drawing_group.take();
411 self.previous_drawing_group_stack.get_or_insert_with(|| Vec::with_capacity(2)).push(previous);
412
413 self.current_drawing_group = Some(drawing_group.clone());
416
417 drawing_group
418 }
419
420 fn add_new_geometry_drawing(
421 &mut self,
422 brush: Option<&Rc<dyn IBrush>>,
423 pen: Option<&Rc<dyn IPen>>,
424 geometry: Ref<Geometry>,
425 ) {
426 let geometry_drawing = GeometryDrawing::new();
428 geometry_drawing.set_brush(brush.cloned());
429 geometry_drawing.set_pen(pen.cloned());
430 geometry_drawing.set_geometry(geometry);
431
432 self.add_drawing(geometry_drawing.upcast());
434 }
435
436 fn add_drawing(&mut self, new_drawing: Ref<Drawing>) {
437 match (&self.root_drawing, &self.current_drawing_group) {
438 (None, Some(_)) => {
439 panic!("When a DrawingGroup is set, it should be made the root if a root drawing didnt exist.");
440 }
441 (None, None) => {
442 self.root_drawing = Some(new_drawing);
447 }
448 (Some(root_drawing), None) => {
449 let current_drawing_group = DrawingGroup::new();
452
453 let children = current_drawing_group.children();
455 children.add(root_drawing.clone());
456 children.add(new_drawing);
457
458 self.root_drawing = Some(current_drawing_group.clone().upcast());
460 self.current_drawing_group = Some(current_drawing_group);
461 }
462 (Some(_), Some(current_drawing_group)) => {
463 current_drawing_group.children().add(new_drawing);
466 }
467 }
468 }
469}
470
471impl IDrawingContextCore for DrawingGroupDrawingContext {
472 fn draw_line_core(&mut self, pen: &Rc<dyn IPen>, p1: Point, p2: Point) {
473 let geometry = self.platform_render_interface.create_line_geometry(p1, p2);
475
476 self.add_new_geometry_drawing(None, Some(pen), PlatformGeometry::new(geometry).upcast());
478 }
479
480 fn draw_geometry_impl_core(
481 &mut self,
482 brush: Option<&Rc<dyn IBrush>>,
483 pen: Option<&Rc<dyn IPen>>,
484 geometry: &Arc<dyn IGeometryImpl>,
485 ) {
486 if brush.is_none() && pen.is_none() {
487 return;
488 }
489
490 self.add_new_geometry_drawing(brush, pen, PlatformGeometry::new(geometry.clone()).upcast());
491 }
492
493 fn draw_rectangle_core(
494 &mut self,
495 brush: Option<&Rc<dyn IBrush>>,
496 pen: Option<&Rc<dyn IPen>>,
497 rrect: RoundedRect,
498 _box_shadows: &BoxShadows,
499 ) {
500 let geometry = self.platform_render_interface.create_rectangle_geometry(rrect.rect);
502
503 self.add_new_geometry_drawing(brush, pen, PlatformGeometry::new(geometry).upcast());
505 }
506
507 fn draw_ellipse_core(&mut self, brush: Option<&Rc<dyn IBrush>>, pen: Option<&Rc<dyn IPen>>, rect: Rect) {
508 if brush.is_none() && pen.is_none() {
509 return;
510 }
511
512 let geometry = self.platform_render_interface.create_ellipse_geometry(rect);
514
515 self.add_new_geometry_drawing(brush, pen, PlatformGeometry::new(geometry).upcast());
517 }
518
519 fn draw_bitmap(&mut self, _source: &crate::utilities::RefCounted<crate::platform::SharedBitmapImpl>, _opacity: f64, _source_rect: Rect, _dest_rect: Rect) {
520 panic!("Drawing a bitmap into a DrawingGroup is not implemented.");
521 }
522
523 fn custom(&mut self, _custom: &std::sync::Arc<dyn ICustomDrawOperation>) {
524 panic!("Custom draw operations are not supported when drawing into a DrawingGroup.");
525 }
526
527 fn draw_glyph_run(&mut self, foreground: Option<&Rc<dyn IBrush>>, glyph_run: &Rc<GlyphRun>) {
528 let Some(foreground) = foreground else {
529 return;
530 };
531
532 let glyph_run_drawing = GlyphRunDrawing::new();
533 glyph_run_drawing.set_foreground(Some(foreground.clone()));
534 glyph_run_drawing.set_glyph_run(Some(glyph_run.clone()));
535
536 self.add_drawing(glyph_run_drawing.upcast());
538 }
539
540 fn push_clip_core(&mut self, rect: Rect) {
541 let drawing_group = self.push_new_drawing_group();
542
543 drawing_group.set_clip_geometry(RectangleGeometry::with_rect(rect).upcast::<Geometry>());
544 }
545
546 fn push_rounded_clip_core(&mut self, _rect: RoundedRect) {
547 panic!("Pushing a rounded clip into a DrawingGroup is not implemented.");
548 }
549
550 fn push_geometry_clip_core(&mut self, clip: &Ref<Geometry>) {
551 let drawing_group = self.push_new_drawing_group();
552
553 drawing_group.set_clip_geometry(clip.clone());
554 }
555
556 fn push_opacity_core(&mut self, opacity: f64) {
557 let drawing_group = self.push_new_drawing_group();
558
559 drawing_group.set_opacity(opacity);
560 }
561
562 fn push_opacity_mask_core(&mut self, mask: &Rc<dyn IBrush>, _bounds: Rect) {
563 let drawing_group = self.push_new_drawing_group();
564
565 drawing_group.set_opacity_mask(Some(mask.clone()));
566 }
567
568 fn push_transform_core(&mut self, matrix: Matrix) {
569 let drawing_group = self.push_new_drawing_group();
571
572 drawing_group.set_transform(MatrixTransform::with_matrix(matrix).upcast::<Transform>());
574 }
575
576 fn push_render_options_core(&mut self, render_options: RenderOptions) {
577 let drawing_group = self.push_new_drawing_group();
579
580 drawing_group.set_render_options(Some(render_options));
582 }
583
584 fn push_text_options_core(&mut self, text_options: TextOptions) {
585 let drawing_group = self.push_new_drawing_group();
587
588 drawing_group.set_text_options(Some(text_options));
590 }
591
592 fn push_effect_core(&mut self, effect: &Rc<dyn IEffect>, bounds: Rect) {
593 let drawing_group = self.push_new_drawing_group();
595
596 drawing_group.set_effect(Some(effect.clone()));
598 drawing_group.set_effect_bounds(Some(bounds));
599 }
600
601 fn pop_clip_core(&mut self) {
602 self.pop();
603 }
604
605 fn pop_geometry_clip_core(&mut self) {
606 self.pop();
607 }
608
609 fn pop_opacity_core(&mut self) {
610 self.pop();
611 }
612
613 fn pop_opacity_mask_core(&mut self) {
614 self.pop();
615 }
616
617 fn pop_transform_core(&mut self) {
618 self.pop();
619 }
620
621 fn pop_render_options_core(&mut self) {
622 self.pop();
623 }
624
625 fn pop_text_options_core(&mut self) {
626 self.pop();
627 }
628
629 fn pop_effect_core(&mut self) {
630 self.pop();
631 }
632
633 fn dispose_core(&mut self) {
634 if self.disposed {
636 return;
637 }
638
639 let stack_count = self.previous_drawing_group_stack.as_ref().map_or(0, Vec::len);
641 for _ in 0..stack_count {
642 self.pop();
643 }
644
645 let root_children = match &self.current_drawing_group {
647 Some(current_drawing_group) => current_drawing_group.children(),
651 None => {
656 let root_children = DrawingCollection::new();
657 if let Some(root_drawing) = &self.root_drawing {
658 root_children.add(root_drawing.clone());
659 }
660 root_children
661 }
662 };
663
664 self.drawing_group.set_children(root_children);
665
666 self.disposed = true;
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
675 use crate::media::effects::BlurEffect;
676 use crate::media::{Brushes, DrawingImage, GeometryDrawing, IImage, ImageDrawing, TranslateTransform};
677 use crate::{PixelRect, Size};
678
679 fn counter(drawing: &Drawing) -> Rc<Cell<i32>> {
680 let count = Rc::new(Cell::new(0));
681 let c = count.clone();
682 drawing.invalidated(move || c.set(c.get() + 1));
683 count
684 }
685
686 #[test]
687 fn children_changes_and_child_changes_raise_invalidated() {
688 let group = DrawingGroup::new();
689 let count = counter(&group);
690
691 let child = ImageDrawing::new();
692 group.children().add(child.clone().upcast());
693 assert_eq!(1, count.get());
694
695 child.set_rect(Rect::new(1.0, 2.0, 3.0, 4.0));
696 assert_eq!(2, count.get());
697 assert_eq!(Rect::new(1.0, 2.0, 3.0, 4.0), group.get_bounds());
698
699 group.children().remove_at(0);
700 assert_eq!(3, count.get());
701 child.set_rect(Rect::default());
702 assert_eq!(3, count.get());
703 }
704
705 #[test]
706 fn replaced_children_collection_is_tracked() {
707 let group = DrawingGroup::new();
708 let old = group.children();
709 let child = ImageDrawing::new();
710 let replacement = DrawingCollection::from_items([child.clone().upcast()]);
711 let count = counter(&group);
712
713 group.set_children(replacement.clone());
714 assert_eq!(1, count.get());
715 assert!(group.get_direct_value(DrawingGroup::children_property()).ptr_eq(&replacement));
716
717 child.set_rect(Rect::new(0.0, 0.0, 5.0, 5.0));
718 assert_eq!(2, count.get());
719
720 old.add(ImageDrawing::new().upcast());
721 assert_eq!(2, count.get());
722
723 group.set_children(replacement);
724 assert_eq!(2, count.get());
725 }
726
727 #[test]
728 #[should_panic(expected = "Collection reset is not supported.")]
729 fn resetting_the_children_is_not_supported() {
730 let group = DrawingGroup::new();
731 let children = group.children();
732 children.set_reset_behavior(crate::media::ResetBehavior::Reset);
733 children.add(ImageDrawing::new().upcast());
734 children.clear();
735 }
736
737 #[test]
738 fn nested_value_changes_raise_invalidated() {
739 let group = DrawingGroup::new();
740 let transform = TranslateTransform::new();
741 let effect = BlurEffect::new();
742 group.set_transform(transform.clone().upcast::<Transform>());
743 group.set_effect(Some(effect.clone().into()));
744 let count = counter(&group);
745
746 transform.set_x(10.0);
747 assert_eq!(1, count.get());
748 effect.set_radius(1.0);
749 assert_eq!(2, count.get());
750
751 group.set_transform(None);
752 group.set_effect(None);
753 assert_eq!(4, count.get());
754 transform.set_x(20.0);
755 effect.set_radius(2.0);
756 assert_eq!(4, count.get());
757
758 let drawing = GeometryDrawing::new();
761 let count = counter(&drawing);
762 let brush = crate::media::SolidColorBrush::new();
763 drawing.set_brush(Some(brush.clone().into()));
764 assert_eq!(1, count.get());
765 brush.set_opacity(0.5);
766 assert_eq!(1, count.get());
767 drawing.set_brush(Some(Brushes::red() as Rc<dyn IBrush>));
768 assert_eq!(2, count.get());
769 }
770
771 #[test]
772 fn bounds_apply_the_transform() {
773 let group = DrawingGroup::new();
774 let a = ImageDrawing::new();
775 a.set_rect(Rect::new(0.0, 0.0, 10.0, 10.0));
776 let b = ImageDrawing::new();
777 b.set_rect(Rect::new(20.0, 20.0, 10.0, 10.0));
778 group.children().add_range([a.upcast(), b.upcast()]);
779 assert_eq!(Rect::new(0.0, 0.0, 30.0, 30.0), group.get_bounds());
780
781 let transform = TranslateTransform::new();
782 transform.set_x(5.0);
783 transform.set_y(7.0);
784 group.set_transform(transform.upcast::<Transform>());
785 assert_eq!(Rect::new(5.0, 7.0, 30.0, 30.0), group.get_bounds());
786 assert_eq!(None, group.effect_bounds());
787 assert_eq!(None, group.render_options());
788 }
789
790 #[test]
791 fn drawing_image_follows_its_drawing() {
792 let drawing = ImageDrawing::new();
793 drawing.set_rect(Rect::new(0.0, 0.0, 16.0, 8.0));
794 let image = DrawingImage::with_drawing(drawing.clone().upcast::<Drawing>());
795 assert_eq!(Size::new(16.0, 8.0), image.size());
796
797 let count = Rc::new(Cell::new(0));
798 let c = count.clone();
799 image.invalidated(move || c.set(c.get() + 1));
800
801 drawing.set_rect(Rect::new(0.0, 0.0, 4.0, 4.0));
802 assert_eq!(1, count.get());
803 assert_eq!(Size::new(4.0, 4.0), image.size());
804
805 image.set_viewbox(Some(Rect::new(1.0, 1.0, 2.0, 3.0)));
806 assert_eq!(2, count.get());
807 assert_eq!(Size::new(2.0, 3.0), image.size());
808
809 let outer = ImageDrawing::new();
811 let handle: Rc<dyn IImage> = image.clone().into();
812 outer.set_image_source(Some(handle.clone()));
813 let outer_count = counter(&outer);
814 image.set_viewbox(None);
815 assert_eq!(1, outer_count.get());
816 assert!(handle.as_bitmap().is_none());
817 assert!(handle.as_object().unwrap().is::<DrawingImage>());
818
819 image.set_drawing(None);
820 let before = count.get();
821 drawing.set_rect(Rect::default());
822 assert_eq!(before, count.get());
823 assert_eq!(Size::default(), image.size());
824 let _ = PixelRect::default();
825 }
826
827 use crate::media::immutable::ImmutableSolidColorBrush;
830 use crate::media::{Colors, PlatformDrawingContext};
831 use crate::rendering::testing::{DrawingLog, MockDrawingContextImpl, MockPlatformRenderInterface};
832
833 fn solid(color: crate::media::Color) -> Rc<dyn IBrush> {
834 Rc::new(ImmutableSolidColorBrush::new(color))
835 }
836
837 fn blur(radius: f64) -> Rc<dyn IEffect> {
838 let effect = BlurEffect::new();
839 effect.set_radius(radius);
840 effect.into()
841 }
842
843 fn rectangle_drawing(brush: Rc<dyn IBrush>, rect: Rect) -> Ref<Drawing> {
844 let drawing = GeometryDrawing::new();
845 drawing.set_brush(Some(brush));
846 drawing.set_geometry(RectangleGeometry::with_rect(rect).upcast::<Geometry>());
847 drawing.upcast()
848 }
849
850 #[derive(Default)]
853 struct MockDrawingContext {
854 effect: Option<Rc<dyn IEffect>>,
855 bounds: Rect,
856 opacity_mask_bounds: Rect,
857 }
858
859 impl IDrawingContextCore for MockDrawingContext {
860 fn draw_line_core(&mut self, _: &Rc<dyn IPen>, _: Point, _: Point) {}
861 fn draw_geometry_impl_core(&mut self, _: Option<&Rc<dyn IBrush>>, _: Option<&Rc<dyn IPen>>, _: &Arc<dyn IGeometryImpl>) {
862 }
863 fn draw_rectangle_core(&mut self, _: Option<&Rc<dyn IBrush>>, _: Option<&Rc<dyn IPen>>, _: RoundedRect, _: &BoxShadows) {
864 }
865 fn draw_ellipse_core(&mut self, _: Option<&Rc<dyn IBrush>>, _: Option<&Rc<dyn IPen>>, _: Rect) {}
866 fn draw_bitmap(&mut self, _: &crate::utilities::RefCounted<crate::platform::SharedBitmapImpl>, _: f64, _: Rect, _: Rect) {}
867 fn custom(&mut self, _: &std::sync::Arc<dyn ICustomDrawOperation>) {}
868 fn draw_glyph_run(&mut self, _: Option<&Rc<dyn IBrush>>, _: &Rc<GlyphRun>) {}
869 fn push_clip_core(&mut self, _: Rect) {}
870 fn push_rounded_clip_core(&mut self, _: RoundedRect) {}
871 fn push_geometry_clip_core(&mut self, _: &Ref<Geometry>) {}
872 fn push_opacity_core(&mut self, _: f64) {}
873 fn push_opacity_mask_core(&mut self, _: &Rc<dyn IBrush>, bounds: Rect) {
874 self.opacity_mask_bounds = bounds;
875 }
876 fn push_transform_core(&mut self, _: Matrix) {}
877 fn push_render_options_core(&mut self, _: RenderOptions) {}
878 fn push_text_options_core(&mut self, _: TextOptions) {}
879 fn push_effect_core(&mut self, effect: &Rc<dyn IEffect>, bounds: Rect) {
880 self.effect = Some(effect.clone());
881 self.bounds = bounds;
882 }
883 fn pop_clip_core(&mut self) {}
884 fn pop_geometry_clip_core(&mut self) {}
885 fn pop_opacity_core(&mut self) {}
886 fn pop_opacity_mask_core(&mut self) {}
887 fn pop_transform_core(&mut self) {}
888 fn pop_render_options_core(&mut self) {}
889 fn pop_text_options_core(&mut self) {}
890 fn pop_effect_core(&mut self) {}
891 fn dispose_core(&mut self) {}
892 }
893
894 #[test]
895 fn push_effect_should_store_provided_bounds() {
896 let (scope, _) = MockPlatformRenderInterface::install();
897 let group = DrawingGroup::new();
898 let effect = blur(10.0);
899 let bounds = Rect::new(10.0, 10.0, 100.0, 100.0);
900
901 {
902 let mut context = group.open();
903 let state = context.push_effect(&effect, bounds);
904 context.draw_rectangle(
905 Some(&solid(Colors::RED)),
906 None,
907 Rect::new(20.0, 20.0, 50.0, 50.0),
908 0.0,
909 0.0,
910 &BoxShadows::default(),
911 );
912 context.pop(state);
913 }
914
915 assert_eq!(1, group.children().len());
918 let child_group = group.children().get(0).cast::<DrawingGroup>().expect("a drawing group");
919 assert!(child_group.effect() == Some(effect));
920 assert_eq!(Some(bounds), child_group.effect_bounds());
921 scope.dispose();
922 }
923
924 #[test]
925 fn drawing_with_effect_should_use_stored_bounds() {
926 let (scope, _) = MockPlatformRenderInterface::install();
927 let effect = blur(10.0);
928 let bounds = Rect::new(10.0, 10.0, 100.0, 100.0);
929 let group = DrawingGroup::new();
930 group.set_effect(Some(effect.clone()));
931 group.set_effect_bounds(Some(bounds));
932 group.children().add(rectangle_drawing(solid(Colors::RED), Rect::new(20.0, 20.0, 50.0, 50.0)));
933
934 let mut mock_context = MockDrawingContext::default();
935 {
936 let mut context = DrawingContext::new(&mut mock_context);
937 group.draw(&mut context);
938 }
939
940 assert!(mock_context.effect == Some(effect));
941 assert_eq!(bounds, mock_context.bounds);
942 scope.dispose();
943 }
944
945 #[test]
949 fn platform_drawing_context_push_effect_should_inflate_bounds_for_platform_api() {
950 let log = DrawingLog::new();
951 let mut effect_impl = MockDrawingContextImpl::new(log.clone());
952 effect_impl.supports_effects = true;
953 let mut core = PlatformDrawingContext::borrowed(&mut effect_impl);
954 let mut context = DrawingContext::new(&mut core);
955
956 let effect = blur(10.0);
957 let content_bounds = Rect::new(10.0, 10.0, 100.0, 100.0);
958 let expected_inflated_bounds =
959 content_bounds.inflate_thickness(EffectExtensions::get_effect_output_padding(Some(&*effect)));
960
961 let state = context.push_effect(&effect, content_bounds);
962 context.pop(state);
963 context.dispose();
964
965 assert_eq!(1, log.count("PushEffect"));
968 assert_eq!(log.entries()[0], format!("PushEffect {expected_inflated_bounds}"));
969 }
970
971 #[test]
975 fn draw_core_with_effect_and_opacity_mask_should_use_effect_bounds_for_opacity_mask() {
976 let (scope, _) = MockPlatformRenderInterface::install();
977 let effect = blur(10.0);
978 let content_bounds = Rect::new(10.0, 10.0, 100.0, 100.0);
979 let expected_effect_bounds =
980 content_bounds.inflate_thickness(EffectExtensions::get_effect_output_padding(Some(&*effect)));
981
982 let group = DrawingGroup::new();
983 group.set_effect(Some(effect));
984 group.set_effect_bounds(Some(content_bounds));
985 group.set_opacity_mask(Some(solid(Colors::RED)));
986 group.children().add(rectangle_drawing(solid(Colors::BLUE), Rect::new(20.0, 20.0, 50.0, 50.0)));
987
988 let mut mock_context = MockDrawingContext::default();
989 {
990 let mut context = DrawingContext::new(&mut mock_context);
991 group.draw(&mut context);
992 }
993
994 assert_eq!(expected_effect_bounds, mock_context.opacity_mask_bounds);
995 scope.dispose();
996 }
997
998 #[test]
999 fn invalidated_is_raised_when_child_is_added_or_removed() {
1000 let group = DrawingGroup::new();
1001 let child: Ref<Drawing> = GeometryDrawing::new().upcast();
1002 let count = counter(&group);
1003
1004 group.children().add(child.clone());
1005 assert_eq!(1, count.get());
1006
1007 group.children().remove(&child);
1008 assert_eq!(2, count.get());
1009 }
1010
1011 #[test]
1012 fn invalidated_is_raised_when_child_changes() {
1013 let child = GeometryDrawing::new();
1014 let group = DrawingGroup::new();
1015 group.children().add(child.clone().upcast());
1016
1017 let count = counter(&group);
1018
1019 child.set_brush(Some(Brushes::red()));
1020 assert!(count.get() > 0);
1021 }
1022
1023 #[test]
1024 fn removed_child_is_no_longer_tracked() {
1025 let child = GeometryDrawing::new();
1026 let group = DrawingGroup::new();
1027 let handle: Ref<Drawing> = child.clone().upcast();
1028 group.children().add(handle.clone());
1029 group.children().remove(&handle);
1030
1031 let count = counter(&group);
1032
1033 child.set_brush(Some(Brushes::red()));
1034 assert_eq!(0, count.get());
1035 }
1036
1037 fn replay(drawing: &Drawing) -> Vec<String> {
1040 let log = DrawingLog::new();
1041 let mut platform_impl = MockDrawingContextImpl::new(log.clone());
1042 platform_impl.log_transforms = false;
1043 platform_impl.supports_effects = true;
1044 {
1045 let mut core = PlatformDrawingContext::borrowed(&mut platform_impl);
1046 let mut context = DrawingContext::new(&mut core);
1047 drawing.draw(&mut context);
1048 }
1049 log.entries()
1050 }
1051
1052 #[test]
1053 fn open_with_a_single_drawing_makes_it_the_only_child() {
1054 let (scope, _) = MockPlatformRenderInterface::install();
1055 let group = DrawingGroup::new();
1056 let old_children = group.children();
1057 let count = counter(&group);
1058 {
1059 let mut context = group.open();
1060 context.draw_ellipse(Some(&solid(Colors::RED)), None, Rect::new(1.0, 2.0, 3.0, 4.0));
1061 assert_eq!(0, group.children().len());
1063 }
1064 assert!(!group.children().ptr_eq(&old_children));
1065 assert_eq!(1, group.children().len());
1066 assert_eq!(1, count.get());
1067 let child = group.children().get(0).cast::<GeometryDrawing>().expect("a geometry drawing");
1068 assert!(child.pen().is_none());
1069 assert_eq!(Rect::new(1.0, 2.0, 3.0, 4.0), group.get_bounds());
1070
1071 group.open().dispose();
1073 assert_eq!(0, group.children().len());
1074 scope.dispose();
1075 }
1076
1077 #[test]
1078 fn open_records_states_as_nested_groups_and_draws_back_the_same_content() {
1079 let (scope, _) = MockPlatformRenderInterface::install();
1080 let group = DrawingGroup::new();
1081 let pen: Rc<dyn IPen> = Rc::new(crate::media::immutable::ImmutablePen::with_brush(
1082 Some(Rc::new(ImmutableSolidColorBrush::new(Colors::BLUE))),
1083 2.0,
1084 ));
1085 {
1086 let mut context = group.open();
1087 context.draw_line(&pen, Point::new(0.0, 0.0), Point::new(10.0, 10.0));
1088 let transform = context.push_transform(Matrix::create_translation(5.0, 5.0));
1089 let opacity = context.push_opacity(0.5);
1090 context.draw_rectangle(
1091 Some(&solid(Colors::RED)),
1092 None,
1093 Rect::new(0.0, 0.0, 4.0, 4.0),
1094 0.0,
1095 0.0,
1096 &BoxShadows::default(),
1097 );
1098 context.pop(opacity);
1099 context.pop(transform);
1100 let clip = context.push_clip(Rect::new(0.0, 0.0, 20.0, 20.0));
1101 let options = context.push_render_options(RenderOptions::default());
1102 let geometry: Ref<Geometry> = RectangleGeometry::with_rect(Rect::new(1.0, 1.0, 2.0, 2.0)).upcast();
1103 let geometry_clip = context.push_geometry_clip(&geometry);
1104 let mask = context.push_opacity_mask(&solid(Colors::GREEN), Rect::new(0.0, 0.0, 9.0, 9.0));
1105 context.draw_geometry(Some(&solid(Colors::RED)), Some(&pen), &geometry);
1106 let _ = (clip, options, geometry_clip, mask);
1108 }
1109
1110 let children = group.children();
1112 assert_eq!(3, children.len());
1113 assert!(children.get(0).is::<GeometryDrawing>());
1114 let transform_group = children.get(1).cast::<DrawingGroup>().unwrap();
1115 assert_eq!(Matrix::create_translation(5.0, 5.0), transform_group.transform().unwrap().value());
1116 let opacity_group = transform_group.children().get(0).cast::<DrawingGroup>().unwrap();
1117 assert_eq!(0.5, opacity_group.opacity());
1118 assert!(opacity_group.children().get(0).is::<GeometryDrawing>());
1119 let clip_group = children.get(2).cast::<DrawingGroup>().unwrap();
1120 assert_eq!(Rect::new(0.0, 0.0, 20.0, 20.0), clip_group.clip_geometry().unwrap().bounds());
1121 let options_group = clip_group.children().get(0).cast::<DrawingGroup>().unwrap();
1122 assert_eq!(Some(RenderOptions::default()), options_group.render_options());
1123
1124 assert_eq!(
1125 replay(&group),
1126 [
1127 "PushOpacity 1",
1128 "DrawGeometry none Blue@2 0, 0, 10, 10",
1129 "PushOpacity 1",
1130 "PushOpacity 0.5",
1131 "DrawGeometry Red none 0, 0, 4, 4",
1132 "PopOpacity",
1133 "PopOpacity",
1134 "PushOpacity 1",
1135 "PushGeometryClip 0, 0, 20, 20",
1136 "PushOpacity 1",
1137 "PushRenderOptions",
1138 "PushOpacity 1",
1139 "PushGeometryClip 1, 1, 2, 2",
1140 "PushOpacity 1",
1141 "PushOpacityMask Green 0, 0, 4, 4",
1143 "DrawGeometry Red Blue@2 1, 1, 2, 2",
1144 "PopOpacityMask",
1145 "PopOpacity",
1146 "PopGeometryClip",
1147 "PopOpacity",
1148 "PopRenderOptions",
1149 "PopOpacity",
1150 "PopGeometryClip",
1151 "PopOpacity",
1152 "PopOpacity",
1153 ]
1154 );
1155 scope.dispose();
1156 }
1157
1158 #[test]
1159 fn open_records_glyph_runs_and_text_options_and_draws_them_back() {
1160 use crate::media::text_formatting::testing::{utf16, TextTestScope};
1161 use crate::media::{TextHintingMode, Typeface};
1162
1163 let _scope = TextTestScope::new();
1164 let glyph_typeface = Typeface::default_typeface().glyph_typeface();
1165 let glyphs: Vec<u16> =
1166 "ab".chars().map(|c| glyph_typeface.character_to_glyph_map().get_glyph(c as i32)).collect();
1167 let glyph_run = GlyphRun::from_glyph_indices(glyph_typeface, 12.0, utf16("ab"), &glyphs, None, 0);
1168 let text_options = TextOptions { text_hinting_mode: TextHintingMode::Strong, ..Default::default() };
1169
1170 let group = DrawingGroup::new();
1171 {
1172 let mut context = group.open();
1173 let options = context.push_text_options(text_options);
1174 context.draw_glyph_run(Some(&solid(Colors::RED)), &glyph_run);
1175 context.draw_glyph_run(None, &glyph_run);
1177 context.pop(options);
1178 }
1179
1180 let children = group.children();
1183 assert_eq!(1, children.len());
1184 let options_group = children.get(0).cast::<DrawingGroup>().expect("a group for the text options");
1185 assert_eq!(Some(text_options), options_group.text_options());
1186 assert_eq!(None, group.text_options());
1187 let group_children = options_group.children();
1188 assert_eq!(1, group_children.len());
1189 let drawing = group_children.get(0).cast::<GlyphRunDrawing>().expect("a glyph run drawing");
1190 assert!(Rc::ptr_eq(&glyph_run, &drawing.glyph_run().unwrap()));
1191 assert!(*drawing.foreground().unwrap() == *solid(Colors::RED));
1192 assert_eq!(glyph_run.bounds(), drawing.get_bounds());
1193 assert_eq!(glyph_run.bounds(), group.get_bounds());
1194
1195 let glyph_bounds = glyph_run.platform_impl().bounds();
1196 assert_eq!(
1197 replay(&group),
1198 [
1199 "PushOpacity 1".to_string(),
1200 "PushOpacity 1".to_string(),
1201 "PushTextOptions Unspecified Strong Unspecified".to_string(),
1202 format!("DrawGlyphRun Red {glyph_bounds}"),
1203 "PopTextOptions".to_string(),
1204 "PopOpacity".to_string(),
1205 "PopOpacity".to_string(),
1206 ]
1207 );
1208 }
1209
1210 #[test]
1211 fn glyph_run_drawing_without_a_glyph_run_draws_nothing() {
1212 let drawing = GlyphRunDrawing::new();
1213 drawing.set_foreground(Some(solid(Colors::RED)));
1214 assert_eq!(Rect::default(), drawing.get_bounds());
1215 assert!(replay(&drawing).is_empty());
1216 }
1217
1218 #[test]
1219 fn drawing_a_group_pushes_its_effect_with_the_children_bounds() {
1220 let (scope, _) = MockPlatformRenderInterface::install();
1221 let effect = blur(10.0);
1222 let content = Rect::new(20.0, 20.0, 50.0, 50.0);
1223 let padding = EffectExtensions::get_effect_output_padding(Some(&*effect));
1224 let group = DrawingGroup::new();
1225 group.set_effect(Some(effect));
1226 group.children().add(rectangle_drawing(solid(Colors::RED), content));
1227
1228 assert_eq!(
1229 replay(&group),
1230 [
1231 "PushOpacity 1".to_string(),
1232 format!("PushEffect {}", content.inflate_thickness(padding)),
1233 "DrawGeometry Red none 20, 20, 50, 50".to_string(),
1234 "PopEffect".to_string(),
1235 "PopOpacity".to_string(),
1236 ]
1237 );
1238 scope.dispose();
1239 }
1240
1241 #[test]
1242 #[should_panic(expected = "Pushing a rounded clip into a DrawingGroup is not implemented.")]
1243 fn open_does_not_record_rounded_clips() {
1244 let (_scope, _) = MockPlatformRenderInterface::install();
1245 let group = DrawingGroup::new();
1246 let mut context = group.open();
1247 let _ = context.push_rounded_clip(RoundedRect::from_rect(Rect::new(0.0, 0.0, 1.0, 1.0)));
1248 }
1249
1250 #[test]
1251 #[should_panic(expected = "Drawing a bitmap into a DrawingGroup is not implemented.")]
1252 fn open_does_not_record_bitmaps() {
1253 let (_scope, render_interface) = MockPlatformRenderInterface::install();
1254 let bitmap_impl: std::sync::Arc<crate::platform::SharedBitmapImpl> = std::sync::Arc::new(crate::rendering::testing::MockDrawingContextLayerImpl::new(
1255 render_interface.log().clone(),
1256 crate::PixelSize::new(1, 1),
1257 ));
1258 let bitmap = crate::utilities::RefCountable::create(bitmap_impl, || {});
1259 let group = DrawingGroup::new();
1260 let mut context = group.open();
1261 context.draw_bitmap(&bitmap, 1.0, Rect::default(), Rect::default());
1262 }
1263}