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, LayoutContext, MouseRegion, MouseState, PaintContext,
 14    SceneBuilder, SizeConstraint, TypeTag, 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(
240        &self,
241        scene: &mut SceneBuilder,
242        bounds: RectF,
243        visible_bounds: RectF,
244        cx: &mut ViewContext<V>,
245    ) {
246        let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
247        let hit_bounds = self.hit_bounds(visible_bounds);
248
249        if let Some(style) = self.cursor_style {
250            scene.push_cursor_region(CursorRegion {
251                bounds: hit_bounds,
252                style,
253            });
254        }
255        scene.push_mouse_region(
256            MouseRegion::from_handlers(
257                self.tag,
258                cx.view_id(),
259                self.region_id,
260                hit_bounds,
261                self.handlers.clone(),
262            )
263            .with_hoverable(self.hoverable)
264            .with_notify_on_hover(self.notify_on_hover)
265            .with_notify_on_click(self.notify_on_click),
266        );
267    }
268}
269
270impl<V: 'static> Element<V> for MouseEventHandler<V> {
271    type LayoutState = ();
272    type PaintState = ();
273
274    fn layout(
275        &mut self,
276        constraint: SizeConstraint,
277        view: &mut V,
278        cx: &mut LayoutContext<V>,
279    ) -> (Vector2F, Self::LayoutState) {
280        (self.child.layout(constraint, view, cx), ())
281    }
282
283    fn paint(
284        &mut self,
285        scene: &mut SceneBuilder,
286        bounds: RectF,
287        visible_bounds: RectF,
288        _: &mut Self::LayoutState,
289        view: &mut V,
290        cx: &mut PaintContext<V>,
291    ) -> Self::PaintState {
292        if self.above {
293            self.child
294                .paint(scene, bounds.origin(), visible_bounds, view, cx);
295
296            scene.paint_layer(None, |scene| {
297                self.paint_regions(scene, bounds, visible_bounds, cx);
298            });
299        } else {
300            self.paint_regions(scene, bounds, visible_bounds, cx);
301            self.child
302                .paint(scene, bounds.origin(), visible_bounds, view, cx);
303        }
304    }
305
306    fn rect_for_text_range(
307        &self,
308        range_utf16: Range<usize>,
309        _: RectF,
310        _: RectF,
311        _: &Self::LayoutState,
312        _: &Self::PaintState,
313        view: &V,
314        cx: &ViewContext<V>,
315    ) -> Option<RectF> {
316        self.child.rect_for_text_range(range_utf16, view, cx)
317    }
318
319    fn debug(
320        &self,
321        _: RectF,
322        _: &Self::LayoutState,
323        _: &Self::PaintState,
324        view: &V,
325        cx: &ViewContext<V>,
326    ) -> serde_json::Value {
327        json!({
328            "type": "MouseEventHandler",
329            "child": self.child.debug(view, cx),
330        })
331    }
332}