mouse_event_handler.rs

  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}