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
17struct 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#[repr(C)]
32pub struct VisualBrush {
33 base: TileBrush,
34 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 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 fn prepare_serialization(this: &Self, c: &Rc<Compositor>) {
72 Self::parent_prepare_serialization(this, c);
73 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 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 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 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 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 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 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 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 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 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 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}