use blitz_dom::{BaseDocument, Node};
use blitz_traits::events::{
BlitzKeyEvent, BlitzPointerEvent, BlitzPointerId, BlitzScrollEvent, BlitzWheelDelta,
BlitzWheelEvent, MouseEventButton,
};
use dioxus_html::{
AnimationData, BeforeInputData, CancelData, ClipboardData, CompositionData, DragData,
FocusData, FormData, FormValue, HasFileData, HasFocusData, HasFormData, HasKeyboardData,
HasMouseData, HasPointerData, HasScrollData, HasTouchData, HasTouchPointData, HasWheelData,
HtmlEventConverter, ImageData, KeyboardData, MediaData, MountedData, MountedError,
MountedResult, MouseData, PlatformEventData, PointerData, RenderedElementBacking, ResizeData,
ScrollBehavior, ScrollData, ScrollToOptions, SelectionData, ToggleData, TouchData, TouchPoint,
TransitionData, VisibleData, WheelData,
geometry::{
ClientPoint, ElementPoint, PagePoint, PixelsRect, PixelsSize, PixelsVector2D, ScreenPoint,
WheelDelta,
euclid::{Point2D, Size2D, Vector3D},
},
input_data::{MouseButton, MouseButtonSet},
point_interaction::{
InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
},
};
use keyboard_types::{Code, Key, Location, Modifiers};
use std::{
any::Any,
cell::{Ref, RefCell, RefMut},
fmt::Display,
future::Future,
pin::Pin,
rc::Rc,
};
use crate::NodeId;
pub struct NativeConverter {}
impl HtmlEventConverter for NativeConverter {
fn convert_cancel_data(&self, _event: &PlatformEventData) -> CancelData {
unimplemented!("todo: convert_cancel_data in dioxus-native. requires support in blitz")
}
fn convert_form_data(&self, event: &PlatformEventData) -> FormData {
event.downcast::<NativeFormData>().unwrap().clone().into()
}
fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData {
event
.downcast::<NativePointerData>()
.unwrap()
.clone()
.into()
}
fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData {
event
.downcast::<BlitzKeyboardData>()
.unwrap()
.clone()
.into()
}
fn convert_focus_data(&self, _event: &PlatformEventData) -> FocusData {
NativeFocusData {}.into()
}
fn convert_animation_data(&self, _event: &PlatformEventData) -> AnimationData {
unimplemented!("todo: convert_animation_data in dioxus-native. requires support in blitz")
}
fn convert_before_input_data(&self, _event: &PlatformEventData) -> BeforeInputData {
unimplemented!(
"todo: convert_before_input_data in dioxus-native. requires support in blitz"
)
}
fn convert_clipboard_data(&self, _event: &PlatformEventData) -> ClipboardData {
unimplemented!("todo: convert_clipboard_data in dioxus-native. requires support in blitz")
}
fn convert_composition_data(&self, _event: &PlatformEventData) -> CompositionData {
unimplemented!("todo: convert_composition_data in dioxus-native. requires support in blitz")
}
fn convert_drag_data(&self, _event: &PlatformEventData) -> DragData {
unimplemented!("todo: convert_drag_data in dioxus-native. requires support in blitz")
}
fn convert_image_data(&self, _event: &PlatformEventData) -> ImageData {
unimplemented!("todo: convert_image_data in dioxus-native. requires support in blitz")
}
fn convert_media_data(&self, _event: &PlatformEventData) -> MediaData {
unimplemented!("todo: convert_media_data in dioxus-native. requires support in blitz")
}
fn convert_mounted_data(&self, event: &PlatformEventData) -> MountedData {
event.downcast::<NodeHandle>().unwrap().clone().into()
}
fn convert_pointer_data(&self, event: &PlatformEventData) -> PointerData {
event
.downcast::<NativePointerData>()
.unwrap()
.clone()
.into()
}
fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData {
event.downcast::<NativeScrollData>().unwrap().clone().into()
}
fn convert_selection_data(&self, _event: &PlatformEventData) -> SelectionData {
unimplemented!("todo: convert_selection_data in dioxus-native. requires support in blitz")
}
fn convert_toggle_data(&self, _event: &PlatformEventData) -> ToggleData {
unimplemented!("todo: convert_toggle_data in dioxus-native. requires support in blitz")
}
fn convert_touch_data(&self, event: &PlatformEventData) -> TouchData {
event.downcast::<NativeTouchData>().unwrap().clone().into()
}
fn convert_transition_data(&self, _event: &PlatformEventData) -> TransitionData {
unimplemented!("todo: convert_transition_data in dioxus-native. requires support in blitz")
}
fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData {
event.downcast::<NativeWheelData>().unwrap().clone().into()
}
fn convert_resize_data(&self, _event: &PlatformEventData) -> ResizeData {
unimplemented!("todo: convert_resize_data in dioxus-native. requires support in blitz")
}
fn convert_visible_data(&self, _event: &PlatformEventData) -> VisibleData {
unimplemented!("todo: convert_visible_data in dioxus-native. requires support in blitz")
}
}
#[derive(Clone)]
pub struct NodeHandle {
pub(crate) doc: Rc<RefCell<BaseDocument>>,
pub(crate) node_id: NodeId,
}
impl NodeHandle {
pub fn node_id(&self) -> NodeId {
self.node_id
}
pub fn doc(&self) -> Ref<'_, BaseDocument> {
self.doc.borrow()
}
pub fn try_doc(&self) -> Option<Ref<'_, BaseDocument>> {
self.doc.try_borrow().ok()
}
pub fn doc_mut(&self) -> RefMut<'_, BaseDocument> {
self.doc.borrow_mut()
}
pub fn node(&self) -> Ref<'_, Node> {
Ref::map(self.doc.borrow(), |doc| {
doc.get_node(self.node_id)
.expect("Node does not exist in the Document")
})
}
pub fn node_mut(&self) -> RefMut<'_, Node> {
RefMut::map(self.doc.borrow_mut(), |doc| {
doc.get_node_mut(self.node_id)
.expect("Node does not exist in the Document")
})
}
fn node_not_exist_err<T>(&self) -> Pin<Box<dyn Future<Output = MountedResult<T>>>> {
let node_id = self.node_id;
let err = MountedError::OperationFailed(Box::new(NodeNotExistErr(node_id)));
Box::pin(async move { Err(err) })
}
}
#[derive(Debug)]
struct NodeNotExistErr(NodeId);
impl Display for NodeNotExistErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "The node {} does not exist", self.0)
}
}
impl std::error::Error for NodeNotExistErr {}
impl RenderedElementBacking for NodeHandle {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn get_scroll_offset(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsVector2D>>>> {
let scroll_offset = self.node().scroll_offset;
Box::pin(async move { Ok(PixelsVector2D::new(scroll_offset.x, scroll_offset.y)) })
}
fn get_scroll_size(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsSize>>>> {
let node = self.node();
let scroll_width = node.final_layout.scroll_width() as f64;
let scroll_height = node.final_layout.scroll_height() as f64;
Box::pin(async move { Ok(PixelsSize::new(scroll_width, scroll_height)) })
}
fn get_client_rect(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsRect>>>> {
let Some(bounding_rect) = self.doc_mut().get_client_bounding_rect(self.node_id) else {
return self.node_not_exist_err();
};
let pixels_rect = PixelsRect::new(
Point2D::new(bounding_rect.x, bounding_rect.y),
Size2D::new(bounding_rect.width, bounding_rect.height),
);
Box::pin(async move { Ok(pixels_rect) })
}
fn scroll_to(
&self,
_options: ScrollToOptions,
) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
Box::pin(async { Err(MountedError::NotSupported) })
}
fn scroll(
&self,
_coordinates: PixelsVector2D,
_behavior: ScrollBehavior,
) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
Box::pin(async { Err(MountedError::NotSupported) })
}
fn set_focus(&self, focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
let mut doc = self.doc_mut();
if focus {
doc.set_focus_to(self.node_id);
} else if doc.get_focussed_node_id() == Some(self.node_id) {
doc.clear_focus();
}
Box::pin(async { Ok(()) })
}
}
#[derive(Clone, Debug)]
pub struct NativeFormData {
pub value: String,
pub values: Vec<(String, FormValue)>,
}
impl HasFormData for NativeFormData {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
fn value(&self) -> String {
self.value.clone()
}
fn values(&self) -> Vec<(String, FormValue)> {
self.values.clone()
}
fn valid(&self) -> bool {
true
}
}
impl HasFileData for NativeFormData {
fn files(&self) -> Vec<dioxus_html::FileData> {
vec![]
}
}
#[derive(Clone, Debug)]
pub(crate) struct BlitzKeyboardData(pub(crate) BlitzKeyEvent);
impl ModifiersInteraction for BlitzKeyboardData {
fn modifiers(&self) -> Modifiers {
self.0.modifiers
}
}
impl HasKeyboardData for BlitzKeyboardData {
fn key(&self) -> Key {
self.0.key.clone()
}
fn code(&self) -> Code {
self.0.code
}
fn location(&self) -> Location {
self.0.location
}
fn is_auto_repeating(&self) -> bool {
self.0.is_auto_repeating
}
fn is_composing(&self) -> bool {
self.0.is_composing
}
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
#[derive(Clone)]
pub struct NativePointerData(pub(crate) BlitzPointerEvent);
impl InteractionLocation for NativePointerData {
fn client_coordinates(&self) -> ClientPoint {
ClientPoint::new(self.0.client_x() as f64, self.0.client_y() as f64)
}
fn screen_coordinates(&self) -> ScreenPoint {
ScreenPoint::new(self.0.screen_x() as f64, self.0.screen_y() as f64)
}
fn page_coordinates(&self) -> PagePoint {
PagePoint::new(self.0.page_x() as f64, self.0.page_y() as f64)
}
}
impl InteractionElementOffset for NativePointerData {
fn element_coordinates(&self) -> ElementPoint {
ElementPoint::new(self.0.element_x() as f64, self.0.element_y() as f64)
}
}
impl ModifiersInteraction for NativePointerData {
fn modifiers(&self) -> Modifiers {
self.0.mods
}
}
impl PointerInteraction for NativePointerData {
fn trigger_button(&self) -> Option<MouseButton> {
Some(match self.0.button {
MouseEventButton::Main => MouseButton::Primary,
MouseEventButton::Auxiliary => MouseButton::Auxiliary,
MouseEventButton::Secondary => MouseButton::Secondary,
MouseEventButton::Fourth => MouseButton::Fourth,
MouseEventButton::Fifth => MouseButton::Fifth,
})
}
fn held_buttons(&self) -> MouseButtonSet {
dioxus_html::input_data::decode_mouse_button_set(self.0.buttons.bits() as u16)
}
}
impl HasMouseData for NativePointerData {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
impl HasPointerData for NativePointerData {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
fn is_primary(&self) -> bool {
self.0.is_primary
}
fn pointer_id(&self) -> i32 {
match self.0.id {
BlitzPointerId::Mouse => 0,
BlitzPointerId::Pen => 0,
BlitzPointerId::Finger(id) => id as i32,
}
}
fn pointer_type(&self) -> String {
match self.0.id {
BlitzPointerId::Mouse => String::from("mouse"),
BlitzPointerId::Pen => String::from("pen"),
BlitzPointerId::Finger(_) => String::from("touch"),
}
}
fn pressure(&self) -> f32 {
self.0.details.pressure as f32
}
fn tangential_pressure(&self) -> f32 {
self.0.details.tangential_pressure
}
fn tilt_x(&self) -> i32 {
self.0.details.tilt_x as i32
}
fn tilt_y(&self) -> i32 {
self.0.details.tilt_y as i32
}
fn twist(&self) -> i32 {
self.0.details.twist as i32
}
fn width(&self) -> f64 {
1.0
}
fn height(&self) -> f64 {
1.0
}
}
#[derive(Clone)]
pub struct NativeTouchData(pub(crate) BlitzPointerEvent);
impl ModifiersInteraction for NativeTouchData {
fn modifiers(&self) -> Modifiers {
self.0.mods
}
}
impl HasTouchData for NativeTouchData {
fn touches(&self) -> Vec<TouchPoint> {
self.0
.active_pointers
.borrow()
.iter()
.map(|event| TouchPoint::new(NativeTouchPointData(event.clone())))
.collect()
}
fn touches_changed(&self) -> Vec<TouchPoint> {
vec![TouchPoint::new(NativeTouchPointData(self.0.clone()))]
}
fn target_touches(&self) -> Vec<TouchPoint> {
self.touches()
}
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
#[derive(Clone)]
pub struct NativeTouchPointData(BlitzPointerEvent);
impl InteractionLocation for NativeTouchPointData {
fn client_coordinates(&self) -> ClientPoint {
ClientPoint::new(self.0.client_x() as f64, self.0.client_y() as f64)
}
fn screen_coordinates(&self) -> ScreenPoint {
ScreenPoint::new(self.0.screen_x() as f64, self.0.screen_y() as f64)
}
fn page_coordinates(&self) -> PagePoint {
PagePoint::new(self.0.page_x() as f64, self.0.page_y() as f64)
}
}
impl HasTouchPointData for NativeTouchPointData {
fn identifier(&self) -> i32 {
match self.0.id {
BlitzPointerId::Finger(id) => id as i32,
BlitzPointerId::Mouse | BlitzPointerId::Pen => 0,
}
}
fn force(&self) -> f64 {
self.0.details.pressure
}
fn radius(&self) -> ScreenPoint {
ScreenPoint::new(1.0, 1.0)
}
fn rotation(&self) -> f64 {
0.0
}
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
#[derive(Clone)]
pub struct NativeFocusData;
impl HasFocusData for NativeFocusData {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
#[derive(Clone)]
pub struct NativeScrollData(pub(crate) BlitzScrollEvent);
impl HasScrollData for NativeScrollData {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
fn scroll_top(&self) -> f64 {
self.0.scroll_top
}
fn scroll_left(&self) -> f64 {
self.0.scroll_left
}
fn scroll_width(&self) -> i32 {
self.0.scroll_width
}
fn scroll_height(&self) -> i32 {
self.0.scroll_height
}
fn client_width(&self) -> i32 {
self.0.client_width
}
fn client_height(&self) -> i32 {
self.0.client_height
}
}
#[derive(Clone)]
pub struct NativeWheelData(pub(crate) BlitzWheelEvent);
impl HasWheelData for NativeWheelData {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
fn delta(&self) -> WheelDelta {
match self.0.delta {
BlitzWheelDelta::Lines(x, y) => {
dioxus_html::geometry::WheelDelta::Lines(Vector3D::new(x, y, 0.0))
}
BlitzWheelDelta::Pixels(x, y) => {
dioxus_html::geometry::WheelDelta::Pixels(Vector3D::new(x, y, 0.0))
}
}
}
}
impl HasMouseData for NativeWheelData {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
impl PointerInteraction for NativeWheelData {
fn trigger_button(&self) -> Option<MouseButton> {
None
}
fn held_buttons(&self) -> MouseButtonSet {
dioxus_html::input_data::decode_mouse_button_set(self.0.buttons.bits() as u16)
}
}
impl ModifiersInteraction for NativeWheelData {
fn modifiers(&self) -> Modifiers {
self.0.mods
}
}
impl InteractionElementOffset for NativeWheelData {
fn element_coordinates(&self) -> ElementPoint {
ElementPoint::new(self.0.element_x() as f64, self.0.element_y() as f64)
}
}
impl InteractionLocation for NativeWheelData {
fn client_coordinates(&self) -> ClientPoint {
ClientPoint::new(self.0.client_x() as f64, self.0.client_y() as f64)
}
fn screen_coordinates(&self) -> ScreenPoint {
ScreenPoint::new(self.0.screen_x() as f64, self.0.screen_y() as f64)
}
fn page_coordinates(&self) -> PagePoint {
PagePoint::new(self.0.page_x() as f64, self.0.page_y() as f64)
}
}
pub fn synthetic_click_event(node: &Node, modifiers: Modifiers) -> Box<dyn Any> {
Box::new(NativePointerData(
node.synthetic_click_event_data(modifiers),
))
}
#[cfg(test)]
mod tests {
use super::*;
use blitz_traits::events::{
BlitzPointerId, MouseEventButton, MouseEventButtons, Point, PointerCoords, PointerDetails,
};
fn finger_event(id: u64, x: f32, y: f32) -> BlitzPointerEvent {
BlitzPointerEvent {
id: BlitzPointerId::Finger(id),
is_primary: id == 0,
coords: PointerCoords {
page_x: x,
page_y: y,
screen_x: x,
screen_y: y,
client_x: x,
client_y: y,
},
button: MouseEventButton::Main,
buttons: MouseEventButtons::from(MouseEventButton::Main),
mods: Default::default(),
details: PointerDetails::default(),
element: Point::default(),
active_pointers: Default::default(),
}
}
#[test]
fn touches_reports_all_active_pointers() {
let f0 = finger_event(0, 10.0, 20.0);
let f1 = finger_event(1, 30.0, 40.0);
let trigger = f1.clone();
{
let mut list = trigger.active_pointers.borrow_mut();
list.push(f0.clone());
list.push(f1.clone());
}
let data = NativeTouchData(trigger);
let touches = data.touches();
assert_eq!(touches.len(), 2);
let coords: Vec<(f64, f64)> = touches
.iter()
.map(|t| {
let c = t.client_coordinates();
(c.x, c.y)
})
.collect();
assert!(coords.contains(&(10.0, 20.0)));
assert!(coords.contains(&(30.0, 40.0)));
assert_eq!(data.touches_changed().len(), 1);
let changed = data.touches_changed();
let changed_coords = changed[0].client_coordinates();
assert_eq!((changed_coords.x, changed_coords.y), (30.0, 40.0));
}
#[test]
fn touches_is_empty_when_no_pointers_are_active() {
let data = NativeTouchData(finger_event(0, 1.0, 2.0));
assert!(data.touches().is_empty());
assert_eq!(data.touches_changed().len(), 1);
}
}