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 {