Skip to main content

ferroui_diagnostics/diagnostics/
visual_extensions.rs

1//! Port of `Diagnostics/VisualExtensions.cs`.
2
3use ferroui_base::data::BindingPriority;
4use ferroui_base::media::imaging::{PngBitmapEncoderOptions, RenderTargetBitmap};
5use ferroui_base::media::{ITransform, RectangleGeometry, TranslateTransform};
6use ferroui_base::reactive::IDisposable;
7use ferroui_base::{PixelSize, Rect, Ref, RelativePoint, RelativeUnit, Vector, Visual};
8use ferroui_controls::Control;
9use std::io::Write;
10use std::rc::Rc;
11
12/// Extension methods of controls for DevTools.
13pub trait VisualExtensions {
14    /// Render control to the destination stream, at 96 DPI.
15    fn render_to(&self, destination: &mut dyn Write) -> std::io::Result<()> {
16        self.render_to_with_dpi(destination, 96.0)
17    }
18
19    /// Render control to the destination stream.
20    ///
21    /// `destination` is the destination stream; `dpi` the dpi quality. An error is what writing
22    /// the image to the stream fails with.
23    fn render_to_with_dpi(&self, destination: &mut dyn Write, dpi: f64) -> std::io::Result<()>;
24}
25
26impl VisualExtensions for Control {
27    fn render_to_with_dpi(&self, destination: &mut dyn Write, dpi: f64) -> std::io::Result<()> {
28        // Deviation (DEVIATIONS.md, Developer tools):
29        // `CompositionVisual.TryGetServerGlobalTransform()` (the transform of the visual on the
30        // render thread) does not exist in the port; the transform of the visual to the root of its
31        // visual tree is the same transform, computed from the layout.
32        if self.composition_visual().is_none() {
33            return Ok(());
34        }
35        let Some(transform) = self.visual_root().and_then(|visual_root| self.transform_to_visual(&visual_root)) else {
36            return Ok(());
37        };
38
39        let rect = Rect::from_size(self.bounds().size()).transform_to_aabb(transform);
40        let top = rect.top_left();
41        let pixel_size = PixelSize::new(rect.width as i32, rect.height as i32);
42        let dpi_vector = Vector::new(dpi, dpi);
43
44        // get Visual root
45        let root: Ref<Control> = self
46            .visual_root()
47            .or_else(|| self.get_visual_root())
48            .and_then(|root| root.cast::<Control>())
49            .unwrap_or_else(|| self.to_ref());
50
51        // Set clip region
52        let clip_region = RectangleGeometry::with_rect(rect);
53        let clip_to_bounds_setter =
54            root.set_value_with_priority(Visual::clip_to_bounds_property(), true, BindingPriority::Animation);
55        let clip_setter =
56            root.set_value_with_priority(Visual::clip_property(), Some(clip_region.upcast()), BindingPriority::Animation);
57
58        // Translate origin
59        let render_transform_origin_setter = root.set_value_with_priority(
60            Visual::render_transform_origin_property(),
61            RelativePoint::from_point(top, RelativeUnit::Absolute),
62            BindingPriority::Animation,
63        );
64
65        let translate: Rc<dyn ITransform> = (&TranslateTransform::with_offset(-top.x, -top.y)).into();
66        let render_transform_setter = root.set_value_with_priority(
67            Visual::render_transform_property(),
68            Some(translate),
69            BindingPriority::Animation,
70        );
71
72        let result = {
73            let bitmap = RenderTargetBitmap::with_dpi(pixel_size, dpi_vector);
74            bitmap.render(&root.clone().upcast());
75            let saved = bitmap.save(&mut *destination, &PngBitmapEncoderOptions::DEFAULT.into());
76            bitmap.dispose();
77            saved
78        };
79
80        // Restore values before trasformation
81        let setters: [Option<Rc<dyn IDisposable>>; 4] =
82            [render_transform_setter, render_transform_origin_setter, clip_setter, clip_to_bounds_setter];
83        for setter in setters.into_iter().flatten() {
84            setter.dispose();
85        }
86        self.invalidate_visual();
87
88        result
89    }
90}