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, MouseMoveOut, 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_move_out(
 86        mut self,
 87        handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
 88    ) -> Self {
 89        self.handlers = self.handlers.on_move_out(handler);
 90        self
 91    }
 92
 93    pub fn on_down(
 94        mut self,
 95        button: MouseButton,
 96        handler: impl Fn(MouseDown, &mut EventContext) + 'static,
 97    ) -> Self {
 98        self.handlers = self.handlers.on_down(button, handler);
 99        self
100    }
101
102    pub fn on_up(
103        mut self,
104        button: MouseButton,
105        handler: impl Fn(MouseUp, &mut EventContext) + 'static,
106    ) -> Self {
107        self.handlers = self.handlers.on_up(button, handler);
108        self
109    }
110
111    pub fn on_click(
112        mut self,
113        button: MouseButton,
114        handler: impl Fn(MouseClick, &mut EventContext) + 'static,
115    ) -> Self {
116        self.handlers = self.handlers.on_click(button, handler);
117        self
118    }
119
120    pub fn on_down_out(
121        mut self,
122        button: MouseButton,
123        handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
124    ) -> Self {
125        self.handlers = self.handlers.on_down_out(button, handler);
126        self
127    }
128
129    pub fn on_up_out(
130        mut self,
131        button: MouseButton,
132        handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
133    ) -> Self {
134        self.handlers = self.handlers.on_up_out(button, handler);
135        self
136    }
137
138    pub fn on_drag(
139        mut self,
140        button: MouseButton,
141        handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
142    ) -> Self {
143        self.handlers = self.handlers.on_drag(button, handler);
144        self
145    }
146
147    pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
148        self.handlers = self.handlers.on_hover(handler);
149        self
150    }
151
152    pub fn on_scroll(
153        mut self,
154        handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
155    ) -> Self {
156        self.handlers = self.handlers.on_scroll(handler);
157        self
158    }
159
160    pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
161        self.hoverable = is_hoverable;
162        self
163    }
164
165    pub fn with_padding(mut self, padding: Padding) -> Self {
166        self.padding = padding;
167        self
168    }
169
170    fn hit_bounds(&self, bounds: RectF) -> RectF {
171        RectF::from_points(
172            bounds.origin() - vec2f(self.padding.left, self.padding.top),
173            bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
174        )
175        .round_out()
176    }
177
178    fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut PaintContext) {
179        let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
180        let hit_bounds = self.hit_bounds(visible_bounds);
181
182        if let Some(style) = self.cursor_style {
183            cx.scene.push_cursor_region(CursorRegion {
184                bounds: hit_bounds,
185                style,
186            });
187        }
188        cx.scene.push_mouse_region(
189            MouseRegion::from_handlers::<Tag>(
190                cx.current_view_id(),
191                self.region_id,
192                hit_bounds,
193                self.handlers.clone(),
194            )
195            .with_hoverable(self.hoverable)
196            .with_notify_on_hover(self.notify_on_hover)
197            .with_notify_on_click(self.notify_on_click),
198        );
199    }
200}
201
202impl<Tag> Element for MouseEventHandler<Tag> {
203    type LayoutState = ();
204    type PaintState = ();
205
206    fn layout(
207        &mut self,
208        constraint: SizeConstraint,
209        cx: &mut LayoutContext,
210    ) -> (Vector2F, Self::LayoutState) {
211        (self.child.layout(constraint, cx), ())
212    }
213
214    fn paint(
215        &mut self,
216        bounds: RectF,
217        visible_bounds: RectF,
218        _: &mut Self::LayoutState,
219        cx: &mut PaintContext,
220    ) -> Self::PaintState {
221        if self.above {
222            self.child.paint(bounds.origin(), visible_bounds, cx);
223
224            cx.paint_layer(None, |cx| {
225                self.paint_regions(bounds, visible_bounds, cx);
226            });
227        } else {
228            self.paint_regions(bounds, visible_bounds, cx);
229            self.child.paint(bounds.origin(), visible_bounds, cx);
230        }
231    }
232
233    fn rect_for_text_range(
234        &self,
235        range_utf16: Range<usize>,
236        _: RectF,
237        _: RectF,
238        _: &Self::LayoutState,
239        _: &Self::PaintState,
240        cx: &MeasurementContext,
241    ) -> Option<RectF> {
242        self.child.rect_for_text_range(range_utf16, cx)
243    }
244
245    fn debug(
246        &self,
247        _: RectF,
248        _: &Self::LayoutState,
249        _: &Self::PaintState,
250        cx: &DebugContext,
251    ) -> serde_json::Value {
252        json!({
253            "type": "MouseEventHandler",
254            "child": self.child.debug(cx),
255        })
256    }
257}