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, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
 11        MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
 12    },
 13    AnyElement, Element, EventContext, MouseRegion, MouseState, SizeConstraint, TypeTag,
 14    ViewContext,
 15};
 16use serde_json::json;
 17use std::ops::Range;
 18
 19pub struct MouseEventHandler<V: 'static> {
 20    child: AnyElement<V>,
 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: TypeTag,
 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<V: 'static> MouseEventHandler<V> {
 35    pub fn for_child<Tag: 'static>(child: impl Element<V>, region_id: usize) -> Self {
 36        Self {
 37            child: child.into_any(),
 38            region_id,
 39            cursor_style: None,
 40            handlers: Default::default(),
 41            notify_on_hover: false,
 42            notify_on_click: false,
 43            hoverable: false,
 44            above: false,
 45            padding: Default::default(),
 46            tag: TypeTag::new::<Tag>(),
 47        }
 48    }
 49
 50    pub fn new<Tag: 'static, E>(
 51        region_id: usize,
 52        cx: &mut ViewContext<V>,
 53        render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
 54    ) -> Self
 55    where
 56        E: Element<V>,
 57    {
 58        let mut mouse_state = cx.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id);
 59        let child = render_child(&mut mouse_state, cx).into_any();
 60        let notify_on_hover = mouse_state.accessed_hovered();
 61        let notify_on_click = mouse_state.accessed_clicked();
 62        Self {
 63            child,
 64            region_id,
 65            cursor_style: None,
 66            handlers: Default::default(),
 67            notify_on_hover,
 68            notify_on_click,
 69            hoverable: true,
 70            above: false,
 71            padding: Default::default(),
 72            tag: TypeTag::new::<Tag>(),
 73        }
 74    }
 75
 76    pub fn new_dynamic(
 77        tag: TypeTag,
 78        region_id: usize,
 79        cx: &mut ViewContext<V>,
 80        render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> AnyElement<V>,
 81    ) -> Self {
 82        let mut mouse_state = cx.mouse_state_dynamic(tag, region_id);
 83        let child = render_child(&mut mouse_state, cx);
 84        let notify_on_hover = mouse_state.accessed_hovered();
 85        let notify_on_click = mouse_state.accessed_clicked();
 86        Self {
 87            child,
 88            region_id,
 89            cursor_style: None,
 90            handlers: Default::default(),
 91            notify_on_hover,
 92            notify_on_click,
 93            hoverable: true,
 94            above: false,
 95            padding: Default::default(),
 96            tag,
 97        }
 98    }
 99
