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