Skip to main content

ferroui_xaml_interactions_custom/writeable_bitmap/
writeable_bitmap_render_behavior.rs

1//! Port of `WriteableBitmap/WriteableBitmapRenderBehavior.cs`.
2
3use crate::{IWriteableBitmapRenderer, WriteableBitmapRef};
4use ferroui_base::media::imaging::WriteableBitmap;
5use ferroui_base::platform::{AlphaFormat, PixelFormat};
6use ferroui_base::reactive::IDisposable;
7use ferroui_base::threading::DispatcherTimer;
8use ferroui_base::{
9    ferro_class, ferro_class_info, ferro_properties, instantiate, DirectProperty, FerroObjectImpl, FerroProperty,
10    PixelSize, Ref, StaticType, StyledElementImpl, StyledProperty, Vector,
11};
12use ferroui_controls::Image;
13use ferroui_xaml_interactivity::{StyledElementBehaviorImpl, StyledElementBehaviorOfT};
14use std::cell::RefCell;
15use std::rc::Rc;
16use std::time::Duration;
17
18/// Creates a [`WriteableBitmap`] and updates it using a renderer on a timer.
19#[repr(C)]
20pub struct WriteableBitmapRenderBehavior {
21    base: StyledElementBehaviorOfT,
22    timer: RefCell<Option<(Rc<DispatcherTimer>, Rc<dyn IDisposable>)>>,
23    bitmap: RefCell<Option<WriteableBitmapRef>>,
24}
25
26ferro_class!(WriteableBitmapRenderBehavior: StyledElementBehaviorOfT);
27ferro_class_info!(WriteableBitmapRenderBehavior { new: WriteableBitmapRenderBehavior::new });
28ferroui_base::ferro_impl_classes!(WriteableBitmapRenderBehavior: FerroObjectImpl, StyledElementImpl);
29
30ferro_properties! {
31    impl WriteableBitmapRenderBehavior {
32        /// Identifies the `PixelWidth` property.
33        pub fn pixel_width_property() -> StyledProperty<i32> {
34            FerroProperty::register::<WriteableBitmapRenderBehavior, _>("PixelWidth", 256)
35        }
36
37        /// Identifies the `PixelHeight` property.
38        pub fn pixel_height_property() -> StyledProperty<i32> {
39            FerroProperty::register::<WriteableBitmapRenderBehavior, _>("PixelHeight", 256)
40        }
41
42        /// Identifies the `Renderer` property.
43        pub fn renderer_property() -> StyledProperty<Option<Rc<dyn IWriteableBitmapRenderer>>> {
44            FerroProperty::register::<WriteableBitmapRenderBehavior, _>("Renderer", None)
45        }
46
47        /// Identifies the `Bitmap` property.
48        pub fn bitmap_property() -> DirectProperty<WriteableBitmapRenderBehavior, Option<WriteableBitmapRef>> {
49            FerroProperty::register_direct::<WriteableBitmapRenderBehavior, _>(
50                "Bitmap",
51                |behavior| behavior.bitmap(),
52                None,
53                None,
54            )
55        }
56    }
57}
58
59ferroui_base::ferro_overrides! {
60    impl StyledElementBehaviorImpl for WriteableBitmapRenderBehavior {
61        fn on_attached_to_visual_tree(this: &Self) {
62            let bitmap = WriteableBitmapRef::new(WriteableBitmap::new(
63                PixelSize::new(this.pixel_width(), this.pixel_height()),
64                Vector::new(96.0, 96.0),
65                Some(PixelFormat::BGRA8888),
66                Some(AlphaFormat::Unpremul),
67            ));
68            this.set_bitmap(Some(bitmap.clone()));
69
70            if let Some(associated_object) = this.associated_object_as::<Image>() {
71                associated_object.set_source(Some(bitmap.as_image()));
72            }
73
74            let timer = DispatcherTimer::new();
75            timer.set_interval(Duration::from_millis(16));
76            let behavior = this.to_ref().downgrade();
77            let tick = timer.tick(move |_timer: &DispatcherTimer| {
78                if let Some(behavior) = behavior.upgrade() {
79                    behavior.on_tick();
80                }
81            });
82            timer.start();
83            *this.timer.borrow_mut() = Some((timer, tick));
84        }
85
86        fn on_detached_from_visual_tree(this: &Self) {
87            let timer = this.timer.borrow_mut().take();
88            if let Some((timer, tick)) = timer {
89                tick.dispose();
90                timer.stop();
91            }
92
93            if let Some(associated_object) = this.associated_object_as::<Image>() {
94                associated_object.set_source(None);
95            }
96
97            if let Some(bitmap) = this.bitmap() {
98                bitmap.dispose();
99            }
100            this.set_bitmap(None);
101        }
102    }
103}
104
105impl WriteableBitmapRenderBehavior {
106    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
107    pub fn construct() -> Self {
108        Self {
109            base: StyledElementBehaviorOfT::construct(<Image as StaticType>::TYPE),
110            timer: RefCell::new(None),
111            bitmap: RefCell::new(None),
112        }
113    }
114
115    /// Creates a behavior.
116    pub fn new() -> Ref<Self> {
117        instantiate(Self::construct())
118    }
119
120    /// Gets or sets the width of the bitmap in pixels. This is a property of the object model.
121    pub fn pixel_width(&self) -> i32 {
122        self.get_value(Self::pixel_width_property())
123    }
124
125    pub fn set_pixel_width(&self, value: i32) {
126        self.set_value(Self::pixel_width_property(), value)
127    }
128
129    /// Gets or sets the height of the bitmap in pixels. This is a property of the object model.
130    pub fn pixel_height(&self) -> i32 {
131        self.get_value(Self::pixel_height_property())
132    }
133
134    pub fn set_pixel_height(&self, value: i32) {
135        self.set_value(Self::pixel_height_property(), value)
136    }
137
138    /// Gets or sets the renderer used to update the bitmap. This is a property of the object
139    /// model.
140    pub fn renderer(&self) -> Option<Rc<dyn IWriteableBitmapRenderer>> {
141        self.get_value(Self::renderer_property())
142    }
143
144    pub fn set_renderer(&self, value: Option<Rc<dyn IWriteableBitmapRenderer>>) {
145        self.set_value(Self::renderer_property(), value)
146    }
147
148    /// Gets the created bitmap.
149    pub fn bitmap(&self) -> Option<WriteableBitmapRef> {
150        self.bitmap.borrow().clone()
151    }
152
153    fn set_bitmap(&self, value: Option<WriteableBitmapRef>) {
154        self.set_and_raise(Self::bitmap_property(), &self.bitmap, value);
155    }
156
157    fn on_tick(&self) {
158        let (Some(bitmap), Some(renderer)) = (self.bitmap(), self.renderer()) else {
159            return;
160        };
161
162        renderer.render(&bitmap);
163        if let Some(associated_object) = self.associated_object_as::<Image>() {
164            associated_object.invalidate_visual();
165        }
166    }
167}