Skip to main content

ferroui_base/rendering/composition/server/
drawing_context_proxy.rs

1//! `CompositorDrawingContextProxy` (upstream `DrawingContextProxy.cs` and
2//! `DrawingContextProxy.PendingCommands.cs`).
3
4use 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/// A command whose execution is postponed until something is drawn, so
30/// that a push that is popped before anything was drawn costs nothing.
31///
32/// The opacity mask, geometry clip and effect pushes carry a borrowed
33/// object and cannot be postponed here: they flush and execute at once.
34#[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
56/// A drawing context that forwards to another one, postponing state
57/// changes until they are needed and applying an optional post transform.
58pub struct CompositorDrawingContextProxy {
59    inner: Box<dyn IDrawingContextImpl>,
60    transform_stack: Vec<Matrix>,
61    post_transform: Option<Matrix>,
62    // Transform that was most recently passed to set_transform or restored by a pop operation.
63    // We use it to report the transform that would correspond to the current state if all commands were executed
64    reported_transform: Matrix,
65    // Transform that was most recently passed to set_impl_transform or restored by a pop operation.
66    // We use it to save the effective transform before executing a push operation
67    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    /// Flushes the pending commands and returns the wrapped context.
86    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    // --- pending commands ------------------------------------------------------
118
119    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        // We've failed to collapse PushX,SetTransform,PopX stack, so we need to execute any pending commands
157        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        // The transform saved by the push is restored.
556        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}