Skip to content
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

Hover Implementation #180

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
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 @@ -886,8 +886,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 @@ -966,6 +964,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
9 changes: 8 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_mouseleave, handle_mouseover};

use crate::BaseDocument;

Expand All @@ -29,7 +31,12 @@ 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(event) => {
handle_mouseover(doc, target_node_id, event.x, event.y);
}
DomEventData::MouseLeave => {
handle_mouseleave(doc, target_node_id);
}
DomEventData::Click(event) => {
handle_click(doc, target_node_id, event.x, event.y);
}
Expand Down
29 changes: 29 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,35 @@ fn parent_hit(node: &Node, x: f32, y: f32) -> Option<HitResult> {
})
}

pub(crate) fn handle_mouseover(doc: &mut BaseDocument, _target: usize, x: f32, y: f32) {
if let Some(node) = doc.get_node_mut(_target) {
// Toggle hover state on the node
node.hover();

doc.set_focus_to(_target);

doc.set_hover_to(x, y);
}
}

pub(crate) fn handle_mouseleave(doc: &mut BaseDocument, target: usize) -> bool {
if let Some(node) = doc.get_node_mut(target) {
// Clear hover state
node.unhover();

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

// Clear hover position
doc.clear_hover_state();

return true;
}
false
}

pub(crate) fn handle_mousemove(
doc: &mut BaseDocument,
target: usize,
Expand Down
48 changes: 37 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::{create_waker, BlitzShellEvent};
use blitz_dom::BaseDocument;
use blitz_traits::{
BlitzMouseButtonEvent, ColorScheme, Devtools, MouseEventButton, MouseEventButtons, Viewport,
BlitzMouseButtonEvent, BlitzMouseOverEvent, ColorScheme, Devtools, MouseEventButton,
MouseEventButtons, Viewport,
};
use blitz_traits::{Document, DocumentRenderer, DomEvent, DomEventData};
use winit::keyboard::PhysicalKey;
Expand Down Expand Up @@ -256,15 +257,42 @@ 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 mouseleave on previous node
if let Some(prev_id) = prev_hover {
let mut mouse_leave_event = DomEvent::new(prev_id, DomEventData::MouseLeave);
self.doc.handle_event(&mut mouse_leave_event);
}

// Handle mouseenter on new node
if let Some(new_id) = new_hover {
let mut hover_event = DomEvent::new(
new_id,
DomEventData::MouseOver(BlitzMouseOverEvent {
x: self.mouse_pos.0,
y: self.mouse_pos.1,
}),
);
self.doc.handle_event(&mut hover_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 +302,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
18 changes: 14 additions & 4 deletions packages/blitz-traits/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ pub enum DomEventData {
Click(BlitzMouseButtonEvent),
KeyPress(BlitzKeyEvent),
Ime(BlitzImeEvent),
Hover,
MouseOver(BlitzMouseOverEvent),
MouseLeave,
}

impl DomEventData {
Expand All @@ -51,7 +52,8 @@ impl DomEventData {
Self::Click { .. } => "click",
Self::KeyPress { .. } => "keypress",
Self::Ime { .. } => "input",
Self::Hover => "mouseover",
Self::MouseOver { .. } => "mouseover",
Self::MouseLeave => "mouseleave",
}
}

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

Expand All @@ -75,11 +78,18 @@ impl DomEventData {
Self::Click { .. } => true,
Self::KeyPress { .. } => true,
Self::Ime { .. } => true,
Self::Hover => true,
Self::MouseOver { .. } => true,
Self::MouseLeave => true,
}
}
}

#[derive(Clone, Debug)]
pub struct BlitzMouseOverEvent {
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, BlitzMouseOverEvent, 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,12 +82,17 @@ 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::MouseLeave => {
let click_event_data = wrap_event_data(NativeClickData);

println!("{:?}", event.name());

for node_id in chain.clone().into_iter() {
let node = &self.inner.tree()[node_id];
let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
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