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