Skip to main content

ferroui_base/media/
visual_brush.rs

1use crate::media::ref_adapter::RefAdapter;
2use crate::media::{BrushImpl, BrushImplExt, DrawingContext, ISceneBrushContent, ServerBrushFactory, TileBrush};
3use crate::rendering::composition::drawing::{
4    CompositionRenderData, CompositionRenderDataSceneBrushContentProperties, RenderDataDrawingContext,
5};
6use crate::rendering::composition::server::ServerCompositionSimpleContentBrush;
7use crate::rendering::composition::transport::BatchStreamWriter;
8use crate::rendering::composition::Compositor;
9use crate::rendering::ImmediateRenderer;
10use crate::{
11    ferro_class, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
12    FerroPropertyChangedEventArgs, Nullable, Rect, Ref, StyledProperty, Visual,
13};
14use std::cell::{Cell, RefCell};
15use std::rc::{Rc, Weak};
16
17/// The content of the brush recorded for one compositor.
18struct RenderDataItem {
19    data: Rc<CompositionRenderData>,
20    rect: Rect,
21    is_dirty: Cell<bool>,
22}
23
24impl RenderDataItem {
25    fn dispose(&self) {
26        self.data.dispose();
27    }
28}
29
30/// Paints an area with a [`Visual`].
31#[repr(C)]
32pub struct VisualBrush {
33    base: TileBrush,
34    /// The recorded content per compositor the brush is used with; `None`
35    /// for a compositor the content of which is empty.
36    render_data_dictionary: RefCell<Vec<(Weak<Compositor>, Option<Rc<RenderDataItem>>)>>,
37}
38
39ferro_class!(VisualBrush: TileBrush);
40crate::ferro_class_info!(VisualBrush { new: VisualBrush::new });
41
42impl FerroObjectImpl for VisualBrush {
43    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
44        // We are supposed to be only calling this when content is actually changed,
45        // but instead we are calling this on brush property change for backwards compatibility
46        this.invalidate_content();
47        Self::parent_on_property_changed(this, change);
48    }
49}
50
51impl BrushImpl for VisualBrush {
52    fn factory(_this: &Self) -> Option<ServerBrushFactory> {
53        Some(|c| ServerCompositionSimpleContentBrush::new(c))
54    }
55
56    fn on_unreferenced_from_compositor(this: &Self, c: &Rc<Compositor>) {
57        let removed = {
58            let mut dictionary = this.render_data_dictionary.borrow_mut();
59            dictionary.iter().position(|(key, _)| is_compositor(key, c)).map(|index| dictionary.remove(index).1)
60        };
61        if let Some(Some(content)) = removed {
62            content.dispose();
63        }
64        Self::parent_on_unreferenced_from_compositor(this, c);
65    }
66
67    /// The first half of the serialization of upstream: records the content
68    /// again when there is none or it is out of date. The render data is a
69    /// new server object, which has to be created before the changes of the
70    /// brush refer to it.
71    fn prepare_serialization(this: &Self, c: &Rc<Compositor>) {
72        Self::parent_prepare_serialization(this, c);
73        // Should always be true here, but just in case do this check
74        if this.is_on_compositor(c) {
75            let data = this.render_data(c);
76            if data.as_ref().is_none_or(|data| data.is_dirty.get()) {
77                let created = this.create_server_content(c);
78                // Dispose the old render list _after_ creating a new one to avoid unnecessary detach/attach
79                // sequence for referenced resources
80                if let Some(data) = data {
81                    data.dispose();
82                }
83
84                this.set_render_data(c, created);
85            }
86        }
87    }
88
89    fn serialize_changes(this: &Self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
90        Self::parent_serialize_changes(this, c, writer);
91        let mut content = None;
92        // Should always be true here, but just in case do this check
93        if this.is_on_compositor(c) {
94            if let Some(data) = this.render_data(c) {
95                content = Some((data.data.server(), Some(data.rect), true));
96            }
97        }
98
99        CompositionRenderDataSceneBrushContentProperties::serialize(writer, content);
100    }
101}
102
103fn is_compositor(key: &Weak<Compositor>, c: &Compositor) -> bool {
104    std::ptr::eq(key.as_ptr(), c)
105}
106
107crate::ferro_properties! { impl VisualBrush {
108    ferro_property!(pub fn visual_property() -> StyledProperty<Option<Ref<Visual>>> {
109        FerroProperty::register::<VisualBrush, _>("Visual", None)
110    });
111} }
112
113impl VisualBrush {
114    /// Creates the class data.
115    pub fn construct() -> Self {
116        Self { base: TileBrush::construct(), render_data_dictionary: RefCell::new(Vec::new()) }
117    }
118
119    pub fn new() -> Ref<Self> {
120        instantiate(Self::construct())
121    }
122
123    /// Creates a brush that draws `visual`.
124    pub fn with_visual(visual: impl Into<Nullable<Visual>>) -> Ref<Self> {
125        let result = Self::new();
126        result.set_visual(visual);
127        result
128    }
129
130    /// The visual to draw.
131    pub fn visual(&self) -> Option<Ref<Visual>> {
132        self.get_value(Self::visual_property())
133    }
134
135    pub fn set_visual(&self, value: impl Into<Nullable<Visual>>) {
136        self.set_value(Self::visual_property(), value.into().0)
137    }
138
139    /// Records the current content of the brush: the rendering of the
140    /// visual. `None` when there is no visual or it draws nothing.
141    pub fn create_content(&self) -> Option<Rc<dyn ISceneBrushContent>> {
142        let visual = self.visual()?;
143
144        visual.ensure_initialized_for_visual_brush();
145
146        let mut recorder = RenderDataDrawingContext::new(None);
147        {
148            let mut context = DrawingContext::new(&mut recorder);
149            ImmediateRenderer::render(&mut context, &visual);
150        }
151        let content = recorder.get_immediate_scene_brush_content(
152            Rc::new(RefAdapter(self.to_ref())),
153            Some(Rect::from_size(visual.bounds().size())),
154            true,
155        );
156        recorder.reset();
157        content.map(|content| content as Rc<dyn ISceneBrushContent>)
158    }
159
160    /// The content recorded for compositor `c`, if there is any.
161    fn render_data(&self, c: &Compositor) -> Option<Rc<RenderDataItem>> {
162        self.render_data_dictionary.borrow().iter().find(|(key, _)| is_compositor(key, c)).and_then(|(_, item)| item.clone())
163    }
164
165    fn set_render_data(&self, c: &Rc<Compositor>, item: Option<Rc<RenderDataItem>>) {
166        let mut dictionary = self.render_data_dictionary.borrow_mut();
167        match dictionary.iter_mut().find(|(key, _)| is_compositor(key, c)) {
168            Some(entry) => entry.1 = item,
169            None => dictionary.push((Rc::downgrade(c), item)),
170        }
171    }
172
173    fn invalidate_content(&self) {
174        for (_, item) in self.render_data_dictionary.borrow().iter() {
175            if let Some(item) = item {
176                item.is_dirty.set(true);
177            }
178        }
179        self.register_for_serialization();
180    }
181
182    fn create_server_content(&self, c: &Rc<Compositor>) -> Option<Rc<RenderDataItem>> {
183        let visual = self.visual()?;
184
185        visual.ensure_initialized_for_visual_brush();
186
187        let mut recorder = RenderDataDrawingContext::new(Some(c.clone()));
188        {
189            let mut context = DrawingContext::new(&mut recorder);
190            ImmediateRenderer::render(&mut context, &visual);
191        }
192        let render_data = recorder.get_render_results();
193        recorder.reset();
194        let render_data = render_data?;
195
196        Some(Rc::new(RenderDataItem {
197            data: render_data,
198            rect: Rect::from_size(visual.bounds().size()),
199            is_dirty: Cell::new(false),
200        }))
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    // Not from upstream: the upstream tests of this class need the compositor.
207    use super::*;
208    use crate::media::{IBrush, Stretch};
209    use std::cell::Cell;
210
211    #[test]
212    fn changing_visual_raises_invalidated() {
213        let target = VisualBrush::new();
214        let raised = Rc::new(Cell::new(0));
215        let r = raised.clone();
216        target.invalidated(move || r.set(r.get() + 1));
217        let visual = Visual::new();
218        target.set_visual(&visual);
219        assert_eq!(1, raised.get());
220        assert!(target.visual().unwrap() == visual);
221        target.set_stretch(Stretch::Fill);
222        assert_eq!(2, raised.get());
223
224        let brush: Rc<dyn IBrush> = VisualBrush::with_visual(&visual).into();
225        assert_eq!(Stretch::Uniform, brush.as_tile_brush().unwrap().stretch());
226        assert!(brush.as_image_brush().is_none());
227        assert!(brush.as_mutable_brush().is_none());
228    }
229
230    #[repr(C)]
231    struct Painted {
232        base: Visual,
233    }
234
235    crate::ferro_class!(Painted: Visual);
236    crate::ferro_impl_classes!(Painted: FerroObjectImpl, crate::StyledElementImpl);
237
238    impl crate::VisualImpl for Painted {
239        fn render(this: &Self, context: &mut DrawingContext) {
240            let brush: Rc<dyn IBrush> = crate::media::Brushes::red();
241            context.fill_rectangle(&brush, Rect::from_size(this.bounds().size()), 0.0);
242        }
243    }
244
245    #[test]
246    fn create_content_records_the_rendering_of_the_visual() {
247        use crate::media::ISceneBrush;
248        use crate::rendering::testing::{DrawingLog, MockDrawingContextImpl};
249
250        let target = VisualBrush::new();
251        assert!(target.create_content().is_none());
252
253        // A visual that draws nothing has no content.
254        target.set_visual(&Visual::new());
255        assert!(target.create_content().is_none());
256
257        let visual: Ref<Painted> = instantiate(Painted { base: Visual::construct() });
258        visual.set_bounds(Rect::new(30.0, 40.0, 20.0, 10.0));
259        let child: Ref<Painted> = instantiate(Painted { base: Visual::construct() });
260        child.set_bounds(Rect::new(5.0, 5.0, 2.0, 2.0));
261        visual.visual_children().add(child.upcast());
262        target.set_visual(&visual.clone().upcast::<Visual>());
263        target.set_stretch(Stretch::None);
264
265        let adapter = RefAdapter(target.clone());
266        let content = ISceneBrush::create_content(&adapter).expect("the visual draws something");
267        // The content covers the visual's own area, wherever its parent
268        // placed it.
269        assert_eq!(Rect::new(0.0, 0.0, 20.0, 10.0), content.rect());
270        assert!(content.use_scalable_rasterization());
271        assert_eq!(Stretch::None, content.brush().stretch());
272
273        let log = DrawingLog::new();
274        let mut context = MockDrawingContextImpl::new(log.clone());
275        context.log_transforms = false;
276        content.render(&mut context, None);
277        assert_eq!(
278            log.entries(),
279            [
280                "PushRoundedClip 0, 0, 20, 10",
281                "DrawRectangle Red none 0, 0, 20, 10 shadows=0",
282                "DrawRectangle Red none 0, 0, 2, 2 shadows=0",
283                "PopClip"
284            ]
285        );
286        content.dispose();
287    }
288}