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(&mut EventContext)>>,
 18    click_handler: Option<Box<dyn FnMut(&mut EventContext)>>,
 19    drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
 20    padding: Padding,
 21}
 22
 23#[derive(Default)]
 24pub struct MouseState {
 25    pub hovered: bool,
 26    pub clicked: bool,
 27    prev_drag_position: Option<Vector2F>,
 28}
 29
 30impl MouseEventHandler {
 31    pub fn new<Tag, C, F>(id: usize, cx: &mut C, render_child: F) -> Self
 32    where
 33        Tag: 'static,
 34        C: ElementStateContext,
 35        F: FnOnce(&MouseState, &mut C) -> ElementBox,
 36    {
 37        let state_handle = cx.element_state::<Tag, _>(id);
 38        let child = state_handle.update(cx, |state, cx| render_child(state, cx));
 39        Self {
 40            state: state_handle,
 41            child,
 42            cursor_style: None,
 43            mouse_down_handler: None,
 44            click_handler: None,
 45            drag_handler: None,
 46            padding: Default::default(),
 47        }
 48    }
 49
 50    pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
 51        self.cursor_style = Some(cursor);
 52        self
 53    }
 54
 55    pub fn on_mouse_down(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self {
 56        self.mouse_down_handler = Some(Box::new(handler));
 57        self
 58    }
 59
 60    pub fn on_click(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self {
 61        self.click_handler = Some(Box::new(handler));
 62        self
 63    }
 64
 65    pub fn on_drag(mut self, handler: impl FnMut(Vector2F, &mut EventContext) + 'static) -> Self {
 66        self.drag_handler = Some(Box::new(handler));
 67        self
 68    }
 69
 70    pub fn with_padding(mut self, padding: Padding) -> Self {
 71        self.padding = padding;
 72        self
 73    }
 74
 75    fn hit_bounds(&self, bounds: RectF) -> RectF {
 76        RectF::from_points(
 77            bounds.origin() - vec2f(self.padding.left, self.padding.top),
 78            bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
 79        )
 80        .round_out()
 81    }
 82}
 83
 84impl Element for MouseEventHandler {
 85    type LayoutState = ();
 86    type PaintState = ();
 87
 88    fn layout(
 89        &mut self,
 90        constraint: SizeConstraint,
 91        cx: &mut LayoutContext,
 92    ) -> (Vector2F, Self::LayoutState) {
 93        (self.child.layout(constraint, cx), ())
 94    }
 95
 96    fn paint(
 97        &mut self,
 98        bounds: RectF,
 99        visible_bounds: RectF,
100        _: &mut Self::LayoutState,
101        cx: &mut PaintContext,
102    ) -> Self::PaintState {
103        if let Some(cursor_style) = self.cursor_style {
104            cx.scene
105                .push_cursor_style(self.hit_bounds(bounds), cursor_style);
106        }
107        self.child.paint(bounds.origin(), visible_bounds, cx);
108    }
109
110    fn dispatch_event(
111        &mut self,
112        event: &Event,
113        _: RectF,
114        visible_bounds: RectF,
115        _: &mut Self::LayoutState,
116        _: &mut Self::PaintState,
117        cx: &mut EventContext,
118    ) -> bool {
119        let hit_bounds = self.hit_bounds(visible_bounds);
120        let mouse_down_handler = self.mouse_down_handler.as_mut();
121        let click_handler = self.click_handler.as_mut();
122        let drag_handler = self.drag_handler.as_mut();
123
124        let handled_in_child = self.child.dispatch_event(event, cx);
125
126        self.state.update(cx, |state, cx| match event {
127            Event::MouseMoved {
128                position,
129                left_mouse_down,
130            } => {
131                if !left_mouse_down {
132                    let mouse_in = hit_bounds.contains_point(*position);
133                    if state.hovered != mouse_in {
134                        state.hovered = mouse_in;
135                        cx.notify();
136                        return true;
137                    }
138                }
139                handled_in_child
140            }
141            Event::LeftMouseDown { position, .. } => {
142                if !handled_in_child && hit_bounds.contains_point(*position) {
143                    state.clicked = true;
144                    state.prev_drag_position = Some(*position);
145                    cx.notify();
146                    if let Some(handler) = mouse_down_handler {
147                        handler(cx);
148                    }
149                    true
150                } else {
151                    handled_in_child
152                }
153            }
154            Event::LeftMouseUp { position, .. } => {
155                state.prev_drag_position = None;
156                if !handled_in_child && state.clicked {
157                    state.clicked = false;
158                    cx.notify();
159                    if let Some(handler) = click_handler {
160                        if hit_bounds.contains_point(*position) {
161                            handler(cx);
162                        }
163                    }
164                    true
165                } else {
166                    handled_in_child
167                }
168            }
169            Event::LeftMouseDragged { position, .. } => {
170                if !handled_in_child && state.clicked {
171                    let prev_drag_position = state.prev_drag_position.replace(*position);
172                    if let Some((handler, prev_position)) = drag_handler.zip(prev_drag_position) {
173                        let delta = *position - prev_position;
174                        if !delta.is_zero() {
175                            (handler)(delta, cx);
176                        }
177                    }
178                    true
179                } else {
180                    handled_in_child
181                }
182            }
183            _ => handled_in_child,
184        })
185    }
186
187    fn debug(
188        &self,
189        _: RectF,
190        _: &Self::LayoutState,
191        _: &Self::PaintState,
192        cx: &DebugContext,
193    ) -> serde_json::Value {
194        json!({
195            "type": "MouseEventHandler",
196            "child": self.child.debug(cx),
197        })
198    }
199}