1use crate::media::effects::IEffect;
5use crate::media::imaging::BitmapBlendingMode;
6use crate::media::immutable::ImmutableSolidColorBrush;
7use crate::media::{BoxShadows, Color, IBrush, IExperimentalAcrylicMaterial, IPen, RenderOptions, TextOptions};
8use crate::platform::{
9 IBitmapImpl, IDrawingContextImpl, IDrawingContextImplWithEffects, IDrawingContextImplWithFragments,
10 IDrawingContextLayerImpl, IDrawingFragment,
11 IDrawingContextWithAcrylicLikeSupport, IGeometryImpl, IGlyphRunImpl, IPlatformRenderInterfaceRegion,
12};
13use crate::{Matrix, PixelSize, Point, Rect, RoundedRect};
14use std::any::{Any, TypeId};
15use std::rc::Rc;
16
17#[derive(Clone, Copy, PartialEq, Eq, Debug)]
18enum PendingCommandType {
19 SetTransform,
20 PushClip,
21 PushOpacity,
22 PushOpacityMask,
23 PushGeometryClip,
24 PushRenderOptions,
25 PushTextOptions,
26 PushEffect,
27}
28
29#[derive(Clone, Copy)]
35enum PendingCommand {
36 SetTransform(Matrix),
37 PushClip(Rect),
38 PushRoundedClip(RoundedRect),
39 PushOpacity(f64, Option<Rect>),
40 PushRenderOptions(RenderOptions),
41 PushTextOptions(TextOptions),
42}
43
44impl PendingCommand {
45 fn command_type(&self) -> PendingCommandType {
46 match self {
47 PendingCommand::SetTransform(_) => PendingCommandType::SetTransform,
48 PendingCommand::PushClip(_) | PendingCommand::PushRoundedClip(_) => PendingCommandType::PushClip,
49 PendingCommand::PushOpacity(..) => PendingCommandType::PushOpacity,
50 PendingCommand::PushRenderOptions(_) => PendingCommandType::PushRenderOptions,
51 PendingCommand::PushTextOptions(_) => PendingCommandType::PushTextOptions,
52 }
53 }
54}
55
56pub struct CompositorDrawingContextProxy {
59 inner: Box<dyn IDrawingContextImpl>,
60 transform_stack: Vec<Matrix>,
61 post_transform: Option<Matrix>,
62 reported_transform: Matrix,
65 effective_transform: Matrix,
68 commands: Vec<PendingCommand>,
69 auto_flush: bool,
70}
71
72impl CompositorDrawingContextProxy {
73 pub fn new(inner: Box<dyn IDrawingContextImpl>) -> Self {
74 Self {
75 inner,
76 transform_stack: Vec::new(),
77 post_transform: None,
78 reported_transform: Matrix::IDENTITY,
79 effective_transform: Matrix::IDENTITY,
80 commands: Vec::new(),
81 auto_flush: false,
82 }
83 }
84
85 pub fn into_inner(mut self) -> Box<dyn IDrawingContextImpl> {
87 self.flush();
88 self.inner
89 }
90
91 pub fn post_transform(&self) -> Option<Matrix> {
92 self.post_transform
93 }
94
95 pub fn set_post_transform(&mut self, value: Option<Matrix>) {
96 self.post_transform = value;
97 }
98
99 fn set_impl_transform(&mut self, mut m: Matrix) {
100 self.effective_transform = m;
101 if let Some(post_transform) = self.post_transform {
102 m = m * post_transform;
103 }
104 self.inner.set_transform(m);
105 }
106
107 fn save_transform(&mut self) {
108 self.transform_stack.push(self.effective_transform);
109 }
110
111 fn restore_transform(&mut self) {
112 let transform = self.transform_stack.pop().expect("a transform was saved");
113 self.reported_transform = transform;
114 self.effective_transform = transform;
115 }
116
117 pub fn auto_flush(&self) -> bool {
120 self.auto_flush
121 }
122
123 pub fn set_auto_flush(&mut self, value: bool) {
124 self.auto_flush = value;
125 if value {
126 self.flush();
127 }
128 }
129
130 fn set_transform_command(&mut self, m: Matrix) {
131 if self.auto_flush {
132 self.set_impl_transform(m);
133 return;
134 }
135
136 let cmd = PendingCommand::SetTransform(m);
137 match self.commands.last_mut() {
138 Some(last) if last.command_type() == PendingCommandType::SetTransform => *last = cmd,
139 _ => self.commands.push(cmd),
140 }
141 }
142
143 fn try_discard_or_flush(&mut self, command_type: PendingCommandType) -> bool {
144 for c in (0..self.commands.len()).rev() {
145 let current = self.commands[c].command_type();
146 if current == PendingCommandType::SetTransform {
147 continue;
148 }
149 if current == command_type {
150 self.commands.truncate(c);
151 return true;
152 }
153 break;
154 }
155
156 self.flush();
158 false
159 }
160
161 fn add_command(&mut self, command: PendingCommand) {
162 if self.auto_flush {
163 self.exec_command(command);
164 } else {
165 self.commands.push(command);
166 }
167 }
168
169 fn exec_command(&mut self, cmd: PendingCommand) {
170 if let PendingCommand::SetTransform(transform) = cmd {
171 self.set_impl_transform(transform);
172 return;
173 }
174
175 self.save_transform();
176
177 match cmd {
178 PendingCommand::PushOpacity(opacity, bounds) => self.inner.push_opacity(opacity, bounds),
179 PendingCommand::PushClip(rect) => self.inner.push_clip(rect),
180 PendingCommand::PushRoundedClip(rect) => self.inner.push_clip_rounded(rect),
181 PendingCommand::PushRenderOptions(options) => self.inner.push_render_options(options),
182 PendingCommand::PushTextOptions(options) => self.inner.push_text_options(options),
183 PendingCommand::SetTransform(_) => {}
184 }
185 }
186
187 pub fn flush(&mut self) {
188 let commands = std::mem::take(&mut self.commands);
189 for command in &commands {
190 self.exec_command(*command);
191 }
192 let mut commands = commands;
193 commands.clear();
194 self.commands = commands;
195 }
196
197 fn pop(&mut self, command_type: PendingCommandType, pop: impl FnOnce(&mut dyn IDrawingContextImpl)) {
198 if !self.try_discard_or_flush(command_type) {
199 pop(&mut *self.inner);
200 self.restore_transform();
201 }
202 }
203}
204
205impl IDrawingContextImpl for CompositorDrawingContextProxy {
206 fn transform(&self) -> Matrix {
207 self.reported_transform
208 }
209
210 fn set_transform(&mut self, value: Matrix) {
211 self.reported_transform = value;
212 self.set_transform_command(value);
213 }
214
215 fn clear(&mut self, color: Color) {
216 self.flush();
217 self.inner.clear(color);
218 }
219
220 fn draw_bitmap(&mut self, source: &dyn IBitmapImpl, opacity: f64, source_rect: Rect, dest_rect: Rect) {
221 self.flush();
222 self.inner.draw_bitmap(source, opacity, source_rect, dest_rect);
223 }
224
225 fn draw_bitmap_with_mask(
226 &mut self,
227 source: &dyn IBitmapImpl,
228 opacity_mask: &dyn IBrush,
229 opacity_mask_rect: Rect,
230 dest_rect: Rect,
231 ) {
232 self.flush();
233 self.inner.draw_bitmap_with_mask(source, opacity_mask, opacity_mask_rect, dest_rect);
234 }
235
236 fn draw_line(&mut self, pen: Option<&dyn IPen>, p1: Point, p2: Point) {
237 self.flush();
238 self.inner.draw_line(pen, p1, p2);
239 }
240
241 fn draw_geometry(&mut self, brush: Option<&dyn IBrush>, pen: Option<&dyn IPen>, geometry: &dyn IGeometryImpl) {
242 self.flush();
243 self.inner.draw_geometry(brush, pen, geometry);
244 }
245
246 fn draw_rectangle(
247 &mut self,
248 brush: Option<&dyn IBrush>,
249 pen: Option<&dyn IPen>,
250 rect: RoundedRect,
251 box_shadows: &BoxShadows,
252 ) {
253 self.flush();
254 self.inner.draw_rectangle(brush, pen, rect, box_shadows);
255 }
256
257 fn draw_region(
258 &mut self,
259 brush: Option<&dyn IBrush>,
260 pen: Option<&dyn IPen>,
261 region: &dyn IPlatformRenderInterfaceRegion,
262 ) {
263 self.flush();
264 self.inner.draw_region(brush, pen, region);
265 }
266
267 fn draw_ellipse(&mut self, brush: Option<&dyn IBrush>, pen: Option<&dyn IPen>, rect: Rect) {
268 self.flush();
269 self.inner.draw_ellipse(brush, pen, rect);
270 }
271
272 fn draw_glyph_run(&mut self, foreground: Option<&dyn IBrush>, glyph_run: &dyn IGlyphRunImpl) {
273 self.flush();
274 self.inner.draw_glyph_run(foreground, glyph_run);
275 }
276
277 fn create_layer(&mut self, size: PixelSize) -> Rc<dyn IDrawingContextLayerImpl> {
278 self.inner.create_layer(size)
279 }
280
281 fn push_clip(&mut self, clip: Rect) {
282 self.add_command(PendingCommand::PushClip(clip));
283 }
284
285 fn push_clip_rounded(&mut self, clip: RoundedRect) {
286 self.add_command(PendingCommand::PushRoundedClip(clip));
287 }
288
289 fn push_clip_region(&mut self, region: &dyn IPlatformRenderInterfaceRegion) {
290 self.flush();
291 self.inner.push_clip_region(region);
292 }
293
294 fn pop_clip(&mut self) {
295 self.pop(PendingCommandType::PushClip, |inner| inner.pop_clip());
296 }
297
298 fn push_layer(&mut self, bounds: Rect) {
299 self.flush();
300 self.inner.push_layer(bounds);
301 }
302
303 fn pop_layer(&mut self) {
304 self.flush();
305 self.inner.pop_layer();
306 }
307
308 fn push_opacity(&mut self, opacity: f64, bounds: Option<Rect>) {
309 self.add_command(PendingCommand::PushOpacity(opacity, bounds));
310 }
311
312 fn pop_opacity(&mut self) {
313 self.pop(PendingCommandType::PushOpacity, |inner| inner.pop_opacity());
314 }
315
316 fn push_opacity_mask(&mut self, mask: &dyn IBrush, bounds: Rect) {
317 self.flush();
318 self.save_transform();
319 self.inner.push_opacity_mask(mask, bounds);
320 }
321
322 fn pop_opacity_mask(&mut self) {
323 self.pop(PendingCommandType::PushOpacityMask, |inner| inner.pop_opacity_mask());
324 }
325
326 fn push_geometry_clip(&mut self, clip: &dyn IGeometryImpl) {
327 self.flush();
328 self.save_transform();
329 self.inner.push_geometry_clip(clip);
330 }
331
332 fn pop_geometry_clip(&mut self) {
333 self.pop(PendingCommandType::PushGeometryClip, |inner| inner.pop_geometry_clip());
334 }
335
336 fn push_render_options(&mut self, render_options: RenderOptions) {
337 self.add_command(PendingCommand::PushRenderOptions(render_options));
338 }
339
340 fn pop_render_options(&mut self) {
341 self.pop(PendingCommandType::PushRenderOptions, |inner| inner.pop_render_options());
342 }
343
344 fn push_text_options(&mut self, text_options: TextOptions) {
345 self.add_command(PendingCommand::PushTextOptions(text_options));
346 }
347
348 fn pop_text_options(&mut self) {
349 self.pop(PendingCommandType::PushTextOptions, |inner| inner.pop_text_options());
350 }
351
352 fn get_feature(&mut self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
353 self.flush();
354 self.inner.get_feature(feature_type)
355 }
356
357 fn try_push_compositing_layer(&mut self, blend: BitmapBlendingMode, opacity: f64, bounds: Rect) -> bool {
358 self.flush();
359 if self.inner.try_push_compositing_layer(blend, opacity, bounds) {
360 self.save_transform();
361 true
362 } else {
363 false
364 }
365 }
366
367 fn pop_compositing_layer(&mut self) {
368 self.flush();
369 self.inner.pop_compositing_layer();
370 self.restore_transform();
371 }
372
373 fn as_drawing_context_impl_with_effects(&mut self) -> Option<&mut dyn IDrawingContextImplWithEffects> {
374 Some(self)
375 }
376
377 fn as_drawing_context_with_acrylic_like_support(
378 &mut self,
379 ) -> Option<&mut dyn IDrawingContextWithAcrylicLikeSupport> {
380 Some(self)
381 }
382
383 fn as_drawing_context_impl_with_layer_effects(
384 &mut self,
385 ) -> Option<&mut dyn crate::platform::IDrawingContextImplWithLayerEffects> {
386 match self.inner.as_drawing_context_impl_with_layer_effects() {
387 Some(_) => Some(self),
388 None => None,
389 }
390 }
391
392 fn as_drawing_context_impl_with_fragments(&mut self) -> Option<&mut dyn IDrawingContextImplWithFragments> {
393 match self.inner.as_drawing_context_impl_with_fragments() {
394 Some(_) => Some(self),
395 None => None,
396 }
397 }
398
399 fn as_any_mut(&mut self) -> &mut dyn Any {
400 self
401 }
402
403 fn dispose(&mut self) {
404 self.flush();
405 debug_assert!(self.transform_stack.is_empty());
406 self.transform_stack.clear();
407 }
408}
409
410impl IDrawingContextWithAcrylicLikeSupport for CompositorDrawingContextProxy {
411 fn draw_rectangle_with_material(&mut self, material: &dyn IExperimentalAcrylicMaterial, rect: RoundedRect) {
412 self.flush();
413 match self.inner.as_drawing_context_with_acrylic_like_support() {
414 Some(acrylic) => acrylic.draw_rectangle_with_material(material, rect),
415 None => self.inner.draw_rectangle(
416 Some(&ImmutableSolidColorBrush::new(material.fallback_color())),
417 None,
418 rect,
419 &BoxShadows::default(),
420 ),
421 }
422 }
423}
424
425impl crate::platform::IDrawingContextImplWithLayerEffects for CompositorDrawingContextProxy {
426 fn push_blend_layer(&mut self, bounds: Rect, mode: crate::media::imaging::BitmapBlendingMode) {
427 self.flush();
428 match self.inner.as_drawing_context_impl_with_layer_effects() {
429 Some(layers) => layers.push_blend_layer(bounds, mode),
430 None => self.inner.push_layer(bounds),
431 }
432 }
433
434 fn pop_blend_layer(&mut self) {
435 self.flush();
436 match self.inner.as_drawing_context_impl_with_layer_effects() {
437 Some(layers) => layers.pop_blend_layer(),
438 None => self.inner.pop_layer(),
439 }
440 }
441
442 fn push_color_matrix_layer(
443 &mut self,
444 bounds: Rect,
445 matrix: &crate::media::ColorMatrix,
446 alpha_mode: crate::media::ColorMatrixAlphaMode,
447 ) {
448 self.flush();
449 match self.inner.as_drawing_context_impl_with_layer_effects() {
450 Some(layers) => layers.push_color_matrix_layer(bounds, matrix, alpha_mode),
451 None => self.inner.push_layer(bounds),
452 }
453 }
454
455 fn pop_color_matrix_layer(&mut self) {
456 self.flush();
457 match self.inner.as_drawing_context_impl_with_layer_effects() {
458 Some(layers) => layers.pop_color_matrix_layer(),
459 None => self.inner.pop_layer(),
460 }
461 }
462
463 fn push_multiply_layer(&mut self, bounds: Rect, factors: &crate::platform::MultiplyLayerFactors) {
464 self.flush();
465 match self.inner.as_drawing_context_impl_with_layer_effects() {
466 Some(layers) => layers.push_multiply_layer(bounds, factors),
467 None => self.inner.push_layer(bounds),
468 }
469 }
470
471 fn pop_multiply_layer(&mut self) {
472 self.flush();
473 match self.inner.as_drawing_context_impl_with_layer_effects() {
474 Some(layers) => layers.pop_multiply_layer(),
475 None => self.inner.pop_layer(),
476 }
477 }
478}
479
480impl IDrawingContextImplWithEffects for CompositorDrawingContextProxy {
481 fn push_effect(&mut self, clip_rect: Option<Rect>, effect: &dyn IEffect) {
482 self.flush();
483 self.save_transform();
484 if let Some(effects) = self.inner.as_drawing_context_impl_with_effects() {
485 effects.push_effect(clip_rect, effect);
486 }
487 }
488
489 fn pop_effect(&mut self) {
490 if !self.try_discard_or_flush(PendingCommandType::PushEffect) {
491 if let Some(effects) = self.inner.as_drawing_context_impl_with_effects() {
492 effects.pop_effect();
493 }
494 self.restore_transform();
495 }
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502 use crate::rendering::testing::{DrawingLog, MockDrawingContextImpl};
503
504 fn proxy() -> (CompositorDrawingContextProxy, DrawingLog) {
505 let log = DrawingLog::new();
506 (CompositorDrawingContextProxy::new(Box::new(MockDrawingContextImpl::new(log.clone()))), log)
507 }
508
509 #[test]
510 fn compositing_layer_flushes_pending_clips_and_restores_the_proxy_transform() {
511 let log = DrawingLog::new();
512 let mut implementation = MockDrawingContextImpl::new(log.clone());
513 implementation.supports_compositing_layers = true;
514 implementation.log_transforms = false;
515 let mut proxy = CompositorDrawingContextProxy::new(Box::new(implementation));
516 let bounds = Rect::new(0.0, 0.0, 10.0, 10.0);
517 proxy.push_clip(bounds);
518 let at_push = Matrix::create_translation(5.0, 6.0);
519 proxy.set_transform(at_push);
520 assert!(proxy.try_push_compositing_layer(BitmapBlendingMode::Multiply, 0.5, bounds));
521 proxy.set_transform(Matrix::create_scale(2.0, 2.0));
522 proxy.draw_ellipse(None, None, bounds);
523 proxy.pop_compositing_layer();
524 assert_eq!(proxy.transform(), at_push);
525 proxy.pop_clip();
526 assert_eq!(proxy.transform(), Matrix::IDENTITY);
527 assert_eq!(log.entries(), [
528 "PushClip 0, 0, 10, 10", "PushCompositingLayer Multiply 0.5 0, 0, 10, 10",
529 "DrawEllipse none none 0, 0, 10, 10", "PopCompositingLayer", "PopClip",
530 ]);
531 }
532
533 #[test]
534 fn a_push_popped_before_drawing_never_reaches_the_context() {
535 let (mut proxy, log) = proxy();
536 proxy.push_clip(Rect::new(0.0, 0.0, 10.0, 10.0));
537 proxy.set_transform(Matrix::create_translation(5.0, 5.0));
538 proxy.push_opacity(0.5, None);
539 proxy.pop_opacity();
540 proxy.pop_clip();
541 assert!(log.entries().is_empty(), "{:?}", log.entries());
542 assert_eq!(proxy.transform(), Matrix::create_translation(5.0, 5.0));
543 }
544
545 #[test]
546 fn drawing_flushes_pending_commands_in_order_and_pops_reach_the_context() {
547 let (mut proxy, log) = proxy();
548 proxy.push_clip(Rect::new(0.0, 0.0, 10.0, 10.0));
549 proxy.set_transform(Matrix::create_translation(5.0, 5.0));
550 proxy.draw_ellipse(None, None, Rect::new(0.0, 0.0, 1.0, 1.0));
551 let before_pop = log.entries().len();
552 assert_eq!(before_pop, 3, "{:?}", log.entries());
553 proxy.pop_clip();
554 assert_eq!(log.entries().len(), before_pop + 1);
555 assert_eq!(proxy.transform(), Matrix::IDENTITY);
557 }
558
559 #[test]
560 fn auto_flush_executes_at_once_and_post_transform_is_applied() {
561 let (mut proxy, log) = proxy();
562 proxy.set_post_transform(Some(Matrix::create_scale(2.0, 2.0)));
563 proxy.set_auto_flush(true);
564 proxy.set_transform(Matrix::create_translation(1.0, 1.0));
565 assert_eq!(log.entries().len(), 1);
566 assert_eq!(proxy.transform(), Matrix::create_translation(1.0, 1.0));
567 let mut inner = proxy.into_inner();
568 assert_eq!(inner.transform(), Matrix::create_translation(1.0, 1.0) * Matrix::create_scale(2.0, 2.0));
569 inner.dispose();
570 }
571}
572
573impl IDrawingContextImplWithFragments for CompositorDrawingContextProxy {
574 fn begin_fragment(&mut self) {
575 self.flush();
576 if let Some(inner) = self.inner.as_drawing_context_impl_with_fragments() {
577 inner.begin_fragment();
578 }
579 }
580
581 fn end_fragment(&mut self) -> Option<Rc<dyn IDrawingFragment>> {
582 self.flush();
583 self.inner.as_drawing_context_impl_with_fragments()?.end_fragment()
584 }
585
586 fn draw_fragment(&mut self, fragment: &Rc<dyn IDrawingFragment>) -> bool {
587 self.flush();
588 self.inner.as_drawing_context_impl_with_fragments().is_some_and(|inner| inner.draw_fragment(fragment))
589 }
590}