mouse_event_handler.rs

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