use std::ops::DerefMut; use crate::{ geometry::{rect::RectF, vector::Vector2F}, platform::CursorStyle, CursorStyleHandle, DebugContext, Element, ElementBox, ElementStateHandle, ElementStateId, Event, EventContext, LayoutContext, MutableAppContext, PaintContext, SizeConstraint, }; use serde_json::json; pub struct MouseEventHandler { state: ElementStateHandle, child: ElementBox, cursor_style: Option, mouse_down_handler: Option>, click_handler: Option>, drag_handler: Option>, } #[derive(Default)] pub struct MouseState { pub hovered: bool, pub clicked: bool, prev_drag_position: Option, cursor_style_handle: Option, } impl MouseEventHandler { pub fn new(id: Id, cx: &mut C, render_child: F) -> Self where Tag: 'static, F: FnOnce(&MouseState, &mut C) -> ElementBox, C: DerefMut, Id: Into, { let state_handle = cx.element_state::(id.into()); let child = state_handle.update(cx, |state, cx| render_child(state, cx)); Self { state: state_handle, child, cursor_style: None, mouse_down_handler: None, click_handler: None, drag_handler: None, } } pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self { self.cursor_style = Some(cursor); self } pub fn on_mouse_down(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self { self.mouse_down_handler = Some(Box::new(handler)); self } pub fn on_click(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self { self.click_handler = Some(Box::new(handler)); self } pub fn on_drag(mut self, handler: impl FnMut(Vector2F, &mut EventContext) + 'static) -> Self { self.drag_handler = Some(Box::new(handler)); self } } impl Element for MouseEventHandler { type LayoutState = (); type PaintState = (); fn layout( &mut self, constraint: SizeConstraint, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { (self.child.layout(constraint, cx), ()) } fn paint( &mut self, bounds: RectF, _: &mut Self::LayoutState, cx: &mut PaintContext, ) -> Self::PaintState { self.child.paint(bounds.origin(), cx); } fn dispatch_event( &mut self, event: &Event, bounds: RectF, _: &mut Self::LayoutState, _: &mut Self::PaintState, cx: &mut EventContext, ) -> bool { let cursor_style = self.cursor_style; let mouse_down_handler = self.mouse_down_handler.as_mut(); let click_handler = self.click_handler.as_mut(); let drag_handler = self.drag_handler.as_mut(); let handled_in_child = self.child.dispatch_event(event, cx); self.state.update(cx, |state, cx| match event { Event::MouseMoved { position, left_mouse_down, } => { if !left_mouse_down { let mouse_in = bounds.contains_point(*position); if state.hovered != mouse_in { state.hovered = mouse_in; if let Some(cursor_style) = cursor_style { if !state.clicked { if state.hovered { state.cursor_style_handle = Some(cx.set_cursor_style(cursor_style)); } else { state.cursor_style_handle = None; } } } cx.notify(); return true; } } handled_in_child } Event::LeftMouseDown { position, .. } => { if !handled_in_child && bounds.contains_point(*position) { state.clicked = true; state.prev_drag_position = Some(*position); cx.notify(); if let Some(handler) = mouse_down_handler { handler(cx); } true } else { handled_in_child } } Event::LeftMouseUp { position, .. } => { state.prev_drag_position = None; if !handled_in_child && state.clicked { state.clicked = false; if !state.hovered { state.cursor_style_handle = None; } cx.notify(); if let Some(handler) = click_handler { if bounds.contains_point(*position) { handler(cx); } } true } else { handled_in_child } } Event::LeftMouseDragged { position, .. } => { if !handled_in_child && state.clicked { let prev_drag_position = state.prev_drag_position.replace(*position); if let Some((handler, prev_position)) = drag_handler.zip(prev_drag_position) { let delta = *position - prev_position; if !delta.is_zero() { (handler)(delta, cx); } } true } else { handled_in_child } } _ => handled_in_child, }) } fn debug( &self, _: RectF, _: &Self::LayoutState, _: &Self::PaintState, cx: &DebugContext, ) -> serde_json::Value { json!({ "type": "MouseEventHandler", "child": self.child.debug(cx), }) } }