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