Skip to content

Mouse Events Implementation #180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions examples/hover.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use dioxus::prelude::*;

fn main() {
dioxus_native::launch(app);
}

fn app() -> Element {
let mut box1_hover = use_signal(|| 0);
let mut box2_hover = use_signal(|| 0);
let mut box3_hover = use_signal(|| 0);

rsx! {
style { {STYLES} }
div { class: "container",
// Box 1 - Simple hover counter
div { class: "hover-box", onmouseover: move |_| box1_hover += 1,
"Hover Count: {box1_hover}"
}

// Box 2 - Parent with child
div {
class: "hover-box parent",
onmouseover: move |_| box2_hover += 1,
"Parent Hovers: {box2_hover}"
div { class: "child", onmouseover: move |_| box3_hover += 1,
"Child Hovers: {box3_hover}"
}
}
}
}
}

static STYLES: &str = r#"
.container {
display: flex;
gap: 20px;
padding: 20px;
}

.hover-box {
padding: 20px;
background: #eee;
border: 2px solid #333;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}

.hover-box:hover {
background: #333;
color: white;
transform: scale(1.05);
}

.parent {
position: relative;
min-width: 200px;
}

.child {
margin-top: 10px;
padding: 10px;
background: #666;
color: white;
border-radius: 4px;
}

.child:hover {
background: #999;
}

.stats {
position: fixed;
top: 20px;
right: 20px;
padding: 10px;
background: #333;
color: white;
border-radius: 4px;
}
"#;
10 changes: 8 additions & 2 deletions packages/blitz-dom/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -884,8 +884,6 @@ impl BaseDocument {
return false;
}

println!("Focussed node {}", focus_node_id);

// Remove focus from the old node
if let Some(id) = self.focus_node_id {
self.snapshot_node_and(id, |node| node.blur());
Expand Down Expand Up @@ -964,6 +962,14 @@ impl BaseDocument {
true
}

pub fn clear_hover_state(&mut self) {
if let Some(id) = self.hover_node_id {
self.snapshot_node_and(id, |node| node.unhover());
self.hover_node_id = None;
self.changed.insert(id);
}
}

pub fn get_hover_node_id(&self) -> Option<usize> {
self.hover_node_id
}
Expand Down
27 changes: 26 additions & 1 deletion packages/blitz-dom/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ mod keyboard;
mod mouse;

use blitz_traits::{DomEvent, DomEventData};

pub(crate) use ime::handle_ime_event;
pub(crate) use keyboard::handle_keypress;
pub(crate) use mouse::{handle_click, handle_mousedown, handle_mousemove};
use mouse::{handle_mouse_hover, handle_mouse_unhover};

use crate::BaseDocument;

Expand All @@ -29,7 +31,30 @@ pub(crate) fn handle_event(doc: &mut BaseDocument, event: &mut DomEvent) {
handle_mousedown(doc, target_node_id, event.x, event.y);
}
DomEventData::MouseUp(_) => {}
DomEventData::Hover => {}
DomEventData::MouseOver(mouse_event) | DomEventData::MouseEnter(mouse_event) => {
handle_mouse_hover(doc, target_node_id, mouse_event.x, mouse_event.y);
}
DomEventData::MouseOut(mouse_event) => {
let changed = handle_mouse_unhover(
doc,
target_node_id,
false,
Some(mouse_event.x),
Some(mouse_event.y),
);
if changed {
event.request_redraw = true;
}
}
DomEventData::MouseLeave(mouse_event) => {
handle_mouse_unhover(
doc,
target_node_id,
true,
Some(mouse_event.x),
Some(mouse_event.y),
);
}
DomEventData::Click(event) => {
handle_click(doc, target_node_id, event.x, event.y);
}
Expand Down
45 changes: 45 additions & 0 deletions packages/blitz-dom/src/events/mouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,51 @@ fn parent_hit(node: &Node, x: f32, y: f32) -> Option<HitResult> {
})
}

/// Handles both mouseover and mouseenter events
pub(crate) fn handle_mouse_hover(doc: &mut BaseDocument, _target: usize, x: f32, y: f32) {
if let Some(node) = doc.get_node_mut(_target) {
// Set hover state on the node
node.hover();

doc.set_focus_to(_target);

doc.set_hover_to(x, y);
}
}

