diff --git a/examples/touch_test.html b/examples/touch_test.html new file mode 100644 index 000000000..ba06e23c0 --- /dev/null +++ b/examples/touch_test.html @@ -0,0 +1,205 @@ + + + + Touch Events Test + + + +

Touch Events Test

+
+ Touch Here! +
+ Touch Status: Ready + Touch Count: 0 +
+
+ +
+ Event log will appear here... +
+ + + + diff --git a/packages/blitz-dom/src/events/driver.rs b/packages/blitz-dom/src/events/driver.rs index 9f79b73cb..cba94d470 100644 --- a/packages/blitz-dom/src/events/driver.rs +++ b/packages/blitz-dom/src/events/driver.rs @@ -65,6 +65,26 @@ impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> { UiEvent::MouseUp(_) => { self.doc_mut().unactive_node(); } + UiEvent::TouchStart(event) => { + let dom_x = event.x + viewport_scroll.x as f32 / zoom; + let dom_y = event.y + viewport_scroll.y as f32 / zoom; + self.doc_mut().set_hover_to(dom_x, dom_y); + hover_node_id = self.doc().hover_node_id; + self.doc_mut().active_node(); + self.doc_mut().set_mousedown_node_id(hover_node_id); + } + UiEvent::TouchEnd(_) => { + self.doc_mut().unactive_node(); + } + UiEvent::TouchMove(event) => { + let dom_x = event.x + viewport_scroll.x as f32 / zoom; + let dom_y = event.y + viewport_scroll.y as f32 / zoom; + self.doc_mut().set_hover_to(dom_x, dom_y); + hover_node_id = self.doc().hover_node_id; + } + UiEvent::TouchCancel(_) => { + self.doc_mut().unactive_node(); + } _ => {} }; @@ -72,6 +92,10 @@ impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> { UiEvent::MouseMove(_) => hover_node_id, UiEvent::MouseUp(_) => hover_node_id, UiEvent::MouseDown(_) => hover_node_id, + UiEvent::TouchStart(_) => hover_node_id, + UiEvent::TouchEnd(_) => hover_node_id, + UiEvent::TouchMove(_) => hover_node_id, + UiEvent::TouchCancel(_) => hover_node_id, UiEvent::KeyUp(_) => focussed_node_id, UiEvent::KeyDown(_) => focussed_node_id, UiEvent::Ime(_) => focussed_node_id, @@ -93,6 +117,26 @@ impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> { y: data.y + viewport_scroll.y as f32 / zoom, ..data }), + UiEvent::TouchStart(mut data) => { + data.x += viewport_scroll.x as f32 / zoom; + data.y += viewport_scroll.y as f32 / zoom; + DomEventData::TouchStart(data) + } + UiEvent::TouchEnd(mut data) => { + data.x += viewport_scroll.x as f32 / zoom; + data.y += viewport_scroll.y as f32 / zoom; + DomEventData::TouchEnd(data) + } + UiEvent::TouchMove(mut data) => { + data.x += viewport_scroll.x as f32 / zoom; + data.y += viewport_scroll.y as f32 / zoom; + DomEventData::TouchMove(data) + } + UiEvent::TouchCancel(mut data) => { + data.x += viewport_scroll.x as f32 / zoom; + data.y += viewport_scroll.y as f32 / zoom; + DomEventData::TouchCancel(data) + } UiEvent::KeyUp(data) => DomEventData::KeyUp(data), UiEvent::KeyDown(data) => DomEventData::KeyDown(data), UiEvent::Ime(data) => DomEventData::Ime(data), diff --git a/packages/blitz-dom/src/events/mod.rs b/packages/blitz-dom/src/events/mod.rs index 01e3e433e..4485e929b 100644 --- a/packages/blitz-dom/src/events/mod.rs +++ b/packages/blitz-dom/src/events/mod.rs @@ -2,6 +2,7 @@ mod driver; mod ime; mod keyboard; mod mouse; +mod touch; use blitz_traits::events::{DomEvent, DomEventData}; pub use driver::{EventDriver, EventHandler, NoopEventHandler}; @@ -56,5 +57,20 @@ pub(crate) fn handle_dom_event( DomEventData::Input(_) => { // Do nothing (no default action) } + DomEventData::TouchStart(event) => { + touch::handle_touch_start(doc, target_node_id, event.x, event.y); + } + DomEventData::TouchEnd(event) => { + touch::handle_touch_end(doc, target_node_id, event, dispatch_event); + } + DomEventData::TouchMove(event) => { + let changed = touch::handle_touch_move(doc, target_node_id, event.x, event.y); + if changed { + doc.shell_provider.request_redraw(); + } + } + DomEventData::TouchCancel(event) => { + touch::handle_touch_cancel(doc, target_node_id, event, dispatch_event); + } } } diff --git a/packages/blitz-dom/src/events/touch.rs b/packages/blitz-dom/src/events/touch.rs new file mode 100644 index 000000000..1cbbddeba --- /dev/null +++ b/packages/blitz-dom/src/events/touch.rs @@ -0,0 +1,67 @@ +use blitz_traits::events::{BlitzTouchEvent, DomEvent}; +use crate::BaseDocument; +use keyboard_types::Modifiers; + +/// Handle touch start events +pub(crate) fn handle_touch_start( + doc: &mut BaseDocument, + target_node_id: usize, + x: f32, + y: f32, +) { + println!("🟢 RUST: TouchStart at ({:.1}, {:.1}) on node {}", x, y, target_node_id); + // For now, just treat touch start similar to mouse down + // This can be expanded later with touch-specific functionality + crate::events::mouse::handle_mousedown(doc, target_node_id, x, y); +} + +/// Handle touch end events +pub(crate) fn handle_touch_end( + doc: &mut BaseDocument, + target_node_id: usize, + event: &BlitzTouchEvent, + dispatch_event: F, +) { + println!("🔴 RUST: TouchEnd at ({:.1}, {:.1}) on node {}", event.x, event.y, target_node_id); + // For now, just treat touch end similar to mouse up + // This can be expanded later with touch-specific functionality + let mouse_event = blitz_traits::events::BlitzMouseButtonEvent { + x: event.x, + y: event.y, + buttons: blitz_traits::events::MouseEventButtons::Primary, + mods: Modifiers::empty(), + button: blitz_traits::events::MouseEventButton::Main, + }; + crate::events::mouse::handle_mouseup(doc, target_node_id, &mouse_event, dispatch_event); +} + +/// Handle touch move events +pub(crate) fn handle_touch_move( + doc: &mut BaseDocument, + target_node_id: usize, + x: f32, + y: f32, +) -> bool { + println!("🟠 RUST: TouchMove to ({:.1}, {:.1}) on node {}", x, y, target_node_id); + // For now, just treat touch move similar to mouse move + // This can be expanded later with touch-specific functionality + crate::events::mouse::handle_mousemove( + doc, + target_node_id, + x, + y, + blitz_traits::events::MouseEventButtons::Primary, + ) +} + +/// Handle touch cancel events +pub(crate) fn handle_touch_cancel( + doc: &mut BaseDocument, + target_node_id: usize, + event: &BlitzTouchEvent, + dispatch_event: F, +) { + println!("❌ RUST: TouchCancel at ({:.1}, {:.1}) on node {}", event.x, event.y, target_node_id); + // For now, just treat touch cancel similar to touch end + handle_touch_end(doc, target_node_id, event, dispatch_event); +} diff --git a/packages/blitz-shell/src/convert_events.rs b/packages/blitz-shell/src/convert_events.rs index e3971fe34..25bfd1457 100644 --- a/packages/blitz-shell/src/convert_events.rs +++ b/packages/blitz-shell/src/convert_events.rs @@ -1,7 +1,7 @@ -use blitz_traits::events::{BlitzImeEvent, BlitzKeyEvent, KeyState}; +use blitz_traits::events::{BlitzImeEvent, BlitzKeyEvent, BlitzTouchEvent, KeyState}; use blitz_traits::shell::ColorScheme; use keyboard_types::{Code, Key, Location, Modifiers}; -use winit::event::ElementState; +use winit::event::{ElementState, Touch, TouchPhase}; use winit::event::Ime; use winit::event::KeyEvent as WinitKeyEvent; use winit::keyboard::Key as WinitKey; @@ -601,3 +601,28 @@ pub(crate) fn winit_key_to_kbt_key(winit_key: &WinitKey) -> Key { }, } } + +/// Convert Winit Touch event to Blitz touch event +pub(crate) fn winit_touch_event_to_blitz( + touch: &Touch, + mods: WinitModifiers, + scale_factor: f64, +) -> BlitzTouchEvent { + let winit::dpi::LogicalPosition { x, y } = touch.location.to_logical(scale_factor); + + BlitzTouchEvent { + x, + y, + identifier: touch.id as i32, + force: touch.force.map(|f| match f { + // Convert different force types to a 0.0-1.0 range + winit::event::Force::Calibrated { force, .. } => force as f32, + winit::event::Force::Normalized(normalized) => normalized as f32, + }), + // Winit doesn't provide radius information, so we use reasonable defaults + radius_x: None, + radius_y: None, + rotation_angle: None, + mods: winit_modifiers_to_kbt_modifiers(mods), + } +} diff --git a/packages/blitz-shell/src/window.rs b/packages/blitz-shell/src/window.rs index 802321116..f8cac3c44 100644 --- a/packages/blitz-shell/src/window.rs +++ b/packages/blitz-shell/src/window.rs @@ -1,7 +1,7 @@ use crate::BlitzShellProvider; use crate::convert_events::{ color_scheme_to_theme, theme_to_color_scheme, winit_ime_to_blitz, winit_key_event_to_blitz, - winit_modifiers_to_kbt_modifiers, + winit_modifiers_to_kbt_modifiers, winit_touch_event_to_blitz, }; use crate::event::{BlitzShellEvent, create_waker}; use anyrender::WindowRenderer; @@ -385,8 +385,23 @@ impl View { WindowEvent::Focused(_) => {} // Touch and motion events - // Todo implement touch scrolling - WindowEvent::Touch(_) => {} + WindowEvent::Touch(touch) => { + let touch_event_data = winit_touch_event_to_blitz( + &touch, + self.keyboard_modifiers.state(), + self.window.scale_factor(), + ); + + let ui_event = match touch.phase { + winit::event::TouchPhase::Started => UiEvent::TouchStart(touch_event_data), + winit::event::TouchPhase::Moved => UiEvent::TouchMove(touch_event_data), + winit::event::TouchPhase::Ended => UiEvent::TouchEnd(touch_event_data), + winit::event::TouchPhase::Cancelled => UiEvent::TouchCancel(touch_event_data), + }; + + self.doc.handle_ui_event(ui_event); + self.request_redraw(); + }, WindowEvent::TouchpadPressure { .. } => {} WindowEvent::AxisMotion { .. } => {} WindowEvent::PinchGesture { .. } => {}, diff --git a/packages/blitz-traits/src/events.rs b/packages/blitz-traits/src/events.rs index eba432e67..fc054402c 100644 --- a/packages/blitz-traits/src/events.rs +++ b/packages/blitz-traits/src/events.rs @@ -50,6 +50,10 @@ pub enum UiEvent { MouseMove(BlitzMouseButtonEvent), MouseUp(BlitzMouseButtonEvent), MouseDown(BlitzMouseButtonEvent), + TouchStart(BlitzTouchEvent), + TouchEnd(BlitzTouchEvent), + TouchMove(BlitzTouchEvent), + TouchCancel(BlitzTouchEvent), KeyUp(BlitzKeyEvent), KeyDown(BlitzKeyEvent), Ime(BlitzImeEvent), @@ -100,6 +104,10 @@ pub enum DomEventKind { MouseDown, MouseUp, Click, + TouchStart, + TouchEnd, + TouchMove, + TouchCancel, KeyPress, KeyDown, KeyUp, @@ -119,6 +127,10 @@ impl FromStr for DomEventKind { "mousedown" => Ok(Self::MouseDown), "mouseup" => Ok(Self::MouseUp), "click" => Ok(Self::Click), + "touchstart" => Ok(Self::TouchStart), + "touchend" => Ok(Self::TouchEnd), + "touchmove" => Ok(Self::TouchMove), + "touchcancel" => Ok(Self::TouchCancel), "keypress" => Ok(Self::KeyPress), "keydown" => Ok(Self::KeyDown), "keyup" => Ok(Self::KeyUp), @@ -136,6 +148,10 @@ pub enum DomEventData { MouseDown(BlitzMouseButtonEvent), MouseUp(BlitzMouseButtonEvent), Click(BlitzMouseButtonEvent), + TouchStart(BlitzTouchEvent), + TouchEnd(BlitzTouchEvent), + TouchMove(BlitzTouchEvent), + TouchCancel(BlitzTouchEvent), KeyPress(BlitzKeyEvent), KeyDown(BlitzKeyEvent), KeyUp(BlitzKeyEvent), @@ -159,6 +175,10 @@ impl DomEventData { Self::MouseDown { .. } => "mousedown", Self::MouseUp { .. } => "mouseup", Self::Click { .. } => "click", + Self::TouchStart { .. } => "touchstart", + Self::TouchEnd { .. } => "touchend", + Self::TouchMove { .. } => "touchmove", + Self::TouchCancel { .. } => "touchcancel", Self::KeyPress { .. } => "keypress", Self::KeyDown { .. } => "keydown", Self::KeyUp { .. } => "keyup", @@ -173,6 +193,10 @@ impl DomEventData { Self::MouseDown { .. } => DomEventKind::MouseDown, Self::MouseUp { .. } => DomEventKind::MouseUp, Self::Click { .. } => DomEventKind::Click, + Self::TouchStart { .. } => DomEventKind::TouchStart, + Self::TouchEnd { .. } => DomEventKind::TouchEnd, + Self::TouchMove { .. } => DomEventKind::TouchMove, + Self::TouchCancel { .. } => DomEventKind::TouchCancel, Self::KeyPress { .. } => DomEventKind::KeyPress, Self::KeyDown { .. } => DomEventKind::KeyDown, Self::KeyUp { .. } => DomEventKind::KeyUp, @@ -187,6 +211,10 @@ impl DomEventData { Self::MouseDown { .. } => true, Self::MouseUp { .. } => true, Self::Click { .. } => true, + Self::TouchStart { .. } => true, + Self::TouchEnd { .. } => true, + Self::TouchMove { .. } => true, + Self::TouchCancel { .. } => false, // touchcancel cannot be prevented Self::KeyDown { .. } => true, Self::KeyUp { .. } => true, Self::KeyPress { .. } => true, @@ -201,6 +229,10 @@ impl DomEventData { Self::MouseDown { .. } => true, Self::MouseUp { .. } => true, Self::Click { .. } => true, + Self::TouchStart { .. } => true, + Self::TouchEnd { .. } => true, + Self::TouchMove { .. } => true, + Self::TouchCancel { .. } => true, Self::KeyDown { .. } => true, Self::KeyUp { .. } => true, Self::KeyPress { .. } => true, @@ -317,6 +349,28 @@ pub struct BlitzInputEvent { pub value: String, } +/// Touch event data based on Web API TouchEvent +/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent) +#[derive(Clone, Debug)] +pub struct BlitzTouchEvent { + /// The x coordinate of the touch point + pub x: f32, + /// The y coordinate of the touch point + pub y: f32, + /// A unique identifier for the touch point + pub identifier: i32, + /// The amount of pressure applied (0.0 to 1.0, with 1.0 being maximum pressure) + pub force: Option, + /// The approximate radius of the touch area in CSS pixels (X-axis) + pub radius_x: Option, + /// The approximate radius of the touch area in CSS pixels (Y-axis) + pub radius_y: Option, + /// The angle of the touch area ellipse in degrees (0 to 90) + pub rotation_angle: Option, + /// Keyboard modifiers active during the touch event + pub mods: Modifiers, +} + /// Copy of Winit IME event to avoid lower-level Blitz crates depending on winit #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum BlitzImeEvent { diff --git a/packages/mini-dxn/src/dioxus_document.rs b/packages/mini-dxn/src/dioxus_document.rs index c7af60ec9..e83f92034 100644 --- a/packages/mini-dxn/src/dioxus_document.rs +++ b/packages/mini-dxn/src/dioxus_document.rs @@ -169,6 +169,15 @@ impl EventHandler for DioxusEventHandler<'_> { | DomEventData::MouseUp { .. } | DomEventData::Click(_) => Some(wrap_event_data(NativeClickData)), + // Map touch events to mouse events - simple approach that works + DomEventData::TouchStart(_) + | DomEventData::TouchEnd(_) + | DomEventData::TouchMove(_) + | DomEventData::TouchCancel(_) => { + println!("🔥 BLITZ: Touch event converted to mouse event"); + Some(wrap_event_data(NativeClickData)) + }, + DomEventData::KeyDown(kevent) | DomEventData::KeyUp(kevent) | DomEventData::KeyPress(kevent) => { diff --git a/packages/mini-dxn/src/events.rs b/packages/mini-dxn/src/events.rs index c08436a5d..d57278c67 100644 --- a/packages/mini-dxn/src/events.rs +++ b/packages/mini-dxn/src/events.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use blitz_traits::events::BlitzKeyEvent; use dioxus_html::{ AnimationData, ClipboardData, CompositionData, DragData, FocusData, FormData, FormValue, - HasFileData, HasFormData, HasKeyboardData, HasMouseData, HtmlEventConverter, ImageData, + HasFileData, HasFormData, HasKeyboardData, HasMouseData, HasTouchData, HtmlEventConverter, ImageData, KeyboardData, MediaData, MountedData, MouseData, PlatformEventData, PointerData, ResizeData, ScrollData, SelectionData, ToggleData, TouchData, TransitionData, VisibleData, WheelData, geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}, @@ -15,6 +15,34 @@ use dioxus_html::{ }; use keyboard_types::{Code, Key, Location, Modifiers}; +// Empty touch data structure for basic TouchData creation +#[derive(Clone, Debug)] +pub struct EmptyTouchData; + +impl ModifiersInteraction for EmptyTouchData { + fn modifiers(&self) -> Modifiers { + Modifiers::empty() + } +} + +impl HasTouchData for EmptyTouchData { + fn as_any(&self) -> &dyn std::any::Any { + self as &dyn std::any::Any + } + + fn touches(&self) -> Vec { + Vec::new() + } + + fn touches_changed(&self) -> Vec { + Vec::new() + } + + fn target_touches(&self) -> Vec { + Vec::new() + } +} + #[derive(Clone)] pub struct NativeClickData; @@ -124,7 +152,9 @@ impl HtmlEventConverter for NativeConverter { } fn convert_touch_data(&self, _event: &PlatformEventData) -> TouchData { - todo!() + println!("🔥 BLITZ: TouchData converter called - creating empty TouchData"); + // Create empty TouchData since touch events are handled as mouse events + TouchData::new(EmptyTouchData {}) } fn convert_transition_data(&self, _event: &PlatformEventData) -> TransitionData {