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