mouse_event_handler.rs

  1use super::Padding;
  2use crate::{
  3    geometry::{
  4        rect::RectF,
  5        vector::{vec2f, Vector2F},
  6    },
  7    platform::CursorStyle,
  8    DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event,
  9    EventContext, LayoutContext, PaintContext, SizeConstraint,
 10};
 11use serde_json::json;
 12
 13pub struct MouseEventHandler {
 14    state: ElementStateHandle<MouseState>,
 15    child: ElementBox,
 16    cursor_style: Option<CursorStyle>,
 17    mouse_down_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
 18    click_handler: Option<Box<dyn FnMut(Vector2F, usize, &mut EventContext)>>,
 19    drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
 20    right_mouse_down_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
 21    right_click_handler: Option<Box<dyn FnMut(Vector2F, usize, &mut EventContext)>>,
 22    padding: Padding,
 23}
 24
 25#[derive(Default)]
 26pub struct MouseState {
 27    pub hovered: bool,
 28    pub clicked: bool,
 29    pub right_clicked: bool,
 30    prev_drag_position: Option<Vector2F>,
 31}
 32
 33impl MouseEventHandler {
 34    pub fn new<Tag, C, F>(id: usize, cx: &mut C, render_child: F) -> Self
 35    where
 36        Tag: 'static,
 37        C: ElementStateContext,
 38        F: FnOnce(&MouseState, &mut C) -> ElementBox,
 39    {
 40        let state_handle = cx.element_state::<Tag, _>(id);
 41        let child = state_handle.update(cx, |state, cx| render_child(state, cx));
 42        Self {
 43            state: state_handle,
 44            child,
 45            cursor_style: None,
 46            mouse_down_handler: None,
 47            click_handler: None,
 48            drag_handler: None,
 49            right_mouse_down_handler: None,
 50            right_click_handler: None,
 51            padding: Default::default(),
 52        }
 53    }
 54
 55    pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
 56        self.cursor_style = Some(cursor);
 57        self
 58    }
 59
 60    pub fn on_mouse_down(
 61        mut self,
 62        handler: impl FnMut(Vector2F, &mut EventContext) + 'static,
 63    ) -> Self {
 64        self.mouse_down_handler = Some(Box::new(handler));
 65        self
 66    }
 67
 68    pub fn on_click(
 69        mut self,
 70        handler: impl FnMut(Vector2F, usize, &mut EventContext) + 'static,
 71    ) -> Self {
 72        self.click_handler = Some(Box::new(handler));
 73        self
 74    }
 75
 76    pub fn on_drag(mut self, handler: impl FnMut(Vector2F, &mut EventContext) + 'static) -> Self {
 77        self.drag_handler = Some(Box::new(handler));
 78        self
 79    }
 80
 81    pub fn on_right_mouse_down(
 82        mut self,
 83        handler: impl FnMut(Vector2F, &mut EventContext) + 'static,
 84    ) -> Self {
 85        self.right_mouse_down_handler = Some(Box::new(handler));
 86        self
 87    }
 88
 89    pub fn on_right_click(
 90        mut self,
 91        handler: impl FnMut(Vector2F, usize, &mut EventContext) + 'static,
 92    ) -> Self {
 93        self.right_click_handler = Some(Box::new(handler));
 94        self
 95    }
 96
 97    pub fn with_padding(mut self, padding: Padding) -> Self {
 98        self.padding = padding;
 99        self
100    }
101
102    fn hit_bounds(&self, bounds: RectF) -> RectF {
103        RectF::from_points(
104            bounds.origin() - vec2f(self.padding.left, self.padding.top),
105            bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
106        )
107        .round_out()
108    }
109}
110
111impl Element for MouseEventHandler {
112    type LayoutState = ();
113    type PaintState = ();
114
115    fn layout(
116        &mut self,
117        constraint: SizeConstraint,
118        cx: &mut LayoutContext,
119    ) -> (Vector2F, Self::LayoutState) {
120        (self.child.layout(constraint, cx), ())
121    }
122
123    fn paint(
124        &mut self,
125        bounds: RectF,
126        visible_bounds: RectF,
127        _: &mut Self::LayoutState,
128        cx: &mut PaintContext,
129    ) -> Self::PaintState {
130        if let Some(cursor_style) = self.cursor_style {
131            cx.scene
132                .push_cursor_style(self.hit_bounds(bounds), cursor_style);
133        }
134        self.child.paint(bounds.origin(), visible_bounds, cx);
135    }
136
137    fn dispatch_event(
138        &mut self,
139        event: &Event,
140        _: RectF,
141        visible_bounds: RectF,
142        _: &mut Self::LayoutState,
143        _: &mut Self::PaintState,
144        cx: &mut EventContext,
145    ) -> bool {
146        let hit_bounds = self.hit_bounds(visible_bounds);
147        let mouse_down_handler = self.mouse_down_handler.as_mut();
148        let click_handler = self.click_handler.as_mut();
149        let drag_handler = self.drag_handler.as_mut();
150        let right_mouse_down_handler = self.right_mouse_down_handler.as_mut();
151        let right_click_handler = self.right_click_handler.as_mut();
152
153        let handled_in_child = self.child.dispatch_event(event, cx);
154
155        self.state.update(cx, |state, cx| match event {
156            Event::MouseMoved {
157                position,
158                left_mouse_down,
159            } => {
160                if !left_mouse_down {
161                    let mouse_in = hit_bounds.contains_point(*position);
162                    if state.hovered != mouse_in {
163                        state.hovered = mouse_in;
164                        cx.notify();
165                        return true;
166                    }
167                }
168                handled_in_child
169            }
170            Event::LeftMouseDown { position, .. } => {
171                if !handled_in_child && hit_bounds.contains_point(*position) {
172                    state.clicked = true;
173                    state.prev_drag_position = Some(*position);
174                    cx.notify();
175                    if let Some(handler) = mouse_down_handler {
176                        handler(*position, cx);
177                    }
178                    true
179                } else {
180                    handled_in_child
181                }
182            }
183            Event::LeftMouseUp {
184                position,
185                click_count,
186                ..
187            } => {
188                state.prev_drag_position = None;
189                if !handled_in_child && state.clicked {
190                    state.clicked = false;
191                    cx.notify();
192                    if let Some(handler) = click_handler {
193                        if hit_bounds.contains_point(*position) {
194                            handler(*position, *click_count, cx);
195                        }
196                    }
197                    true
198                } else {
199                    handled_in_child
200                }
201            }
202            Event::LeftMouseDragged { position, .. } => {
203                if !handled_in_child && state.clicked {
204                    let prev_drag_position = state.prev_drag_position.replace(*position);
205                    if let Some((handler, prev_position)) = drag_handler.zip(prev_drag_position) {
206                        let delta = *position - prev_position;
207                        if !delta.is_zero() {
208                            (handler)(delta, cx);
209                        }
210                    }
211                    true
212                } else {
213                    handled_in_child
214                }
215            }
216            Event::RightMouseDown { position, .. } => {
217                if !handled_in_child && hit_bounds.contains_point(*position) {
218                    state.right_clicked = true;
219                    cx.notify();
220                    if let Some(handler) = right_mouse_down_handler {
221                        handler(*position, cx);
222                    }
223                    true
224                } else {
225                    handled_in_child
226                }
227            }
228            Event::RightMouseUp {
229                position,
230                click_count,
231                ..
232            } => {
233                if !handled_in_child && state.right_clicked {
234                    state.right_clicked = false;
235                    cx.notify();
236                    if let Some(handler) = right_click_handler {
237                        if hit_bounds.contains_point(*position) {
238                            handler(*position, *click_count, cx);
239                        }
240                    }
241                    true
242                } else {
243                    handled_in_child
244                }
245            }
246            _ => handled_in_child,
247        })
248    }
249
250    fn debug(
251        &self,
252        _: RectF,
253        _: &Self::LayoutState,
254        _: &Self::PaintState,
255        cx: &DebugContext,
256    ) -> serde_json::Value {
257        json!({
258            "type": "MouseEventHandler",
259            "child": self.child.debug(cx),
260        })
261    }
262}