Skip to main content

ferroui_xaml_interactions_custom/render_target/
render_target_bitmap_behavior.rs

1//! Port of `RenderTarget/RenderTargetBitmapBehavior.cs`.
2
3use crate::{IRenderTargetBitmapRenderHost, IRenderTargetBitmapRenderer};
4use ferroui_base::animation::TimeSpan;
5use ferroui_base::media::imaging::RenderTargetBitmap;
6use ferroui_base::media::IImage;
7use ferroui_base::reactive::IDisposable;
8use ferroui_base::threading::{Dispatcher, DispatcherTimer};
9use ferroui_base::{
10    ferro_class, ferro_class_info, ferro_properties, instantiate, FerroObject, FerroObjectImpl, FerroObjectImplExt,
11    FerroProperty, PixelSize, Ref, StaticType, StyledElementImpl, StyledProperty, Vector,
12};
13use ferroui_controls::Image;
14use ferroui_xaml_interactivity::{StyledElementBehaviorImpl, StyledElementBehaviorImplExt, StyledElementBehaviorOfT};
15use std::cell::{Cell, RefCell};
16use std::rc::Rc;
17use std::time::Duration;
18
19/// Behavior that draws into a [`RenderTargetBitmap`] and assigns it to the associated [`Image`].
20#[repr(C)]
21pub struct RenderTargetBitmapBehavior {
22    base: StyledElementBehaviorOfT,
23    bitmap: RefCell<Option<Rc<RenderTargetBitmap>>>,
24    timer: RefCell<Option<(Rc<DispatcherTimer>, Rc<dyn IDisposable>)>>,
25    // Deviation (DEVIATIONS.md, Behaviors): the stopwatch of the reference is the clock of the
26    // dispatcher (milliseconds), started when the behavior is created.
27    stopwatch_start: Cell<Option<i64>>,
28}
29
30ferro_class!(RenderTargetBitmapBehavior: StyledElementBehaviorOfT);
31ferro_class_info!(RenderTargetBitmapBehavior {
32    new: RenderTargetBitmapBehavior::new,
33    interfaces: [
34        Rc<dyn IRenderTargetBitmapRenderHost> => render_host_of,
35    ],
36});
37ferroui_base::ferro_impl_classes!(RenderTargetBitmapBehavior: StyledElementImpl);
38
39ferro_properties! {
40    impl RenderTargetBitmapBehavior {
41        /// Identifies the `PixelWidth` property.
42        pub fn pixel_width_property() -> StyledProperty<i32> {
43            FerroProperty::register::<RenderTargetBitmapBehavior, _>("PixelWidth", 200)
44        }
45
46        /// Identifies the `PixelHeight` property.
47        pub fn pixel_height_property() -> StyledProperty<i32> {
48            FerroProperty::register::<RenderTargetBitmapBehavior, _>("PixelHeight", 200)
49        }
50
51        /// Identifies the `Dpi` property.
52        pub fn dpi_property() -> StyledProperty<Vector> {
53            FerroProperty::register::<RenderTargetBitmapBehavior, _>("Dpi", Vector::new(96.0, 96.0))
54        }
55
56        /// Identifies the `Renderer` property.
57        pub fn renderer_property() -> StyledProperty<Option<Rc<dyn IRenderTargetBitmapRenderer>>> {
58            FerroProperty::register::<RenderTargetBitmapBehavior, _>("Renderer", None)
59        }
60    }
61}
62
63ferroui_base::ferro_overrides! {
64    impl FerroObjectImpl for RenderTargetBitmapBehavior {
65        fn constructed(this: &Self) {
66            Self::parent_constructed(this);
67
68            let dispatcher = Dispatcher::ui_thread();
69            if dispatcher.check_access() {
70                this.stopwatch_start.set(Some(dispatcher.now()));
71            }
72        }
73    }
74}
75
76ferroui_base::ferro_overrides! {
77    impl StyledElementBehaviorImpl for RenderTargetBitmapBehavior {
78        fn on_attached_to_visual_tree(this: &Self) {
79            Self::parent_on_attached_to_visual_tree(this);
80
81            let Some(associated_object) = this.associated_object_as::<Image>() else {
82                return;
83            };
84
85            let bitmap =
86                Rc::new(RenderTargetBitmap::with_dpi(PixelSize::new(this.pixel_width(), this.pixel_height()), this.dpi()));
87            *this.bitmap.borrow_mut() = Some(bitmap.clone());
88            associated_object.set_source(Some(bitmap as Rc<dyn IImage>));
89
90            let timer = DispatcherTimer::new();
91            timer.set_interval(Duration::from_millis(16));
92            let behavior = this.to_ref().downgrade();
93            let tick = timer.tick(move |_timer: &DispatcherTimer| {
94                if let Some(behavior) = behavior.upgrade() {
95                    behavior.on_tick();
96                }
97            });
98            timer.start();
99            *this.timer.borrow_mut() = Some((timer, tick));
100        }
101
102        fn on_detached_from_visual_tree(this: &Self) {
103            Self::parent_on_detached_from_visual_tree(this);
104
105            let timer = this.timer.borrow_mut().take();
106            if let Some((timer, tick)) = timer {
107                timer.stop();
108                tick.dispose();
109            }
110
111            let bitmap = this.bitmap.borrow_mut().take();
112            if let Some(bitmap) = bitmap {
113                bitmap.dispose();
114            }
115        }
116    }
117}
118
119impl RenderTargetBitmapBehavior {
120    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
121    pub fn construct() -> Self {
122        Self {
123            base: StyledElementBehaviorOfT::construct(<Image as StaticType>::TYPE),
124            bitmap: RefCell::new(None),
125            timer: RefCell::new(None),
126            stopwatch_start: Cell::new(None),
127        }
128    }
129
130    /// Creates a behavior.
131    pub fn new() -> Ref<Self> {
132        instantiate(Self::construct())
133    }
134
135    /// Gets or sets the pixel width of the bitmap. This is a property of the object model.
136    pub fn pixel_width(&self) -> i32 {
137        self.get_value(Self::pixel_width_property())
138    }
139
140    pub fn set_pixel_width(&self, value: i32) {
141        self.set_value(Self::pixel_width_property(), value)
142    }
143
144    /// Gets or sets the pixel height of the bitmap. This is a property of the object model.
145    pub fn pixel_height(&self) -> i32 {
146        self.get_value(Self::pixel_height_property())
147    }
148
149    pub fn set_pixel_height(&self, value: i32) {
150        self.set_value(Self::pixel_height_property(), value)
151    }
152
153    /// Gets or sets the DPI vector of the bitmap. This is a property of the object model.
154    pub fn dpi(&self) -> Vector {
155        self.get_value(Self::dpi_property())
156    }
157
158    pub fn set_dpi(&self, value: Vector) {
159        self.set_value(Self::dpi_property(), value)
160    }
161
162    /// Gets or sets the renderer used to draw the bitmap. This is a property of the object model.
163    pub fn renderer(&self) -> Option<Rc<dyn IRenderTargetBitmapRenderer>> {
164        self.get_value(Self::renderer_property())
165    }
166
167    pub fn set_renderer(&self, value: Option<Rc<dyn IRenderTargetBitmapRenderer>>) {
168        self.set_value(Self::renderer_property(), value)
169    }
170
171    /// Requests that the behavior renders its content to the bitmap
172    /// ([`IRenderTargetBitmapRenderHost::render`]).
173    pub fn render(&self) {
174        let Some(associated_object) = self.associated_object_as::<Image>() else {
175            return;
176        };
177
178        let Some(bitmap) = self.bitmap.borrow().clone() else {
179            return;
180        };
181
182        let mut context = bitmap.create_drawing_context();
183        if let Some(renderer) = self.renderer() {
184            renderer.render(&mut context, self.elapsed());
185        }
186        context.dispose();
187
188        associated_object.invalidate_visual();
189    }
190
191    /// The behavior as the contract of render hosts.
192    pub fn as_render_target_bitmap_render_host(&self) -> Rc<dyn IRenderTargetBitmapRenderHost> {
193        Rc::new(RenderHostHandle(self.to_ref()))
194    }
195
196    fn elapsed(&self) -> TimeSpan {
197        let now = Dispatcher::ui_thread().now();
198        let start = match self.stopwatch_start.get() {
199            Some(start) => start,
200            None => {
201                self.stopwatch_start.set(Some(now));
202                now
203            }
204        };
205        TimeSpan::from_milliseconds((now - start) as f64)
206    }
207
208    fn on_tick(&self) {
209        self.render();
210    }
211}
212
213impl IRenderTargetBitmapRenderHost for RenderTargetBitmapBehavior {
214    fn render(&self) {
215        RenderTargetBitmapBehavior::render(self)
216    }
217
218    fn reference_id(&self) -> *const () {
219        let object: &FerroObject = self;
220        object as *const FerroObject as *const ()
221    }
222}
223
224// Deviation (DEVIATIONS.md, Behaviors): a property typed `IRenderTargetBitmapRenderHost` holds a
225// shared handle of the contract; the behavior converts to it through this handle (an interface of
226// its class info), which compares by the identity of the behavior.
227/// The handle of a behavior as the contract of render hosts.
228struct RenderHostHandle(Ref<RenderTargetBitmapBehavior>);
229
230impl IRenderTargetBitmapRenderHost for RenderHostHandle {
231    fn render(&self) {
232        self.0.render()
233    }
234
235    fn reference_id(&self) -> *const () {
236        let object: &FerroObject = &self.0;
237        object as *const FerroObject as *const ()
238    }
239}
240
241fn render_host_of(behavior: Ref<RenderTargetBitmapBehavior>) -> Rc<dyn IRenderTargetBitmapRenderHost> {
242    behavior.as_render_target_bitmap_render_host()
243}