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