mouse_event_handler.rs

  1use super::Padding;
  2use crate::{
  3    geometry::{
  4        rect::RectF,
  5        vector::{vec2f, Vector2F},
  6    },
  7    platform::CursorStyle,
  8    scene::{
  9        CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover,
 10        MouseMove, MouseScrollWheel, MouseUp, MouseUpOut,
 11    },
 12    DebugContext, Element, ElementBox, EventContext, LayoutContext, MeasurementContext,
 13    MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View,
 14};
 15use serde_json::json;
 16use std::{marker::PhantomData, ops::Range};
 17
 18pub struct MouseEventHandler<Tag: 'static> {
 19    child: ElementBox,
 20    region_id: usize,
 21    cursor_style: Option<CursorStyle>,
 22    handlers: HandlerSet,
 23    hoverable: bool,
 24    notify_on_hover: bool,
 25    notify_on_click: bool,
 26    above: bool,
 27    padding: Padding,
 28    _tag: PhantomData<Tag>,
 29}
 30
 31/// Element which provides a render_child callback with a MouseState and paints a mouse
 32/// region under (or above) it for easy mouse event handling.
 33impl<Tag> MouseEventHandler<Tag> {
 34    pub fn new<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
 35    where
 36        V: View,
 37        F: FnOnce(&mut MouseState, &mut RenderContext<V>) -> ElementBox,
 38    {
 39        let mut mouse_state = cx.mouse_state::<Tag>(region_id);
 40        let child = render_child(&mut mouse_state, cx);
 41        let notify_on_hover = mouse_state.accessed_hovered();
 42        let notify_on_click = mouse_state.accessed_clicked();
 43        Self {
 44            child,
 45            region_id,
 46            cursor_style: None,
 47            handlers: Default::default(),
 48            notify_on_hover,
 49            notify_on_click,
 50            hoverable: true,
 51            above: false,
 52            padding: Default::default(),
 53            _tag: PhantomData,
 54        }
 55    }
 56
 57    /// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
 58    /// for drag and drop handling and similar events which should be captured before the child
 59    /// gets the opportunity
 60    pub fn above<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
 61    where
 62        V: View,
 63        F: FnOnce(&mut MouseState, &mut RenderContext<V>) -> ElementBox,
 64    {
 65        let mut handler = Self::new(region_id, cx, render_child);
 66        handler.above = true;
 67        handler
 68    }
 69
 70    pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
 71        self.cursor_style = Some(cursor);
 72        self
 73    }
 74
 75    pub fn capture_all(mut self) -> Self {
 76        self.handlers = HandlerSet::capture_all();
 77        self
 78    }
 79
 80    pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
 81        self.handlers = self.handlers.on_move(handler);
 82        self
 83    }
 84
 85    pub fn on_down(
 86        mut self,
 87        button: MouseButton,
 88        handler: impl Fn(MouseDown, &mut EventContext) + 'static,
 89    ) -> Self {
 90        self.handlers = self.handlers.on_down(button, handler);
 91        self
 92    }
 93
 94    pub fn on_up(
 95        mut self,
 96        button: MouseButton,
 97        handler: impl Fn(MouseUp, &mut EventContext) + 'static,
 98    ) -> Self {
 99        self.handlers = self.handlers.on_up(button, handler);
100        self
101    }
102
103    pub fn on_click(
104        mut self,
105        button: MouseButton,
106        handler: impl Fn(MouseClick, &mut EventContext) + 'static,
107    ) -> Self {
108        self.handlers = self.handlers.on_click(button, handler);
109        self
110    }
111
112    pub fn on_down_out(
113        mut self,
114        button: MouseButton,
115        handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
116    ) -> Self {
117        self.handlers = self.handlers.on_down_out(button, handler);
118        self
119    }
120
121    pub fn on_up_out(
122        mut self,
123        button: MouseButton,
124        handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
125    ) -> Self {
126        self.handlers = self.handlers.on_up_out(button, handler);
127        self
128    }
129
130    pub fn on_drag(
131        mut self,
132        button: MouseButton,
133        handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
134    ) -> Self {
135        self.handlers = self.handlers.on_drag(button, handler);
136        self
137    }
138
139    pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
140        self.handlers = self.handlers.on_hover(handler);
141        self
142    }
143
144    pub fn on_scroll(
145        mut self,
146        handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
147    ) -> Self {
148        self.handlers = self.handlers.on_scroll(handler);
149        self
150    }
151
152    pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
153        self.hoverable = is_hoverable;
154        self
155    }
156
157    pub fn with_padding(mut self, padding: Padding) -> Self {
158        self.padding = padding;
159        self
160    }
161
162    fn hit_bounds(&self, bounds: RectF) -> RectF {
163        RectF::from_points(
164            bounds.origin() - vec2f(self.padding.left, self.padding.top),
165            bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
166        )
167        .round_out()
168    }
169
170    fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut PaintContext) {
171        let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
172        let hit_bounds = self.hit_bounds(visible_bounds);
173
174        if let Some(style) = self.cursor_style {
175            cx.scene.push_cursor_region(CursorRegion {
176                bounds: hit_bounds,
177                style,
178            });
179        }
180        cx.scene.push_mouse_region(
181            MouseRegion::from_handlers::<Tag>(
182                cx.current_view_id(),
183                self.region_id,
184                hit_bounds,
185                self.handlers.clone(),
186            )
187            .with_hoverable(self.hoverable)
188            .with_notify_on_hover(self.notify_on_hover)
189            .with_notify_on_click(self.notify_on_click),
190        );
191    }
192}
193
194impl<Tag> Element for MouseEventHandler<Tag> {
195    type LayoutState = ();
196    type PaintState = ();
197
198    fn layout(
199        &mut self,
200        constraint: SizeConstraint,
201        cx: &mut LayoutContext,
202    ) -> (Vector2F, Self::LayoutState) {
203        (self.child.layout(constraint, cx), ())
204    }
205
206    fn paint(
207        &mut self,
208        bounds: RectF,
209        visible_bounds: RectF,
210        _: &mut Self::LayoutState,
211        cx: &mut PaintContext,
212    ) -> Self::PaintState {
213        if self.above {
214            self.child.paint(bounds.origin(), visible_bounds, cx);
215
216            cx.paint_layer(None, |cx| {
217                self.paint_regions(bounds, visible_bounds, cx);
218            });
219        } else {
220            self.paint_regions(bounds, visible_bounds, cx);
221            self.child.paint(bounds.origin(), visible_bounds, cx);
222        }
223    }
224
225    fn rect_for_text_range(
226        &self,
227        range_utf16: Range<usize>,
228        _: RectF,
229        _: RectF,
230        _: &Self::LayoutState,
231        _: &Self::PaintState,
232        cx: &MeasurementContext,
233    ) -> Option<RectF> {
234        self.child.rect_for_text_range(range_utf16, cx)
235    }
236
237    fn debug(
238        &self,
239        _: RectF,
240        _: &Self::LayoutState,
241        _: &Self::PaintState,
242        cx: &DebugContext,
243    ) -> serde_json::Value {
244        json!({
245            "type": "MouseEventHandler",
246            "child": self.child.debug(cx),
247        })
248    }
249}