1use std::ops::DerefMut;
2
3use crate::{
4 geometry::{rect::RectF, vector::Vector2F},
5 DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext, LayoutContext,
6 MutableAppContext, PaintContext, SizeConstraint,
7};
8use serde_json::json;
9
10pub struct MouseEventHandler {
11 state: ElementStateHandle<MouseState>,
12 child: ElementBox,
13 click_handler: Option<Box<dyn FnMut(&mut EventContext)>>,
14 drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
15}
16
17#[derive(Clone, Copy, Debug, Default)]
18pub struct MouseState {
19 pub hovered: bool,
20 pub clicked: bool,
21 prev_drag_position: Option<Vector2F>,
22}
23
24impl MouseEventHandler {
25 pub fn new<Tag, F, C>(id: usize, cx: &mut C, render_child: F) -> Self
26 where
27 Tag: 'static,
28 F: FnOnce(&MouseState, &mut C) -> ElementBox,
29 C: DerefMut<Target = MutableAppContext>,
30 {
31 let state_handle = cx.element_state::<Tag, _>(id);
32 let child = state_handle.update(cx, |state, cx| render_child(state, cx));
33 Self {
34 state: state_handle,
35 child,
36 click_handler: None,
37 drag_handler: None,
38 }
39 }
40
41 pub fn on_click(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self {
42 self.click_handler = Some(Box::new(handler));
43 self
44 }
45
46 pub fn on_drag(mut self, handler: impl FnMut(Vector2F, &mut EventContext) + 'static) -> Self {
47 self.drag_handler = Some(Box::new(handler));
48 self
49 }
50}
51
52impl Element for MouseEventHandler {
53 type LayoutState = ();
54 type PaintState = ();
55
56 fn layout(
57 &mut self,
58 constraint: SizeConstraint,
59 cx: &mut LayoutContext,
60 ) -> (Vector2F, Self::LayoutState) {
61 (self.child.layout(constraint, cx), ())
62 }
63
64 fn paint(
65 &mut self,
66 bounds: RectF,
67 _: &mut Self::LayoutState,
68 cx: &mut PaintContext,
69 ) -> Self::PaintState {
70 self.child.paint(bounds.origin(), cx);
71 }
72
73 fn dispatch_event(
74 &mut self,
75 event: &Event,
76 bounds: RectF,
77 _: &mut Self::LayoutState,
78 _: &mut Self::PaintState,
79 cx: &mut EventContext,
80 ) -> bool {
81 let click_handler = self.click_handler.as_mut();
82 let drag_handler = self.drag_handler.as_mut();
83
84 let handled_in_child = self.child.dispatch_event(event, cx);
85
86 self.state.update(cx, |state, cx| match event {
87 Event::MouseMoved { position } => {
88 let mouse_in = bounds.contains_point(*position);
89 if state.hovered != mouse_in {
90 state.hovered = mouse_in;
91 cx.notify();
92 true
93 } else {
94 handled_in_child
95 }
96 }
97 Event::LeftMouseDown { position, .. } => {
98 if !handled_in_child && bounds.contains_point(*position) {
99 state.clicked = true;
100 state.prev_drag_position = Some(*position);
101 cx.notify();
102 true
103 } else {
104 handled_in_child
105 }
106 }
107 Event::LeftMouseUp { position, .. } => {
108 state.prev_drag_position = None;
109 if !handled_in_child && state.clicked {
110 state.clicked = false;
111 cx.notify();
112 if let Some(handler) = click_handler {
113 if bounds.contains_point(*position) {
114 handler(cx);
115 }
116 }
117 true
118 } else {
119 handled_in_child
120 }
121 }
122 Event::LeftMouseDragged { position, .. } => {
123 if !handled_in_child && state.clicked {
124 let prev_drag_position = state.prev_drag_position.replace(*position);
125 if let Some((handler, prev_position)) = drag_handler.zip(prev_drag_position) {
126 let delta = *position - prev_position;
127 if !delta.is_zero() {
128 (handler)(delta, cx);
129 }
130 }
131 true
132 } else {
133 handled_in_child
134 }
135 }
136 _ => handled_in_child,
137 })
138 }
139
140 fn debug(
141 &self,
142 _: RectF,
143 _: &Self::LayoutState,
144 _: &Self::PaintState,
145 cx: &DebugContext,
146 ) -> serde_json::Value {
147 json!({
148 "type": "MouseEventHandler",
149 "child": self.child.debug(cx),
150 })
151 }
152}