mouse_event_handler.rs

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