Skip to main content

ferroui_base/media/
drawing_group.rs

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/// Represents a collection of drawings that can be operated upon as a single
22/// drawing.
23#[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        // Compute local bounds from children only when the effect bounds
48        // are not explicitly set. The effect bounds store content bounds
49        // (pre-inflation); pushing the effect handles inflation.
50        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        // Note: the effect is intentionally not reflected here. The bounds
115        // are the content/geometric bounds only. Effects render additively
116        // outside the content bounds and must not shift the coordinate
117        // origin (e.g. when used inside a drawing image).
118        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        /// Defines the `Opacity` property.
129        pub fn opacity_property() -> StyledProperty<f64> {
130            FerroProperty::register::<DrawingGroup, _>("Opacity", 1.0)
131        }
132    );
133
134    ferro_property!(
135        /// Defines the `Transform` property.
136        pub fn transform_property() -> StyledProperty<Option<Ref<Transform>>> {
137            FerroProperty::register::<DrawingGroup, _>("Transform", None)
138        }
139    );
140
141    ferro_property!(
142        /// Defines the `ClipGeometry` property.
143        pub fn clip_geometry_property() -> StyledProperty<Option<Ref<Geometry>>> {
144            FerroProperty::register::<DrawingGroup, _>("ClipGeometry", None)
145        }
146    );
147
148    ferro_property!(
149        /// Defines the `OpacityMask` property.
150        pub fn opacity_mask_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
151            FerroProperty::register::<DrawingGroup, _>("OpacityMask", None)
152        }
153    );
154
155    ferro_property!(
156        /// Defines the `Effect` property.
157        pub fn effect_property() -> StyledProperty<Option<Rc<dyn IEffect>>> {
158            FerroProperty::register::<DrawingGroup, _>("Effect", None)
159        }
160    );
161
162    ferro_property!(
163        /// Defines the `Children` property.
164        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    /// Creates the class data.
177    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    /// The opacity the children are drawn with.
194    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    /// The transform applied to the children.
203    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    /// The geometry the children are clipped to.
212    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    /// The opacity mask the children are drawn through.
221    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    /// The effect applied to the children.
230    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    /// The render options the children are drawn with. Set by the drawing
239    /// context that records into a group.
240    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    /// The text options the children are drawn with. Set by the drawing
249    /// context that records into a group.
250    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    /// The bounds of the content the effect applies to; computed from the
259    /// children when not set. Set by the drawing context that records into a
260    /// group.
261    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    /// The collection that contains the child drawings.
270    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    /// Opens the group for populating: what is drawn on the returned
296    /// context replaces the children of the group when the context is
297    /// disposed (or dropped).
298    ///
299    /// Panics when no platform render interface is registered.
300    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
345/// The drawing context core that records what is drawn as the children of
346/// a [`DrawingGroup`].
347struct DrawingGroupDrawingContext {
348    drawing_group: Ref<DrawingGroup>,
349    platform_render_interface: Rc<dyn IPlatformRenderInterface>,
350
351    disposed: bool,
352
353    // Root drawing created by this drawing context.
354    //
355    // If there is only a single child of the root drawing group,
356    // `root_drawing` references the single child, and the root
357    // `current_drawing_group` value is `None`. Otherwise, `root_drawing`
358    // references the root drawing group, and is the same value as the root
359    // `current_drawing_group`.
360    //
361    // Either way, `root_drawing` always references the root drawing.
362    root_drawing: Option<Ref<Drawing>>,
363
364    // Current drawing group that new children are added to.
365    current_drawing_group: Option<Ref<DrawingGroup>>,
366
367    // Previous values of `current_drawing_group`.
368    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        // Verify that pop hasn't been called too many times.
385        let Some(previous) = self.previous_drawing_group_stack.as_mut().and_then(Vec::pop) else {
386            panic!("DrawingGroupStack count missmatch.");
387        };
388
389        // Restore the previous value of the current drawing group.
390        self.current_drawing_group = previous;
391    }
392
393    fn push_new_drawing_group(&mut self) -> Ref<DrawingGroup> {
394        // Instantiate a new drawing group.
395        let drawing_group = DrawingGroup::new();
396
397        // Add it to the drawing graph, like any other drawing.
398        self.add_drawing(drawing_group.clone().upcast());
399
400        // Save the previous `current_drawing_group` value. The stack is
401        // allocated lazily because many uses have a depth of one.
402        //
403        // If this is the first call, the value of `current_drawing_group`
404        // is `None` because `add_drawing` doesn't create a current group
405        // for the first drawing. Having `None` on the stack is valid, and
406        // simply denotes that this new drawing group is the first child in
407        // the root drawing group. It is also possible for the first value
408        // on the stack to be `Some`, which means that the root drawing
409        // group has other children.
410        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        // Set this drawing group as the current one so that subsequent
414        // drawings are added as its children until pop is called.
415        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        // Instantiate the geometry drawing.
427        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        // Add it to the drawing graph.
433        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                // If this is the first drawing being added, avoid creating
443                // a drawing group and set this drawing as the root drawing.
444                // This optimizes the common case where only a single child
445                // exists in the root drawing group.
446                self.root_drawing = Some(new_drawing);
447            }
448            (Some(root_drawing), None) => {
449                // When the second drawing is added at the root level, set a
450                // drawing group as the root and add both drawings to it.
451                let current_drawing_group = DrawingGroup::new();
452
453                // Add both children.
454                let children = current_drawing_group.children();
455                children.add(root_drawing.clone());
456                children.add(new_drawing);
457
458                // Set the new drawing group as the current.
459                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                // If there already is a current drawing group, then simply
464                // add the new drawing to it.
465                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        // Instantiate the geometry.
474        let geometry = self.platform_render_interface.create_line_geometry(p1, p2);
475
476        // Add the drawing to the drawing graph.
477        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        // Instantiate the geometry.
501        let geometry = self.platform_render_interface.create_rectangle_geometry(rrect.rect);
502
503        // Add the drawing to the drawing graph.
504        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        // Instantiate the geometry.
513        let geometry = self.platform_render_interface.create_ellipse_geometry(rect);
514
515        // Add the drawing to the drawing graph.
516        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        // Add the drawing to the drawing graph.
537        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        // Instantiate a new drawing group and set it as the current one.
570        let drawing_group = self.push_new_drawing_group();
571
572        // Set the transform on the new drawing group.
573        drawing_group.set_transform(MatrixTransform::with_matrix(matrix).upcast::<Transform>());
574    }
575
576    fn push_render_options_core(&mut self, render_options: RenderOptions) {
577        // Instantiate a new drawing group and set it as the current one.
578        let drawing_group = self.push_new_drawing_group();
579
580        // Set the render options on the new drawing group.
581        drawing_group.set_render_options(Some(render_options));
582    }
583
584    fn push_text_options_core(&mut self, text_options: TextOptions) {
585        // Instantiate a new drawing group and set it as the current one.
586        let drawing_group = self.push_new_drawing_group();
587
588        // Set the text options on the new drawing group.
589        drawing_group.set_text_options(Some(text_options));
590    }
591
592    fn push_effect_core(&mut self, effect: &Rc<dyn IEffect>, bounds: Rect) {
593        // Instantiate a new drawing group and set it as the current one.
594        let drawing_group = self.push_new_drawing_group();
595
596        // Set the effect on the new drawing group.
597        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        // Dispose may be called multiple times without failing.
635        if self.disposed {
636            return;
637        }
638
639        // Match any outstanding push calls with a pop.
640        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        // Close with the root drawing group's children.
646        let root_children = match &self.current_drawing_group {
647            // If a root drawing group was created because multiple elements
648            // exist at the root level, provide its children collection
649            // directly.
650            Some(current_drawing_group) => current_drawing_group.children(),
651            // Create a new collection if no root drawing group was created
652            // because the root level only contained a single child. The
653            // collection is needed because opening a group always replaces
654            // its children collection.
655            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    // Not from upstream: the upstream tests of this class record through a
673    // drawing context.
674    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        // A brush is not baked into the recording: only its replacement
759        // invalidates the drawing.
760        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        // A nested drawing image invalidates the drawing that draws it.
810        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    // --- from upstream: DrawingGroupTests --------------------------------
828
829    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    /// A drawing context core that captures the bounds of the pushed effect
851    /// and opacity mask.
852    #[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        // Opening the group adds a child drawing group to the root group
916        // when an effect is pushed.
917        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    /// Regression test: the platform drawing context was passing the raw
946    /// content bounds to the platform effect API without inflating them by
947    /// the effect output padding, causing effects to be clipped.
948    #[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        // The platform API received the inflated (output) bounds, not the
966        // raw content bounds.
967        assert_eq!(1, log.count("PushEffect"));
968        assert_eq!(log.entries()[0], format!("PushEffect {expected_inflated_bounds}"));
969    }
970
971    /// Regression test: drawing a group was passing uninflated local bounds
972    /// to the opacity mask when an effect was also set, meaning the opacity
973    /// mask didn't cover the effect output region.
974    #[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    // --- not from upstream: recording into a group and drawing it ---------
1038
1039    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            // Nothing changes before the context is closed.
1062            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        // Opening again replaces the content; an empty recording clears it.
1072        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            // The states still pushed are closed with the context.
1107            let _ = (clip, options, geometry_clip, mask);
1108        }
1109
1110        // Root: the line, the transform group and the clip group.
1111        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                // The bounds of the masked content, including the stroke.
1142                "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            // Without a foreground nothing is recorded.
1176            context.draw_glyph_run(None, &glyph_run);
1177            context.pop(options);
1178        }
1179
1180        // The pushed state is the only root drawing: a group with the text
1181        // options whose only child is the glyph run drawing.
1182        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}