100    /// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
101    /// for drag and drop handling and similar events which should be captured before the child
102    /// gets the opportunity
103    pub fn above<Tag: 'static, D>(
104        region_id: usize,
105        cx: &mut ViewContext<V>,
106        render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
107    ) -> Self
108    where
109        D: Element<V>,
110    {
111        let mut handler = Self::new::<Tag, _>(region_id, cx, render_child);
112        handler.above = true;
113        handler
114    }
115
116    pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
117        self.cursor_style = Some(cursor);
118        self
119    }
120
121    pub fn capture_all(mut self) -> Self {
122        self.handlers = HandlerSet::capture_all();
123        self
124    }
125
126    pub fn on_move(
127        mut self,
128        handler: impl Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
129    ) -> Self {
130        self.handlers = self.handlers.on_move(handler);
131        self
132    }
133
134    pub fn on_move_out(
135        mut self,
136        handler: impl Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
137    ) -> Self {
138        self.handlers = self.handlers.on_move_out(handler);
139        self
140    }
141
142    pub fn on_down(
143        mut self,
144        button: MouseButton,
145        handler: impl Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
146    ) -> Self {
147        self.handlers = self.handlers.on_down(button, handler);
148        self
149    }
150
151    pub fn on_up(
152        mut self,
153        button: MouseButton,
154        handler: impl Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
155    ) -> Self {
156        self.handlers = self.handlers.on_up(button, handler);
157        self
158    }
159
160    pub fn on_click(
161        mut self,
162        button: MouseButton,
163        handler: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
164    ) -> Self {
165        self.handlers = self.handlers.on_click(button, handler);
166        self
167    }
168
169    pub fn on_click_out(
170        mut self,
171        button: MouseButton,
172        handler: impl Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
173    ) -> Self {
174        self.handlers = self.handlers.on_click_out(button, handler);
175        self
176    }
177
178    pub fn on_down_out(
179        mut self,
180        button: MouseButton,
181        handler: impl Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
182    ) -> Self {
183        self.handlers = self.handlers.on_down_out(button, handler);
184        self
185    }
186
187    pub fn on_up_out(
188        mut self,
189        button: MouseButton,
190        handler: impl Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
191    ) -> Self {
192        self.handlers = self.handlers.on_up_out(button, handler);
193        self
194    }
195
196    pub fn on_drag(
197        mut self,
198        button: MouseButton,
199        handler: impl Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
200    ) -> Self {
201        self.handlers = self.handlers.on_drag(button, handler);
202        self
203    }
204
205    pub fn on_hover(
206        mut self,
207        handler: impl Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
208    ) -> Self {
209        self.handlers = self.handlers.on_hover(handler);
210        self
211    }
212
213    pub fn on_scroll(
214        mut self,
215        handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
216    ) -> Self {
217        self.handlers = self.handlers.on_scroll(handler);
218        self
219    }
220
221    pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
222        self.hoverable = is_hoverable;
223        self
224    }
225
226    pub fn with_padding(mut self, padding: Padding) -> Self {
227        self.padding = padding;
228        self
229    }
230
231    fn hit_bounds(&self, bounds: RectF) -> RectF {
232        RectF::from_points(
233            bounds.origin() - vec2f(self.padding.left, self.padding.top),
234            bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
235        )
236        .round_out()
237    }
238
239    fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut ViewContext<V>) {
240        let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
241        let hit_bounds = self.hit_bounds(visible_bounds);
242
243        if let Some(style) = self.cursor_style {
244            cx.scene().push_cursor_region(CursorRegion {
245                bounds: hit_bounds,
246                style,
247            });
248        }
249        let view_id = cx.view_id();
250        cx.scene().push_mouse_region(
251            MouseRegion::from_handlers(
252                self.tag,
253                view_id,
254                self.region_id,
255                hit_bounds,
256                self.handlers.clone(),
257            )
258            .with_hoverable(self.hoverable)
259            .with_notify_on_hover(self.notify_on_hover)
260            .with_notify_on_click(self.notify_on_click),
261        );
262    }
263}
264
265impl<V: 'static> Element<V> for MouseEventHandler<V> {
266    type LayoutState = ();
267    type PaintState = ();
268
269    fn layout(
270        &mut self,
271        constraint: SizeConstraint,
272        view: &mut V,
273        cx: &mut ViewContext<V>,
274    ) -> (Vector2F, Self::LayoutState) {
275        (self.child.layout(constraint, view, cx), ())
276    }
277
278    fn paint(
279        &mut self,
280        bounds: RectF,
281        visible_bounds: RectF,
282        _: &mut Self::LayoutState,
283        view: &mut V,
284        cx: &mut ViewContext<V>,
285    ) -> Self::PaintState {
286        if self.above {
287            self.child.paint(bounds.origin(), visible_bounds, view, cx);
288            cx.paint_layer(None, |cx| {
289                self.paint_regions(bounds, visible_bounds, cx);
290            });
291        } else {
292            self.paint_regions(bounds, visible_bounds, cx);
293            self.child.paint(bounds.origin(), visible_bounds, view, cx);
294        }
295    }
296
297    fn rect_for_text_range(
298        &self,
299        range_utf16: Range<usize>,
300        _: RectF,
301        _: RectF,
302        _: &Self::LayoutState,
303        _: &Self::PaintState,
304        view: &V,
305        cx: &ViewContext<V>,
306    ) -> Option<RectF> {
307        self.child.rect_for_text_range(range_utf16, view, cx)
308    }
309
310    fn debug(
311        &self,
312        _: RectF,
313        _: &Self::LayoutState,
314        _: &Self::PaintState,
315        view: &V,
316        cx: &ViewContext<V>,
317    ) -> serde_json::Value {
318        json!({
319            "type": "MouseEventHandler",
320            "child": self.child.debug(view, cx),
321        })
322    }
323}