/// Handles both mouseout and mouseleave events
///
/// For mouseout: Clears hover state on the current node but updates hover state with new coordinates
/// For mouseleave: Clears hover state completely including focus if needed
pub(crate) fn handle_mouse_unhover(
doc: &mut BaseDocument,
target: usize,
is_leave: bool,
x: Option<f32>,
y: Option<f32>,
) -> bool {
if let Some(node) = doc.get_node_mut(target) {
// Clear hover state
node.unhover();

if is_leave {
// If this was the focused node, clear focus (only for mouseleave)
if doc.get_focussed_node_id() == Some(target) {
doc.clear_focus();
}

// Clear hover position (only for mouseleave)
doc.clear_hover_state();
} else if let (Some(x_val), Some(y_val)) = (x, y) {
// Update hover state with new coordinates (only for mouseout)
doc.set_hover_to(x_val, y_val);
}

return true;
}
false
}

pub(crate) fn handle_mousemove(
doc: &mut BaseDocument,
target: usize,
Expand Down
76 changes: 65 additions & 11 deletions packages/blitz-shell/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use crate::convert_events::{
use crate::event::{BlitzShellEvent, create_waker};
use blitz_dom::BaseDocument;
use blitz_traits::{
BlitzMouseButtonEvent, ColorScheme, Devtools, MouseEventButton, MouseEventButtons, Viewport,
BlitzMouseButtonEvent, BlitzMousePositionEvent, ColorScheme, Devtools, MouseEventButton,
MouseEventButtons, Viewport,
};
use blitz_traits::{Document, DocumentRenderer, DomEvent, DomEventData};
use winit::keyboard::PhysicalKey;
Expand Down Expand Up @@ -256,15 +257,70 @@ impl<Doc: Document<Doc = D>, Rend: DocumentRenderer<Doc = D>> View<Doc, Rend> {
let dom_x = x + viewport_scroll.x as f32 / self.viewport.zoom();
let dom_y = y + viewport_scroll.y as f32 / self.viewport.zoom();

// println!("Mouse move: ({}, {})", x, y);
// println!("Unscaled: ({}, {})",);

self.mouse_pos = (x, y);
self.dom_mouse_pos = (dom_x, dom_y);
let mut changed = self.doc.as_mut().set_hover_to(dom_x, dom_y);

if let Some(node_id) = self.doc.as_ref().get_hover_node_id() {
let mut event = DomEvent::new(
// Get previous hover state before updating
let prev_hover = self.doc.as_ref().get_hover_node_id();

// Update hover state once
let hover_changed = self.doc.as_mut().set_hover_to(dom_x, dom_y);
let new_hover = self.doc.as_ref().get_hover_node_id();

let mut changed = hover_changed;

// Dispatch hover events only when hover state changes
if prev_hover != new_hover {
// Handle mouseout and mouseleave on previous node
if let Some(prev_id) = prev_hover {
// First dispatch mouseout (bubbles)
let mut mouse_out_event = DomEvent::new(
prev_id,
DomEventData::MouseOut(BlitzMousePositionEvent {
x: self.mouse_pos.0,
y: self.mouse_pos.1,
}),
);
self.doc.handle_event(&mut mouse_out_event);

// Then dispatch mouseleave (doesn't bubble)
let mut mouse_leave_event = DomEvent::new(
prev_id,
DomEventData::MouseLeave(BlitzMousePositionEvent {
x: self.mouse_pos.0,
y: self.mouse_pos.1,
}),
);
self.doc.handle_event(&mut mouse_leave_event);
}

// Handle mouseover and mouseenter on new node
if let Some(new_id) = new_hover {
// First dispatch mouseover (bubbles)
let mut hover_event = DomEvent::new(
new_id,
DomEventData::MouseOver(BlitzMousePositionEvent {
x: self.mouse_pos.0,
y: self.mouse_pos.1,
}),
);
self.doc.handle_event(&mut hover_event);

// Then dispatch mouseenter (doesn't bubble)
let mut enter_event = DomEvent::new(
new_id,
DomEventData::MouseEnter(BlitzMousePositionEvent {
x: self.mouse_pos.0,
y: self.mouse_pos.1,
}),
);
self.doc.handle_event(&mut enter_event);
}
}

// Always dispatch mousemove if we have a target
if let Some(node_id) = new_hover {
let mut move_event = DomEvent::new(
node_id,
DomEventData::MouseMove(BlitzMouseButtonEvent {
x: self.dom_mouse_pos.0,
Expand All @@ -274,10 +330,8 @@ impl<Doc: Document<Doc = D>, Rend: DocumentRenderer<Doc = D>> View<Doc, Rend> {
mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
}),
);
self.doc.handle_event(&mut event);
if event.request_redraw {
changed = true;
}
self.doc.handle_event(&mut move_event);
changed |= move_event.request_redraw;
}

changed
Expand Down
26 changes: 22 additions & 4 deletions packages/blitz-traits/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ pub enum DomEventData {
Click(BlitzMouseButtonEvent),
KeyPress(BlitzKeyEvent),
Ime(BlitzImeEvent),
Hover,
MouseOver(BlitzMousePositionEvent),
MouseEnter(BlitzMousePositionEvent),
MouseOut(BlitzMousePositionEvent),
MouseLeave(BlitzMousePositionEvent),
}

impl DomEventData {
Expand All @@ -51,7 +54,10 @@ impl DomEventData {
Self::Click { .. } => "click",
Self::KeyPress { .. } => "keypress",
Self::Ime { .. } => "input",
Self::Hover => "mouseover",
Self::MouseOver { .. } => "mouseover",
Self::MouseEnter { .. } => "mouseenter",
Self::MouseOut { .. } => "mouseout",
Self::MouseLeave { .. } => "mouseleave",
}
}

Expand All @@ -63,7 +69,10 @@ impl DomEventData {
Self::Click { .. } => true,
Self::KeyPress { .. } => true,
Self::Ime { .. } => true,
Self::Hover => true,
Self::MouseOver { .. } => true,
Self::MouseEnter { .. } => false,
Self::MouseOut { .. } => true,
Self::MouseLeave { .. } => false,
}
}

Expand All @@ -75,11 +84,20 @@ impl DomEventData {
Self::Click { .. } => true,
Self::KeyPress { .. } => true,
Self::Ime { .. } => true,
Self::Hover => true,
Self::MouseOver { .. } => true,
Self::MouseEnter { .. } => false,
Self::MouseOut { .. } => true,
Self::MouseLeave { .. } => true,
}
}
}

#[derive(Clone, Debug)]
pub struct BlitzMousePositionEvent {
pub x: f32,
pub y: f32,
}

#[derive(Debug, Clone, Copy)]
pub struct HitResult {
/// The node_id of the node identified as the hit target
Expand Down
4 changes: 2 additions & 2 deletions packages/blitz-traits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ pub mod navigation;

mod events;
pub use events::{
BlitzImeEvent, BlitzKeyEvent, BlitzMouseButtonEvent, DomEvent, DomEventData, HitResult,
KeyState, MouseEventButton, MouseEventButtons,
BlitzImeEvent, BlitzKeyEvent, BlitzMouseButtonEvent, BlitzMousePositionEvent, DomEvent,
DomEventData, HitResult, KeyState, MouseEventButton, MouseEventButtons,
};

mod document;
Expand Down
8 changes: 6 additions & 2 deletions packages/dioxus-native/src/dioxus_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,15 @@ impl Document for DioxusDocument {
let mut prevent_default = false;
let mut stop_propagation = false;

// TODO: Maybe we might need to handle the cases separately for mouse leave ?
match &event.data {
DomEventData::MouseMove { .. }
| DomEventData::MouseDown { .. }
| DomEventData::MouseUp { .. } => {
| DomEventData::MouseUp { .. }
| DomEventData::MouseOver { .. }
| DomEventData::MouseEnter { .. }
| DomEventData::MouseOut { .. }
| DomEventData::MouseLeave { .. } => {
let click_event_data = wrap_event_data(NativeClickData);

for node_id in chain.clone().into_iter() {
Expand Down Expand Up @@ -268,7 +273,6 @@ impl Document for DioxusDocument {
}
// TODO: Implement IME and Hover events handling
DomEventData::Ime(_) => {}
DomEventData::Hover => {}
}

if !event.cancelable || !prevent_default {
Expand Down