mouse_event_handler.rs

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