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