1use crate::ferro_headless_platform::FerroHeadlessPlatform;
5use crate::headless_platform_render_interface::HeadlessPlatformRenderInterface;
6use crate::headless_window_impl::HeadlessWindowImpl;
7use crate::i_headless_touch_pointer::IHeadlessTouchPointer;
8use crate::i_headless_window::IHeadlessWindow;
9use ferroui_base::input::raw::{RawDragEventType, RawPointerEventType};
10use ferroui_base::input::{DragDropEffects, IDataTransfer, Key, MouseButton, PhysicalKey, RawInputModifiers};
11use ferroui_base::media::imaging::WriteableBitmap;
12use ferroui_base::reactive::IDisposable;
13use ferroui_base::threading::{Dispatcher, DispatcherPriority};
14use ferroui_base::{Point, Ref, Vector};
15use ferroui_controls::TopLevel;
16use std::any::Any;
17use std::cell::Cell;
18use std::rc::Rc;
19use std::sync::atomic::{AtomicI64, Ordering};
20
21static NEXT_TOUCH_POINT_ID: AtomicI64 = AtomicI64::new(0);
22
23pub trait HeadlessWindowExtensions {
29 fn capture_rendered_frame(&self) -> Option<WriteableBitmap>;
34
35 fn get_last_rendered_frame(&self) -> Option<WriteableBitmap>;
47
48 fn key_press(&self, key: Key, modifiers: RawInputModifiers, physical_key: PhysicalKey, key_symbol: Option<&str>);
50
51 fn key_press_qwerty(&self, physical_key: PhysicalKey, modifiers: RawInputModifiers);
54
55 fn key_release(&self, key: Key, modifiers: RawInputModifiers, physical_key: PhysicalKey, key_symbol: Option<&str>);
57
58 fn key_release_qwerty(&self, physical_key: PhysicalKey, modifiers: RawInputModifiers);
61
62 fn key_text_input(&self, text: &str);
68
69 fn mouse_down(&self, point: Point, button: MouseButton, modifiers: RawInputModifiers);
75
76 fn mouse_move(&self, point: Point, modifiers: RawInputModifiers);
78
79 fn mouse_up(&self, point: Point, button: MouseButton, modifiers: RawInputModifiers);
81
82 fn mouse_wheel(&self, point: Point, delta: Vector, modifiers: RawInputModifiers);
84
85 fn touch_begin(&self, point: Point, modifiers: RawInputModifiers) -> Rc<dyn IHeadlessTouchPointer>;
95
96 fn touch_move(&self, touch_pointer: &dyn IHeadlessTouchPointer, point: Point, modifiers: RawInputModifiers);
99
100 fn touch_end(&self, touch_pointer: &dyn IHeadlessTouchPointer, point: Point, modifiers: RawInputModifiers);
103
104 fn drag_drop(
108 &self,
109 point: Point,
110 type_: RawDragEventType,
111 data: Rc<dyn IDataTransfer>,
112 effects: DragDropEffects,
113 modifiers: RawInputModifiers,
114 );
115
116 fn set_render_scaling(&self, scaling: f64);
120}
121
122impl HeadlessWindowExtensions for TopLevel {
123 fn capture_rendered_frame(&self) -> Option<WriteableBitmap> {
124 let mut bitmap = None;
125 run_jobs_on_impl(self, |w| bitmap = w.get_last_rendered_frame());
126 bitmap
127 }
128
129 fn get_last_rendered_frame(&self) -> Option<WriteableBitmap> {
130 if HeadlessPlatformRenderInterface::is_current() {
131 panic!(
132 "To capture a rendered frame, make sure that headless application was initialized with the Skia backend and disabled 'use_headless_drawing' in the 'FerroHeadlessPlatformOptions'."
133 );
134 }
135
136 with_impl(self, |w| w.get_last_rendered_frame())
137 }
138
139 fn key_press(&self, key: Key, modifiers: RawInputModifiers, physical_key: PhysicalKey, key_symbol: Option<&str>) {
140 run_jobs_on_impl(self, |w| w.key_press(key, modifiers, physical_key, key_symbol));
141 }
142
143 fn key_press_qwerty(&self, physical_key: PhysicalKey, modifiers: RawInputModifiers) {
144 run_jobs_on_impl(self, |w| {
145 w.key_press(physical_key.to_qwerty_key(), modifiers, physical_key, physical_key.to_qwerty_key_symbol(false))
146 });
147 }
148
149 fn key_release(&self, key: Key, modifiers: RawInputModifiers, physical_key: PhysicalKey, key_symbol: Option<&str>) {
150 run_jobs_on_impl(self, |w| w.key_release(key, modifiers, physical_key, key_symbol));
151 }
152
153 fn key_release_qwerty(&self, physical_key: PhysicalKey, modifiers: RawInputModifiers) {
154 run_jobs_on_impl(self, |w| {
155 w.key_release(physical_key.to_qwerty_key(), modifiers, physical_key, physical_key.to_qwerty_key_symbol(false))
156 });
157 }
158
159 fn key_text_input(&self, text: &str) {
160 run_jobs_on_impl(self, |w| w.text_input(text));
161 }
162
163 fn mouse_down(&self, point: Point, button: MouseButton, modifiers: RawInputModifiers) {
164 run_jobs_on_impl(self, |w| w.mouse_down(point, button, modifiers));
165 }
166
167 fn mouse_move(&self, point: Point, modifiers: RawInputModifiers) {
168 run_jobs_on_impl(self, |w| w.mouse_move(point, modifiers));
169 }
170
171 fn mouse_up(&self, point: Point, button: MouseButton, modifiers: RawInputModifiers) {
172 run_jobs_on_impl(self, |w| w.mouse_up(point, button, modifiers));
173 }
174
175 fn mouse_wheel(&self, point: Point, delta: Vector, modifiers: RawInputModifiers) {
176 run_jobs_on_impl(self, |w| w.mouse_wheel(point, delta, modifiers));
177 }
178
179 fn touch_begin(&self, point: Point, modifiers: RawInputModifiers) -> Rc<dyn IHeadlessTouchPointer> {
180 let touch_point_id = NEXT_TOUCH_POINT_ID.fetch_add(1, Ordering::SeqCst) + 1;
181 run_jobs_on_impl(self, |w| w.touch(point, touch_point_id, RawPointerEventType::TouchBegin, modifiers));
182 Rc::new(HeadlessTouchPointer {
183 top_level: self.to_ref(),
184 touch_point_id,
185 position: Cell::new(point),
186 pressed: Cell::new(true),
187 })
188 }
189
190 fn touch_move(&self, touch_pointer: &dyn IHeadlessTouchPointer, point: Point, modifiers: RawInputModifiers) {
191 get_touch_pointer(self, touch_pointer).move_(point, modifiers);
192 }
193
194 fn touch_end(&self, touch_pointer: &dyn IHeadlessTouchPointer, point: Point, modifiers: RawInputModifiers) {
195 get_touch_pointer(self, touch_pointer).end(point, modifiers);
196 }
197
198 fn drag_drop(
199 &self,
200 point: Point,
201 type_: RawDragEventType,
202 data: Rc<dyn IDataTransfer>,
203 effects: DragDropEffects,
204 modifiers: RawInputModifiers,
205 ) {
206 run_jobs_on_impl(self, |w| w.drag_drop(point, type_, data, effects, modifiers));
207 }
208
209 fn set_render_scaling(&self, scaling: f64) {
210 run_jobs_on_impl(self, |w| w.set_render_scaling(scaling));
211 }
212}
213
214fn run_jobs_on_impl(top_level: &TopLevel, action: impl FnOnce(&HeadlessWindowImpl)) {
215 run_jobs_and_render();
216 with_impl(top_level, action);
217 run_jobs_and_render();
218}
219
220fn run_jobs_and_render() {
221 let dispatcher = Dispatcher::ui_thread();
222
223 for _ in 0..10 {
228 dispatcher.run_jobs(None);
229 FerroHeadlessPlatform::force_render_timer_tick(1);
230
231 if !dispatcher.has_jobs_with_priority(DispatcherPriority::SYSTEM_IDLE) {
233 return;
234 }
235 }
236
237 dispatcher.run_jobs(None);
239}
240
241fn with_impl<R>(top_level: &TopLevel, f: impl FnOnce(&HeadlessWindowImpl) -> R) -> R {
247 let Some(platform_impl) = top_level.platform_impl() else {
248 panic!("Cannot access a disposed object.\nObject name: '{}'.", top_level.get_type().name());
249 };
250 match platform_impl.as_any().downcast_ref::<HeadlessWindowImpl>() {
251 Some(headless) => f(headless),
252 None => panic!("TopLevel must be a headless window."),
253 }
254}
255
256fn has_headless_impl(top_level: &TopLevel) -> bool {
258 top_level
259 .platform_impl()
260 .is_some_and(|platform_impl| platform_impl.as_any().downcast_ref::<HeadlessWindowImpl>().is_some())
261}
262
263fn get_touch_pointer<'a>(top_level: &TopLevel, touch_pointer: &'a dyn IHeadlessTouchPointer) -> &'a HeadlessTouchPointer {
267 let Some(headless_touch_pointer) = touch_pointer.as_any().downcast_ref::<HeadlessTouchPointer>() else {
268 panic!("The touch pointer was not created by TouchBegin. (Parameter 'touchPointer')");
269 };
270 if headless_touch_pointer.top_level != top_level.to_ref() {
271 panic!("The touch pointer belongs to a different toplevel. (Parameter 'touchPointer')");
272 }
273 headless_touch_pointer
274}
275
276struct HeadlessTouchPointer {
277 top_level: Ref<TopLevel>,
278 touch_point_id: i64,
279 position: Cell<Point>,
280 pressed: Cell<bool>,
281}
282
283impl HeadlessTouchPointer {
284 fn move_(&self, point: Point, modifiers: RawInputModifiers) {
285 self.throw_if_released();
286 let touch_point_id = self.touch_point_id;
287 run_jobs_on_impl(&self.top_level, |w| w.touch(point, touch_point_id, RawPointerEventType::TouchUpdate, modifiers));
288 self.position.set(point);
289 }
290
291 fn end(&self, point: Point, modifiers: RawInputModifiers) {
292 self.throw_if_released();
293 self.pressed.set(false);
294 let touch_point_id = self.touch_point_id;
295 run_jobs_on_impl(&self.top_level, |w| w.touch(point, touch_point_id, RawPointerEventType::TouchEnd, modifiers));
296 }
297
298 fn throw_if_released(&self) {
301 if !self.pressed.get() {
302 panic!("The touch pointer has already been released.");
303 }
304 }
305}
306
307impl IDisposable for HeadlessTouchPointer {
308 fn dispose(&self) {
309 if !self.pressed.get() {
310 return;
311 }
312 self.pressed.set(false);
313
314 if has_headless_impl(&self.top_level) {
316 let position = self.position.get();
317 let touch_point_id = self.touch_point_id;
318 run_jobs_on_impl(&self.top_level, |w| {
319 w.touch(position, touch_point_id, RawPointerEventType::TouchCancel, RawInputModifiers::empty())
320 });
321 }
322 }
323}
324
325impl IHeadlessTouchPointer for HeadlessTouchPointer {
326 fn as_any(&self) -> &dyn Any {
327 self
328 }
329}