ferroui_xaml_interactions_custom/render_target/
render_target_bitmap_behavior.rs1use 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#[repr(C)]
21pub struct RenderTargetBitmapBehavior {
22 base: StyledElementBehaviorOfT,
23 bitmap: RefCell<Option<Rc<RenderTargetBitmap>>>,
24 timer: RefCell<Option<(Rc<DispatcherTimer>, Rc<dyn IDisposable>)>>,
25 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 pub fn pixel_width_property() -> StyledProperty<i32> {
43 FerroProperty::register::<RenderTargetBitmapBehavior, _>("PixelWidth", 200)
44 }
45
46 pub fn pixel_height_property() -> StyledProperty<i32> {
48 FerroProperty::register::<RenderTargetBitmapBehavior, _>("PixelHeight", 200)
49 }
50
51 pub fn dpi_property() -> StyledProperty<Vector> {
53 FerroProperty::register::<RenderTargetBitmapBehavior, _>("Dpi", Vector::new(96.0, 96.0))
54 }
55
56 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 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 pub fn new() -> Ref<Self> {
132 instantiate(Self::construct())
133 }
134
135 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 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 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 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 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 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
224struct 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}