1use blitz_dom::{BaseDocument, Node};
2use blitz_traits::events::{
3 BlitzKeyEvent, BlitzPointerEvent, BlitzPointerId, BlitzScrollEvent, BlitzWheelDelta,
4 BlitzWheelEvent, MouseEventButton,
5};
6use dioxus_html::{
7 AnimationData, BeforeInputData, CancelData, ClipboardData, CompositionData, DragData,
8 FocusData, FormData, FormValue, HasFileData, HasFocusData, HasFormData, HasKeyboardData,
9 HasMouseData, HasPointerData, HasScrollData, HasTouchData, HasTouchPointData, HasWheelData,
10 HtmlEventConverter, ImageData, KeyboardData, MediaData, MountedData, MountedError,
11 MountedResult, MouseData, PlatformEventData, PointerData, RenderedElementBacking, ResizeData,
12 ScrollBehavior, ScrollData, ScrollToOptions, SelectionData, ToggleData, TouchData, TouchPoint,
13 TransitionData, VisibleData, WheelData,
14 geometry::{
15 ClientPoint, ElementPoint, PagePoint, PixelsRect, PixelsSize, PixelsVector2D, ScreenPoint,
16 WheelDelta,
17 euclid::{Point2D, Size2D, Vector3D},
18 },
19 input_data::{MouseButton, MouseButtonSet},
20 point_interaction::{
21 InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
22 },
23};
24use keyboard_types::{Code, Key, Location, Modifiers};
25use std::{
26 any::Any,
27 cell::{Ref, RefCell, RefMut},
28 fmt::Display,
29 future::Future,
30 pin::Pin,
31 rc::Rc,
32};
33
34use crate::NodeId;
35
36pub struct NativeConverter {}
37
38impl HtmlEventConverter for NativeConverter {
39 fn convert_cancel_data(&self, _event: &PlatformEventData) -> CancelData {
40 unimplemented!("todo: convert_cancel_data in dioxus-native. requires support in blitz")
41 }
42
43 fn convert_form_data(&self, event: &PlatformEventData) -> FormData {
44 event.downcast::<NativeFormData>().unwrap().clone().into()
45 }
46
47 fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData {
48 event
49 .downcast::<NativePointerData>()
50 .unwrap()
51 .clone()
52 .into()
53 }
54
55 fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData {
56 event
57 .downcast::<BlitzKeyboardData>()
58 .unwrap()
59 .clone()
60 .into()
61 }
62
63 fn convert_focus_data(&self, _event: &PlatformEventData) -> FocusData {
64 NativeFocusData {}.into()
65 }
66
67 fn convert_animation_data(&self, _event: &PlatformEventData) -> AnimationData {
68 unimplemented!("todo: convert_animation_data in dioxus-native. requires support in blitz")
69 }
70
71 fn convert_before_input_data(&self, _event: &PlatformEventData) -> BeforeInputData {
72 unimplemented!(
73 "todo: convert_before_input_data in dioxus-native. requires support in blitz"
74 )
75 }
76
77 fn convert_clipboard_data(&self, _event: &PlatformEventData) -> ClipboardData {
78 unimplemented!("todo: convert_clipboard_data in dioxus-native. requires support in blitz")
79 }
80
81 fn convert_composition_data(&self, _event: &PlatformEventData) -> CompositionData {
82 unimplemented!("todo: convert_composition_data in dioxus-native. requires support in blitz")
83 }
84
85 fn convert_drag_data(&self, _event: &PlatformEventData) -> DragData {
86 unimplemented!("todo: convert_drag_data in dioxus-native. requires support in blitz")
87 }
88
89 fn convert_image_data(&self, _event: &PlatformEventData) -> ImageData {
90 unimplemented!("todo: convert_image_data in dioxus-native. requires support in blitz")
91 }
92
93 fn convert_media_data(&self, _event: &PlatformEventData) -> MediaData {
94 unimplemented!("todo: convert_media_data in dioxus-native. requires support in blitz")
95 }
96
97 fn convert_mounted_data(&self, event: &PlatformEventData) -> MountedData {
98 event.downcast::<NodeHandle>().unwrap().clone().into()
99 }
100
101 fn convert_pointer_data(&self, event: &PlatformEventData) -> PointerData {
102 event
103 .downcast::<NativePointerData>()
104 .unwrap()
105 .clone()
106 .into()
107 }
108
109 fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData {
110 event.downcast::<NativeScrollData>().unwrap().clone().into()
111 }
112
113 fn convert_selection_data(&self, _event: &PlatformEventData) -> SelectionData {
114 unimplemented!("todo: convert_selection_data in dioxus-native. requires support in blitz")
115 }
116
117 fn convert_toggle_data(&self, _event: &PlatformEventData) -> ToggleData {
118 unimplemented!("todo: convert_toggle_data in dioxus-native. requires support in blitz")
119 }
120
121 fn convert_touch_data(&self, event: &PlatformEventData) -> TouchData {
122 event.downcast::<NativeTouchData>().unwrap().clone().into()
123 }
124
125 fn convert_transition_data(&self, _event: &PlatformEventData) -> TransitionData {
126 unimplemented!("todo: convert_transition_data in dioxus-native. requires support in blitz")
127 }
128
129 fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData {
130 event.downcast::<NativeWheelData>().unwrap().clone().into()
131 }
132
133 fn convert_resize_data(&self, _event: &PlatformEventData) -> ResizeData {
134 unimplemented!("todo: convert_resize_data in dioxus-native. requires support in blitz")
135 }
136
137 fn convert_visible_data(&self, _event: &PlatformEventData) -> VisibleData {
138 unimplemented!("todo: convert_visible_data in dioxus-native. requires support in blitz")
139 }
140}
141
142#[derive(Clone)]
143pub struct NodeHandle {
144 pub(crate) doc: Rc<RefCell<BaseDocument>>,
145 pub(crate) node_id: NodeId,
146}
147
148impl NodeHandle {
149 pub fn node_id(&self) -> NodeId {
150 self.node_id
151 }
152
153 pub fn doc(&self) -> Ref<'_, BaseDocument> {
154 self.doc.borrow()
155 }
156
157 pub fn try_doc(&self) -> Option<Ref<'_, BaseDocument>> {
160 self.doc.try_borrow().ok()
161 }
162
163 pub fn doc_mut(&self) -> RefMut<'_, BaseDocument> {
164 self.doc.borrow_mut()
165 }
166
167 pub fn node(&self) -> Ref<'_, Node> {
168 Ref::map(self.doc.borrow(), |doc| {
169 doc.get_node(self.node_id)
170 .expect("Node does not exist in the Document")
171 })
172 }
173
174 pub fn node_mut(&self) -> RefMut<'_, Node> {
175 RefMut::map(self.doc.borrow_mut(), |doc| {
176 doc.get_node_mut(self.node_id)
177 .expect("Node does not exist in the Document")
178 })
179 }
180
181 fn node_not_exist_err<T>(&self) -> Pin<Box<dyn Future<Output = MountedResult<T>>>> {
182 let node_id = self.node_id;
183 let err = MountedError::OperationFailed(Box::new(NodeNotExistErr(node_id)));
184 Box::pin(async move { Err(err) })
185 }
186}
187
188#[derive(Debug)]
189struct NodeNotExistErr(NodeId);
190impl Display for NodeNotExistErr {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 write!(f, "The node {} does not exist", self.0)
193 }
194}
195impl std::error::Error for NodeNotExistErr {}
196
197impl RenderedElementBacking for NodeHandle {
198 fn as_any(&self) -> &dyn std::any::Any {
199 self
200 }
201
202 fn get_scroll_offset(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsVector2D>>>> {
203 let scroll_offset = self.node().scroll_offset;
204 Box::pin(async move { Ok(PixelsVector2D::new(scroll_offset.x, scroll_offset.y)) })
205 }
206
207 fn get_scroll_size(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsSize>>>> {
208 let node = self.node();
209 let scroll_width = node.final_layout.scroll_width() as f64;
210 let scroll_height = node.final_layout.scroll_height() as f64;
211 Box::pin(async move { Ok(PixelsSize::new(scroll_width, scroll_height)) })
212 }
213
214 fn get_client_rect(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsRect>>>> {
215 let Some(bounding_rect) = self.doc_mut().get_client_bounding_rect(self.node_id) else {
216 return self.node_not_exist_err();
217 };
218 let pixels_rect = PixelsRect::new(
219 Point2D::new(bounding_rect.x, bounding_rect.y),
220 Size2D::new(bounding_rect.width, bounding_rect.height),
221 );
222 Box::pin(async move { Ok(pixels_rect) })
223 }
224
225 fn scroll_to(
226 &self,
227 _options: ScrollToOptions,
228 ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
229 Box::pin(async { Err(MountedError::NotSupported) })
230 }
231
232 fn scroll(
233 &self,
234 _coordinates: PixelsVector2D,
235 _behavior: ScrollBehavior,
236 ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
237 Box::pin(async { Err(MountedError::NotSupported) })
238 }
239
240 fn set_focus(&self, focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
241 let mut doc = self.doc_mut();
242 if focus {
243 doc.set_focus_to(self.node_id);
245 } else if doc.get_focussed_node_id() == Some(self.node_id) {
246 doc.clear_focus();
249 }
250
251 Box::pin(async { Ok(()) })
252 }
253}
254
255#[derive(Clone, Debug)]
256pub struct NativeFormData {
257 pub value: String,
258 pub values: Vec<(String, FormValue)>,
259}
260
261impl HasFormData for NativeFormData {
262 fn as_any(&self) -> &dyn Any {
263 self as &dyn Any
264 }
265
266 fn value(&self) -> String {
267 self.value.clone()
268 }
269
270 fn values(&self) -> Vec<(String, FormValue)> {
271 self.values.clone()
272 }
273 fn valid(&self) -> bool {
274 true
276 }
277}
278
279impl HasFileData for NativeFormData {
280 fn files(&self) -> Vec<dioxus_html::FileData> {
281 vec![]
282 }
283}
284
285#[derive(Clone, Debug)]
286pub(crate) struct BlitzKeyboardData(pub(crate) BlitzKeyEvent);
287
288impl ModifiersInteraction for BlitzKeyboardData {
289 fn modifiers(&self) -> Modifiers {
290 self.0.modifiers
291 }
292}
293
294impl HasKeyboardData for BlitzKeyboardData {
295 fn key(&self) -> Key {
296 self.0.key.clone()
297 }
298
299 fn code(&self) -> Code {
300 self.0.code
301 }
302
303 fn location(&self) -> Location {
304 self.0.location
305 }
306
307 fn is_auto_repeating(&self) -> bool {
308 self.0.is_auto_repeating
309 }
310
311 fn is_composing(&self) -> bool {
312 self.0.is_composing
313 }
314
315 fn as_any(&self) -> &dyn Any {
316 self as &dyn Any
317 }
318}
319
320#[derive(Clone)]
321pub struct NativePointerData(pub(crate) BlitzPointerEvent);
322
323impl InteractionLocation for NativePointerData {
324 fn client_coordinates(&self) -> ClientPoint {
325 ClientPoint::new(self.0.client_x() as f64, self.0.client_y() as f64)
326 }
327
328 fn screen_coordinates(&self) -> ScreenPoint {
329 ScreenPoint::new(self.0.screen_x() as f64, self.0.screen_y() as f64)
330 }
331
332 fn page_coordinates(&self) -> PagePoint {
333 PagePoint::new(self.0.page_x() as f64, self.0.page_y() as f64)
334 }
335}
336
337impl InteractionElementOffset for NativePointerData {
338 fn element_coordinates(&self) -> ElementPoint {
339 ElementPoint::new(self.0.element_x() as f64, self.0.element_y() as f64)
340 }
341}
342
343impl ModifiersInteraction for NativePointerData {
344 fn modifiers(&self) -> Modifiers {
345 self.0.mods
346 }
347}
348
349impl PointerInteraction for NativePointerData {
350 fn trigger_button(&self) -> Option<MouseButton> {
351 Some(match self.0.button {
352 MouseEventButton::Main => MouseButton::Primary,
353 MouseEventButton::Auxiliary => MouseButton::Auxiliary,
354 MouseEventButton::Secondary => MouseButton::Secondary,
355 MouseEventButton::Fourth => MouseButton::Fourth,
356 MouseEventButton::Fifth => MouseButton::Fifth,
357 })
358 }
359
360 fn held_buttons(&self) -> MouseButtonSet {
361 dioxus_html::input_data::decode_mouse_button_set(self.0.buttons.bits() as u16)
362 }
363}
364impl HasMouseData for NativePointerData {
365 fn as_any(&self) -> &dyn Any {
366 self as &dyn Any
367 }
368}
369
370impl HasPointerData for NativePointerData {
371 fn as_any(&self) -> &dyn Any {
372 self as &dyn Any
373 }
374
375 fn is_primary(&self) -> bool {
376 self.0.is_primary
377 }
378
379 fn pointer_id(&self) -> i32 {
380 match self.0.id {
381 BlitzPointerId::Mouse => 0,
382 BlitzPointerId::Pen => 0,
383 BlitzPointerId::Finger(id) => id as i32,
384 }
385 }
386
387 fn pointer_type(&self) -> String {
388 match self.0.id {
389 BlitzPointerId::Mouse => String::from("mouse"),
390 BlitzPointerId::Pen => String::from("pen"),
391 BlitzPointerId::Finger(_) => String::from("touch"),
392 }
393 }
394
395 fn pressure(&self) -> f32 {
396 self.0.details.pressure as f32
397 }
398 fn tangential_pressure(&self) -> f32 {
399 self.0.details.tangential_pressure
400 }
401 fn tilt_x(&self) -> i32 {
402 self.0.details.tilt_x as i32
403 }
404 fn tilt_y(&self) -> i32 {
405 self.0.details.tilt_y as i32
406 }
407 fn twist(&self) -> i32 {
408 self.0.details.twist as i32
409 }
410
411 fn width(&self) -> f64 {
413 1.0
414 }
415 fn height(&self) -> f64 {
416 1.0
417 }
418}
419
420#[derive(Clone)]
427pub struct NativeTouchData(pub(crate) BlitzPointerEvent);
428
429impl ModifiersInteraction for NativeTouchData {
430 fn modifiers(&self) -> Modifiers {
431 self.0.mods
432 }
433}
434
435impl HasTouchData for NativeTouchData {
436 fn touches(&self) -> Vec<TouchPoint> {
437 self.0
439 .active_pointers
440 .borrow()
441 .iter()
442 .map(|event| TouchPoint::new(NativeTouchPointData(event.clone())))
443 .collect()
444 }
445
446 fn touches_changed(&self) -> Vec<TouchPoint> {
447 vec![TouchPoint::new(NativeTouchPointData(self.0.clone()))]
449 }
450
451 fn target_touches(&self) -> Vec<TouchPoint> {
452 self.touches()
455 }
456
457 fn as_any(&self) -> &dyn Any {
458 self as &dyn Any
459 }
460}
461
462#[derive(Clone)]
463pub struct NativeTouchPointData(BlitzPointerEvent);
464
465impl InteractionLocation for NativeTouchPointData {
466 fn client_coordinates(&self) -> ClientPoint {
467 ClientPoint::new(self.0.client_x() as f64, self.0.client_y() as f64)
468 }
469
470 fn screen_coordinates(&self) -> ScreenPoint {
471 ScreenPoint::new(self.0.screen_x() as f64, self.0.screen_y() as f64)
472 }
473
474 fn page_coordinates(&self) -> PagePoint {
475 PagePoint::new(self.0.page_x() as f64, self.0.page_y() as f64)
476 }
477}
478
479impl HasTouchPointData for NativeTouchPointData {
480 fn identifier(&self) -> i32 {
481 match self.0.id {
482 BlitzPointerId::Finger(id) => id as i32,
483 BlitzPointerId::Mouse | BlitzPointerId::Pen => 0,
484 }
485 }
486
487 fn force(&self) -> f64 {
488 self.0.details.pressure
489 }
490
491 fn radius(&self) -> ScreenPoint {
492 ScreenPoint::new(1.0, 1.0)
494 }
495
496 fn rotation(&self) -> f64 {
497 0.0
499 }
500
501 fn as_any(&self) -> &dyn Any {
502 self as &dyn Any
503 }
504}
505
506#[derive(Clone)]
507pub struct NativeFocusData;
508impl HasFocusData for NativeFocusData {
509 fn as_any(&self) -> &dyn Any {
510 self as &dyn Any
511 }
512}
513
514#[derive(Clone)]
515pub struct NativeScrollData(pub(crate) BlitzScrollEvent);
516impl HasScrollData for NativeScrollData {
517 fn as_any(&self) -> &dyn Any {
518 self as &dyn Any
519 }
520
521 fn scroll_top(&self) -> f64 {
522 self.0.scroll_top
523 }
524
525 fn scroll_left(&self) -> f64 {
526 self.0.scroll_left
527 }
528
529 fn scroll_width(&self) -> i32 {
530 self.0.scroll_width
531 }
532
533 fn scroll_height(&self) -> i32 {
534 self.0.scroll_height
535 }
536
537 fn client_width(&self) -> i32 {
538 self.0.client_width
539 }
540
541 fn client_height(&self) -> i32 {
542 self.0.client_height
543 }
544}
545
546#[derive(Clone)]
547pub struct NativeWheelData(pub(crate) BlitzWheelEvent);
548impl HasWheelData for NativeWheelData {
549 fn as_any(&self) -> &dyn Any {
550 self as &dyn Any
551 }
552
553 fn delta(&self) -> WheelDelta {
554 match self.0.delta {
555 BlitzWheelDelta::Lines(x, y) => {
556 dioxus_html::geometry::WheelDelta::Lines(Vector3D::new(x, y, 0.0))
557 }
558 BlitzWheelDelta::Pixels(x, y) => {
559 dioxus_html::geometry::WheelDelta::Pixels(Vector3D::new(x, y, 0.0))
560 }
561 }
562 }
563}
564
565impl HasMouseData for NativeWheelData {
566 fn as_any(&self) -> &dyn Any {
567 self as &dyn Any
568 }
569}
570
571impl PointerInteraction for NativeWheelData {
572 fn trigger_button(&self) -> Option<MouseButton> {
573 None
574 }
575
576 fn held_buttons(&self) -> MouseButtonSet {
577 dioxus_html::input_data::decode_mouse_button_set(self.0.buttons.bits() as u16)
578 }
579}
580
581impl ModifiersInteraction for NativeWheelData {
582 fn modifiers(&self) -> Modifiers {
583 self.0.mods
584 }
585}
586
587impl InteractionElementOffset for NativeWheelData {
588 fn element_coordinates(&self) -> ElementPoint {
589 ElementPoint::new(self.0.element_x() as f64, self.0.element_y() as f64)
590 }
591}
592
593impl InteractionLocation for NativeWheelData {
594 fn client_coordinates(&self) -> ClientPoint {
595 ClientPoint::new(self.0.client_x() as f64, self.0.client_y() as f64)
596 }
597
598 fn screen_coordinates(&self) -> ScreenPoint {
599 ScreenPoint::new(self.0.screen_x() as f64, self.0.screen_y() as f64)
600 }
601
602 fn page_coordinates(&self) -> PagePoint {
603 PagePoint::new(self.0.page_x() as f64, self.0.page_y() as f64)
604 }
605}
606
607pub fn synthetic_click_event(node: &Node, modifiers: Modifiers) -> Box<dyn Any> {
608 Box::new(NativePointerData(
609 node.synthetic_click_event_data(modifiers),
610 ))
611}
612
613#[cfg(test)]
614mod tests {
615 use super::*;
616 use blitz_traits::events::{
617 BlitzPointerId, MouseEventButton, MouseEventButtons, Point, PointerCoords, PointerDetails,
618 };
619
620 fn finger_event(id: u64, x: f32, y: f32) -> BlitzPointerEvent {
621 BlitzPointerEvent {
622 id: BlitzPointerId::Finger(id),
623 is_primary: id == 0,
624 coords: PointerCoords {
625 page_x: x,
626 page_y: y,
627 screen_x: x,
628 screen_y: y,
629 client_x: x,
630 client_y: y,
631 },
632 button: MouseEventButton::Main,
633 buttons: MouseEventButtons::from(MouseEventButton::Main),
634 mods: Default::default(),
635 details: PointerDetails::default(),
636 element: Point::default(),
637 active_pointers: Default::default(),
638 }
639 }
640
641 #[test]
642 fn touches_reports_all_active_pointers() {
643 let f0 = finger_event(0, 10.0, 20.0);
644 let f1 = finger_event(1, 30.0, 40.0);
645
646 let trigger = f1.clone();
649 {
650 let mut list = trigger.active_pointers.borrow_mut();
651 list.push(f0.clone());
652 list.push(f1.clone());
653 }
654
655 let data = NativeTouchData(trigger);
656
657 let touches = data.touches();
659 assert_eq!(touches.len(), 2);
660 let coords: Vec<(f64, f64)> = touches
661 .iter()
662 .map(|t| {
663 let c = t.client_coordinates();
664 (c.x, c.y)
665 })
666 .collect();
667 assert!(coords.contains(&(10.0, 20.0)));
668 assert!(coords.contains(&(30.0, 40.0)));
669
670 assert_eq!(data.touches_changed().len(), 1);
672 let changed = data.touches_changed();
673 let changed_coords = changed[0].client_coordinates();
674 assert_eq!((changed_coords.x, changed_coords.y), (30.0, 40.0));
675 }
676
677 #[test]
678 fn touches_is_empty_when_no_pointers_are_active() {
679 let data = NativeTouchData(finger_event(0, 1.0, 2.0));
683 assert!(data.touches().is_empty());
684 assert_eq!(data.touches_changed().len(), 1);
685 }
686}