1use crate::media::ref_adapter::RefAdapter;
2use crate::media::{BrushImpl, BrushImplExt, Drawing, DrawingContext, ISceneBrushContent, ServerBrushFactory, TileBrush};
3use crate::reactive::IDisposable;
4use crate::rendering::composition::drawing::{
5 CompositionRenderData, CompositionRenderDataSceneBrushContentProperties, RenderDataDrawingContext,
6};
7use crate::rendering::composition::server::ServerCompositionSimpleContentBrush;
8use crate::rendering::composition::transport::BatchStreamWriter;
9use crate::rendering::composition::Compositor;
10use crate::{
11 ferro_class, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
12 FerroPropertyChangedEventArgs, Nullable, Ref, StyledProperty,
13};
14use std::cell::{Cell, RefCell};
15use std::rc::{Rc, Weak};
16
17struct RenderDataItem {
19 data: Rc<CompositionRenderData>,
20 is_dirty: Cell<bool>,
21}
22
23impl RenderDataItem {
24 fn dispose(&self) {
25 self.data.dispose();
26 }
27}
28
29#[repr(C)]
31pub struct DrawingBrush {
32 base: TileBrush,
33 drawing_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
34 render_data_dictionary: RefCell<Vec<(Weak<Compositor>, Option<Rc<RenderDataItem>>)>>,
37}
38
39ferro_class!(DrawingBrush: TileBrush);
40crate::ferro_class_info!(DrawingBrush { new: DrawingBrush::new });
41
42impl FerroObjectImpl for DrawingBrush {
43 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
44 if change.property() == Self::drawing_property().as_property() {
48 let new_value = change.get_new_value::<Option<Ref<Drawing>>>();
49 if let Some(subscription) = this.drawing_subscription.take() {
50 subscription.dispose();
51 }
52 if let Some(new_value) = new_value {
53 let weak = this.to_ref().downgrade();
54 let subscription = new_value.invalidated(move || {
55 if let Some(this) = weak.upgrade() {
56 this.invalidate_content();
57 }
58 });
59 *this.drawing_subscription.borrow_mut() = Some(subscription);
60 }
61 this.invalidate_content();
62 }
63
64 Self::parent_on_property_changed(this, change);
65 }
66}
67
68impl BrushImpl for DrawingBrush {
69 fn factory(_this: &Self) -> Option<ServerBrushFactory> {
70 Some(|c| ServerCompositionSimpleContentBrush::new(c))
71 }
72
73 fn on_unreferenced_from_compositor(this: &Self, c: &Rc<Compositor>) {
74 let removed = {
75 let mut dictionary = this.render_data_dictionary.borrow_mut();
76 dictionary.iter().position(|(key, _)| is_compositor(key, c)).map(|index| dictionary.remove(index).1)
77 };
78 if let Some(Some(content)) = removed {
79 content.dispose();
80 }
81 Self::parent_on_unreferenced_from_compositor(this, c);
82 }
83
84 fn prepare_serialization(this: &Self, c: &Rc<Compositor>) {
89 Self::parent_prepare_serialization(this, c);
90 if this.is_on_compositor(c) {
92 let data = this.render_data(c);
93 if data.as_ref().is_none_or(|data| data.is_dirty.get()) {
94 let created = this.create_server_content(c);
95 if let Some(data) = data {
98 data.dispose();
99 }
100 this.set_render_data(c, created);
101 }
102 }
103 }
104
105 fn serialize_changes(this: &Self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
106 Self::parent_serialize_changes(this, c, writer);
107
108 let mut content = None;
109 if this.is_on_compositor(c) {
111 if let Some(data) = this.render_data(c) {
112 content = Some((data.data.server(), None, true));
113 }
114 }
115
116 CompositionRenderDataSceneBrushContentProperties::serialize(writer, content);
117 }
118}
119
120fn is_compositor(key: &Weak<Compositor>, c: &Compositor) -> bool {
121 std::ptr::eq(key.as_ptr(), c)
122}
123
124crate::ferro_properties! { impl DrawingBrush {
125 ferro_property!(pub fn drawing_property() -> StyledProperty<Option<Ref<Drawing>>> {
126 FerroProperty::register::<DrawingBrush, _>("Drawing", None)
127 });
128} }
129
130impl DrawingBrush {
131 pub fn construct() -> Self {
133 Self {
134 base: TileBrush::construct(),
135 drawing_subscription: RefCell::new(None),
136 render_data_dictionary: RefCell::new(Vec::new()),
137 }
138 }
139
140 pub fn new() -> Ref<Self> {
141 instantiate(Self::construct())
142 }
143
144 pub fn with_drawing(drawing: impl Into<Nullable<Drawing>>) -> Ref<Self> {
146 let result = Self::new();
147 result.set_drawing(drawing);
148 result
149 }
150
151 pub fn drawing(&self) -> Option<Ref<Drawing>> {
153 self.get_value(Self::drawing_property())
154 }
155
156 pub fn set_drawing(&self, value: impl Into<Nullable<Drawing>>) {
157 self.set_value(Self::drawing_property(), value.into().0)
158 }
159
160 pub fn create_content(&self) -> Option<Rc<dyn ISceneBrushContent>> {
163 let drawing = self.drawing()?;
164
165 let mut recorder = RenderDataDrawingContext::new(None);
166 {
167 let mut context = DrawingContext::new(&mut recorder);
168 drawing.draw(&mut context);
169 }
170 let content = recorder.get_immediate_scene_brush_content(Rc::new(RefAdapter(self.to_ref())), None, true);
171 recorder.reset();
172 content.map(|content| content as Rc<dyn ISceneBrushContent>)
173 }
174
175 fn render_data(&self, c: &Compositor) -> Option<Rc<RenderDataItem>> {
177 self.render_data_dictionary.borrow().iter().find(|(key, _)| is_compositor(key, c)).and_then(|(_, item)| item.clone())
178 }
179
180 fn set_render_data(&self, c: &Rc<Compositor>, item: Option<Rc<RenderDataItem>>) {
181 let mut dictionary = self.render_data_dictionary.borrow_mut();
182 match dictionary.iter_mut().find(|(key, _)| is_compositor(key, c)) {
183 Some(entry) => entry.1 = item,
184 None => dictionary.push((Rc::downgrade(c), item)),
185 }
186 }
187
188 fn invalidate_content(&self) {
190 for (_, item) in self.render_data_dictionary.borrow().iter() {
191 if let Some(item) = item {
192 item.is_dirty.set(true);
193 }
194 }
195
196 self.register_for_serialization();
197 self.raise_invalidated();
198 }
199
200 fn create_server_content(&self, c: &Rc<Compositor>) -> Option<Rc<RenderDataItem>> {
201 let drawing = self.drawing()?;
202
203 let mut recorder = RenderDataDrawingContext::new(Some(c.clone()));
204 {
205 let mut context = DrawingContext::new(&mut recorder);
206 drawing.draw(&mut context);
207 }
208 let render_data = recorder.get_render_results();
209 recorder.reset();
210 Some(Rc::new(RenderDataItem { data: render_data?, is_dirty: Cell::new(false) }))
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
218 use crate::media::{Brushes, GeometryDrawing};
219 use std::cell::Cell;
220
221 #[test]
222 fn drawing_changes_raise_invalidated() {
223 let target = DrawingBrush::new();
224 let raised = Rc::new(Cell::new(0));
225 let r = raised.clone();
226 target.invalidated(move || r.set(r.get() + 1));
227
228 let drawing = GeometryDrawing::new();
229 target.set_drawing(&drawing);
230 assert!(raised.get() >= 1);
231
232 let before = raised.get();
233 drawing.set_brush(Some(Brushes::red()));
234 assert_eq!(before + 1, raised.get());
235
236 target.set_drawing(None);
237 let before = raised.get();
238 drawing.set_brush(Some(Brushes::blue()));
239 assert_eq!(before, raised.get());
240 }
241
242 #[test]
243 fn create_content_records_the_drawing() {
244 use crate::media::{ISceneBrush, RectangleGeometry, Stretch};
245 use crate::rendering::testing::{DrawingLog, MockDrawingContextImpl, MockPlatformRenderInterface};
246 use crate::{Matrix, Rect};
247
248 let (scope, _) = MockPlatformRenderInterface::install();
249 let target = DrawingBrush::new();
250 assert!(target.create_content().is_none());
251
252 let drawing = GeometryDrawing::new();
253 target.set_drawing(&drawing);
254 assert!(target.create_content().is_none());
256
257 drawing.set_brush(Some(Brushes::red()));
258 drawing.set_geometry(RectangleGeometry::with_rect(Rect::new(1.5, 2.5, 10.0, 20.0)).upcast::<crate::media::Geometry>());
259 target.set_stretch(Stretch::Fill);
260 target.set_opacity(0.25);
261
262 let adapter = RefAdapter(target.clone());
264 let content = ISceneBrush::create_content(&adapter).expect("the drawing draws something");
265 assert_eq!(Rect::new(1.0, 2.0, 11.0, 21.0), content.rect());
267 assert!(content.use_scalable_rasterization());
268 assert_eq!(Stretch::Fill, content.brush().stretch());
269 assert_eq!(0.25, content.opacity());
270
271 let log = DrawingLog::new();
272 let mut context = MockDrawingContextImpl::new(log.clone());
273 content.render(&mut context, None);
274 assert_eq!(log.entries(), ["DrawGeometry Red none 1.5, 2.5, 10, 20"]);
275
276 log.clear();
278 let transform = Matrix::create_scale(2.0, 2.0);
279 content.render(&mut context, Some(transform));
280 assert_eq!(
281 log.entries(),
282 [
283 format!("SetTransform {transform}"),
284 "DrawGeometry Red none 1.5, 2.5, 10, 20".to_string(),
285 format!("SetTransform {}", Matrix::IDENTITY),
286 ]
287 );
288
289 content.dispose();
291 content.dispose();
292 log.clear();
293 content.render(&mut context, None);
294 assert!(log.entries().is_empty());
295
296 drawing.set_brush(Some(Brushes::blue()));
298 let content = target.create_content().unwrap();
299 content.render(&mut context, None);
300 assert_eq!(log.entries(), ["DrawGeometry Blue none 1.5, 2.5, 10, 20"]);
301 scope.dispose();
302 }
303}