1use crate::media::effects::IEffect;
5use crate::media::immutable::ImmutableSolidColorBrush;
6use crate::media::{BoxShadows, Color, IBrush, IExperimentalAcrylicMaterial, IPen, RenderOptions, TextOptions};
7use crate::platform::{
8 IBitmapImpl, IDrawingContextImpl, IDrawingContextImplWithEffects, IDrawingContextLayerImpl,
9 IDrawingContextWithAcrylicLikeSupport, IGeometryImpl, IGlyphRunImpl, IPlatformRenderInterfaceRegion,
10};
11use crate::{Matrix, PixelSize, Point, Rect, RoundedRect};
12use std::any::{Any, TypeId};
13use std::rc::Rc;
14
15#[derive(Clone, Copy, PartialEq, Eq, Debug)]
16enum PendingCommandType {
17 SetTransform,
18 PushClip,
19 PushOpacity,
20 PushOpacityMask,
21 PushGeometryClip,
22 PushRenderOptions,
23 PushTextOptions,
24 PushEffect,
25}
26
27#[derive(Clone, Copy)]
33enum PendingCommand {
34 SetTransform(Matrix),
35 PushClip(Rect),
36 PushRoundedClip(RoundedRect),
37 PushOpacity(f64, Option<Rect>),
38 PushRenderOptions(RenderOptions),
39 PushTextOptions(TextOptions),
40}
41
42impl PendingCommand {
43 fn command_type(&self) -> PendingCommandType {
44 match self {
45 PendingCommand::SetTransform(_) => PendingCommandType::SetTransform,
46 PendingCommand::PushClip(_) | PendingCommand::PushRoundedClip(_) => PendingCommandType::PushClip,
47 PendingCommand::PushOpacity(..) => PendingCommandType::PushOpacity,
48 PendingCommand::PushRenderOptions(_) => PendingCommandType::PushRenderOptions,
49 PendingCommand::PushTextOptions(_) => PendingCommandType::PushTextOptions,
50 }
51 }
52}
53
54pub struct CompositorDrawingContextProxy {
57 inner: Box<dyn IDrawingContextImpl>,
58 transform_stack: Vec<Matrix>,
59 post_transform: Option<Matrix>,
60 reported_transform: Matrix,
63 effective_transform: Matrix,
66 commands: Vec<PendingCommand>,
67 auto_flush: bool,
68}
69
70impl CompositorDrawingContextProxy {
71 pub fn new(inner: Box<dyn IDrawingContextImpl>) -> Self {
72 Self {
73 inner,
74 transform_stack: Vec::new(),
75 post_transform: None,
76 reported_transform: Matrix::IDENTITY,
77 effective_transform: Matrix::IDENTITY,
78 commands: Vec::new(),
79 auto_flush: false,
80 }
81 }
82
83 pub fn into_inner(mut self) -> Box<dyn IDrawingContextImpl> {
85 self.flush();
86 self.inner
87 }
88
89 pub fn post_transform(&self) -> Option<Matrix> {
90 self.post_transform
91 }
92
93 pub fn set_post_transform(&mut self, value: Option<Matrix>) {
94 self.post_transform = value;
95 }
96
97 fn set_impl_transform(&mut self, mut m: Matrix) {
98 self.effective_transform = m;
99 if let Some(post_transform) = self.post_transform {
100 m = m * post_transform;
101 }
102 self.inner.set_transform(m);
103 }
104
105 fn save_transform(&mut self) {
106 self.transform_stack.push(self.effective_transform);
107 }
108
109 fn restore_transform(&mut self) {
110 let transform = self.transform_stack.pop().expect("a transform was saved");
111 self.reported_transform = transform;
112 self.effective_transform = transform;
113 }
114
115 pub fn auto_flush(&self) -> bool {
118 self.auto_flush
119 }
120
121 pub fn set_auto_flush(&mut self, value: bool) {
122 self.auto_flush = value;
123 if value {
124 self.flush();
125 }
126 }
127
128 fn set_transform_command(&mut self, m: Matrix) {
129 if self.auto_flush {
130 self.set_impl_transform(m);
131 return;
132 }
133
134 let cmd = PendingCommand::SetTransform(m);
135 match self.commands.last_mut() {
136 Some(last) if last.command_type() == PendingCommandType::SetTransform => *last = cmd,
137 _ => self.commands.push(cmd),
138 }
139 }
140
141 fn try_discard_or_flush(&mut self, command_type: PendingCommandType) -> bool {
142 for c in (0..self.commands.len()).rev() {
143 let current = self.commands[c].command_type();
144 if current == PendingCommandType::SetTransform {
145 continue;
146 }
147 if current == command_type {
148 self.commands.truncate(c);
149 return true;
150 }
151 break;
152 }
153
154 self.flush();
156 false
157 }
158
159 fn add_command(&mut self, command: PendingCommand) {
160 if self.auto_flush {
161 self.exec_command(command);
162 } else {
163 self.commands.push(command);
164 }
165 }
166
167 fn exec_command(&mut self, cmd: PendingCommand) {
168 if let PendingCommand::SetTransform(transform) = cmd {
169 self.set_impl_transform(transform);
170 return;
171 }
172
173 self.save_transform();
174
175 match cmd {
176 PendingCommand::PushOpacity(opacity, bounds) => self.inner.push_opacity(opacity, bounds),
177 PendingCommand::PushClip(rect) => self.inner.push_clip(rect),
178 PendingCommand::PushRoundedClip(rect) => self.inner.push_clip_rounded(rect),
179 PendingCommand::PushRenderOptions(options) => self.inner.push_render_options(options),
180 PendingCommand::PushTextOptions(options) => self.inner.push_text_options(options),
181 PendingCommand::SetTransform(_) => {}
182 }
183 }
184
185 pub fn flush(&mut self) {
186 let commands = std::mem::take(&mut self.commands);
187 for command in &commands {
188 self.exec_command(*command);
189 }
190 let mut commands = commands;
191 commands.clear();
192 self.commands = commands;
193 }
194
195 fn pop(&mut self, command_type: PendingCommandType, pop: impl FnOnce(&mut dyn IDrawingContextImpl)) {
196 if !self.try_discard_or_flush(command_type) {
197 pop(&mut *self.inner);
198 self.restore_transform();
199 }
200 }
201}
202
203impl IDrawingContextImpl for CompositorDrawingContextProxy {
204 fn transform(&self) -> Matrix {
205 self.reported_transform
206 }
207
208 fn set_transform(&mut self, value: Matrix) {
209 self.reported_transform = value;
210 self.set_transform_command(value);
211 }
212
213 fn clear(&mut self, color: Color) {
214 self.flush();
215 self.inner.clear(color);
216 }
217
218 fn draw_bitmap(&mut self, source: &dyn IBitmapImpl, opacity: f64, source_rect: Rect, dest_rect: Rect) {
219 self.flush();
220 self.inner.draw_bitmap(source, opacity, source_rect, dest_rect);
221 }
222
223 fn draw_bitmap_with_mask(
224 &mut self,
225 source: &dyn IBitmapImpl,
226 opacity_mask: &dyn IBrush,
227 opacity_mask_rect: Rect,
228 dest_rect: Rect,
229 ) {
230 self.flush();
231 self.inner.draw_bitmap_with_mask(source, opacity_mask, opacity_mask_rect, dest_rect);
232 }
233
234 fn draw_line(&mut self, pen: Option<&dyn IPen>, p1: Point, p2: Point) {
235 self.flush();
236 self.inner.draw_line(pen, p1, p2);
237 }
238
239 fn draw_geometry(&mut self, brush: Option<&dyn IBrush>, pen: Option<&dyn IPen>, geometry: &dyn IGeometryImpl) {
240 self.flush();
241 self.inner.draw_geometry(brush, pen, geometry);
242 }
243
244 fn draw_rectangle(
245 &mut self,
246 brush: Option<&dyn IBrush>,
247 pen: Option<&dyn IPen>,
248 rect: RoundedRect,
249 box_shadows: &BoxShadows,
250 ) {
251 self.flush();
252 self.inner.draw_rectangle(brush, pen, rect, box_shadows);
253 }
254
255 fn draw_region(
256 &mut self,
257 brush: Option<&dyn IBrush>,
258 pen: Option<&dyn IPen>,
259 region: &dyn IPlatformRenderInterfaceRegion,
260 ) {
261 self.flush();
262 self.inner.draw_region(brush, pen, region);
263 }
264
265 fn draw_ellipse(&mut self, brush: Option<&dyn IBrush>, pen: Option<&dyn IPen>, rect: Rect) {
266 self.flush();
267 self.inner.draw_ellipse(brush, pen, rect);
268 }
269
270 fn draw_glyph_run(&mut self, foreground: Option<&dyn IBrush>, glyph_run: &dyn IGlyphRunImpl) {
271 self.flush();
272 self.inner.draw_glyph_run(foreground, glyph_run);
273 }
274
275 fn create_layer(&mut self, size: PixelSize) -> Rc<dyn IDrawingContextLayerImpl> {
276 self.inner.create_layer(size)
277 }
278
279 fn push_clip(&mut self, clip: Rect) {
280 self.add_command(PendingCommand::PushClip(clip));
281 }
282
283 fn push_clip_rounded(&mut self, clip: RoundedRect) {
284 self.add_command(PendingCommand::PushRoundedClip(clip));
285 }
286
287 fn push_clip_region(&mut self, region: &dyn IPlatformRenderInterfaceRegion) {
288 self.flush();
289 self.inner.push_clip_region(region);
290 }
291
292 fn pop_clip(&mut self) {
293 self.pop(PendingCommandType::PushClip, |inner| inner.pop_clip());
294 }
295
296 fn push_layer(&mut self, bounds: Rect) {
297 self.flush();
298 self.inner.push_layer(bounds);
299 }
300
301 fn pop_layer(&mut self) {
302 self.flush();
303 self.inner.pop_layer();
304 }
305
306 fn push_opacity(&mut self, opacity: f64, bounds: Option<Rect>) {
307 self.add_command(PendingCommand::PushOpacity(opacity, bounds));
308 }
309
310 fn pop_opacity(&mut self) {
311 self.pop(PendingCommandType::PushOpacity, |inner| inner.pop_opacity());
312 }
313
314 fn push_opacity_mask(&mut self, mask: &dyn IBrush, bounds: Rect) {
315 self.flush();
316 self.save_transform();
317 self.inner.push_opacity_mask(mask, bounds);
318 }
319
320 fn pop_opacity_mask(&mut self) {
321 self.pop(PendingCommandType::PushOpacityMask, |inner| inner.pop_opacity_mask());
322 }
323
324 fn push_geometry_clip(&mut self, clip: &dyn IGeometryImpl) {
325 self.flush();
326 self.save_transform();
327 self.inner.push_geometry_clip(clip);
328 }
329
330 fn pop_geometry_clip(&mut self) {
331 self.pop(PendingCommandType::PushGeometryClip, |inner| inner.pop_geometry_clip());
332 }
333
334 fn push_render_options(&mut self, render_options: RenderOptions) {
335 self.add_command(PendingCommand::PushRenderOptions(render_options));
336 }
337
338 fn pop_render_options(&mut self) {
339 self.pop(PendingCommandType::PushRenderOptions, |inner| inner.pop_render_options());
340 }
341
342 fn push_text_options(&mut self, text_options: TextOptions) {
343 self.add_command(PendingCommand::PushTextOptions(text_options));
344 }
345
346 fn pop_text_options(&mut self) {
347 self.pop(PendingCommandType::PushTextOptions, |inner| inner.pop_text_options());
348 }
349
350 fn get_feature(&mut self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
351 self.flush();
352 self.inner.get_feature(feature_type)
353 }
354
355 fn as_drawing_context_impl_with_effects(&mut self) -> Option<&mut dyn IDrawingContextImplWithEffects> {
356 Some(self)
357 }
358
359 fn as_drawing_context_with_acrylic_like_support(
360 &mut self,
361 ) -> Option<&mut dyn IDrawingContextWithAcrylicLikeSupport> {
362 Some(self)
363 }
364
365 fn as_any_mut(&mut self) -> &mut dyn Any {
366 self
367 }
368
369 fn dispose(&mut self) {
370 self.flush();
371 debug_assert!(self.transform_stack.is_empty());
372 self.transform_stack.clear();
373 }
374}
375
376impl IDrawingContextWithAcrylicLikeSupport for CompositorDrawingContextProxy {
377 fn draw_rectangle_with_material(&mut self, material: &dyn IExperimentalAcrylicMaterial, rect: RoundedRect) {
378 self.flush();
379 match self.inner.as_drawing_context_with_acrylic_like_support() {
380 Some(acrylic) => acrylic.draw_rectangle_with_material(material, rect),
381 None => self.inner.draw_rectangle(
382 Some(&ImmutableSolidColorBrush::new(material.fallback_color())),
383 None,
384 rect,
385 &BoxShadows::default(),
386 ),
387 }
388 }
389}
390
391impl IDrawingContextImplWithEffects for CompositorDrawingContextProxy {
392 fn push_effect(&mut self, clip_rect: Option<Rect>, effect: &dyn IEffect) {
393 self.flush();
394 self.save_transform();
395 if let Some(effects) = self.inner.as_drawing_context_impl_with_effects() {
396 effects.push_effect(clip_rect, effect);
397 }
398 }
399
400 fn pop_effect(&mut self) {
401 if !self.try_discard_or_flush(PendingCommandType::PushEffect) {
402 if let Some(effects) = self.inner.as_drawing_context_impl_with_effects() {
403 effects.pop_effect();
404 }
405 self.restore_transform();
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413 use crate::rendering::testing::{DrawingLog, MockDrawingContextImpl};
414
415 fn proxy() -> (CompositorDrawingContextProxy, DrawingLog) {
416 let log = DrawingLog::new();
417 (CompositorDrawingContextProxy::new(Box::new(MockDrawingContextImpl::new(log.clone()))), log)
418 }
419
420 #[test]
421 fn a_push_popped_before_drawing_never_reaches_the_context() {
422 let (mut proxy, log) = proxy();
423 proxy.push_clip(Rect::new(0.0, 0.0, 10.0, 10.0));
424 proxy.set_transform(Matrix::create_translation(5.0, 5.0));
425 proxy.push_opacity(0.5, None);
426 proxy.pop_opacity();
427 proxy.pop_clip();
428 assert!(log.entries().is_empty(), "{:?}", log.entries());
429 assert_eq!(proxy.transform(), Matrix::create_translation(5.0, 5.0));
430 }
431
432 #[test]
433 fn drawing_flushes_pending_commands_in_order_and_pops_reach_the_context() {
434 let (mut proxy, log) = proxy();
435 proxy.push_clip(Rect::new(0.0, 0.0, 10.0, 10.0));
436 proxy.set_transform(Matrix::create_translation(5.0, 5.0));
437 proxy.draw_ellipse(None, None, Rect::new(0.0, 0.0, 1.0, 1.0));
438 let before_pop = log.entries().len();
439 assert_eq!(before_pop, 3, "{:?}", log.entries());
440 proxy.pop_clip();
441 assert_eq!(log.entries().len(), before_pop + 1);
442 assert_eq!(proxy.transform(), Matrix::IDENTITY);
444 }
445
446 #[test]
447 fn auto_flush_executes_at_once_and_post_transform_is_applied() {
448 let (mut proxy, log) = proxy();
449 proxy.set_post_transform(Some(Matrix::create_scale(2.0, 2.0)));
450 proxy.set_auto_flush(true);
451 proxy.set_transform(Matrix::create_translation(1.0, 1.0));
452 assert_eq!(log.entries().len(), 1);
453 assert_eq!(proxy.transform(), Matrix::create_translation(1.0, 1.0));
454 let mut inner = proxy.into_inner();
455 assert_eq!(inner.transform(), Matrix::create_translation(1.0, 1.0) * Matrix::create_scale(2.0, 2.0));
456 inner.dispose();
457 }
458}