mouse_region.rs

  1use crate::{platform::MouseButton, window::WindowContext, EventContext, TypeTag, ViewContext};
  2use collections::HashMap;
  3use pathfinder_geometry::rect::RectF;
  4use smallvec::SmallVec;
  5use std::{any::Any, fmt::Debug, mem::Discriminant, rc::Rc};
  6
  7use super::{
  8    mouse_event::{
  9        MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp,
 10        MouseUpOut,
 11    },
 12    MouseClickOut, MouseMoveOut, MouseScrollWheel,
 13};
 14
 15#[derive(Clone)]
 16pub struct MouseRegion {
 17    pub id: MouseRegionId,
 18    pub bounds: RectF,
 19    pub handlers: HandlerSet,
 20    pub hoverable: bool,
 21    pub notify_on_hover: bool,
 22    pub notify_on_click: bool,
 23}
 24
 25impl MouseRegion {
 26    /// Region ID is used to track semantically equivalent mouse regions across render passes.
 27    /// e.g. if you have mouse handlers attached to a list item type, then each item of the list
 28    /// should pass a different (consistent) region_id. If you have one big region that covers your
 29    /// whole component, just pass the view_id again.
 30    pub fn new<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
 31        Self::from_handlers(
 32            TypeTag::new::<Tag>(),
 33            view_id,
 34            region_id,
 35            bounds,
 36            Default::default(),
 37        )
 38    }
 39
 40    pub fn handle_all<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
 41        Self::from_handlers(
 42            TypeTag::new::<Tag>(),
 43            view_id,
 44            region_id,
 45            bounds,
 46            HandlerSet::capture_all(),
 47        )
 48    }
 49
 50    pub fn from_handlers(
 51        tag: TypeTag,
 52        view_id: usize,
 53        region_id: usize,
 54        bounds: RectF,
 55        handlers: HandlerSet,
 56    ) -> Self {
 57        Self {
 58            id: MouseRegionId {
 59                view_id,
 60                tag,
 61                region_id,
 62            },
 63            bounds,
 64            handlers,
 65            hoverable: true,
 66            notify_on_hover: false,
 67            notify_on_click: false,
 68        }
 69    }
 70
 71    pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
 72    where
 73        V: 'static,
 74        F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
 75    {
 76        self.handlers = self.handlers.on_down(button, handler);
 77        self
 78    }
 79
 80    pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
 81    where
 82        V: 'static,
 83        F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
 84    {
 85        self.handlers = self.handlers.on_up(button, handler);
 86        self
 87    }
 88
 89    pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
 90    where
 91        V: 'static,
 92        F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
 93    {
 94        self.handlers = self.handlers.on_click(button, handler);
 95        self
 96    }
 97
 98    pub fn on_click_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
 99    where
100        V: 'static,
101        F: Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
102    {
103        self.handlers = self.handlers.on_click_out(button, handler);
104        self
105    }
106
107    pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
108    where
109        V: 'static,
110        F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
111    {
112        self.handlers = self.handlers.on_down_out(button, handler);
113        self
114    }
115
116    pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
117    where
118        V: 'static,
119        F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
120    {
121        self.handlers = self.handlers.on_up_out(button, handler);
122        self
123    }
124
125    pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
126    where
127        V: 'static,
128        F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
129    {
130        self.handlers = self.handlers.on_drag(button, handler);
131        self
132    }
133
134    pub fn on_hover<V, F>(mut self, handler: F) -> Self
135    where
136        V: 'static,
137        F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
138    {
139        self.handlers = self.handlers.on_hover(handler);
140        self
141    }
142
143    pub fn on_move<V, F>(mut self, handler: F) -> Self
144    where
145        V: 'static,
146        F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
147    {
148        self.handlers = self.handlers.on_move(handler);
149        self
150    }
151
152    pub fn on_move_out<V, F>(mut self, handler: F) -> Self
153    where
154        V: 'static,
155        F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
156    {
157        self.handlers = self.handlers.on_move_out(handler);
158        self
159    }
160
161    pub fn on_scroll<V, F>(mut self, handler: F) -> Self
162    where
163        V: 'static,
164        F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
165    {
166        self.handlers = self.handlers.on_scroll(handler);
167        self
168    }
169
170    pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
171        self.hoverable = is_hoverable;
172        self
173    }
174
175    pub fn with_notify_on_hover(mut self, notify: bool) -> Self {
176        self.notify_on_hover = notify;
177        self
178    }
179
180    pub fn with_notify_on_click(mut self, notify: bool) -> Self {
181        self.notify_on_click = notify;
182        self
183    }
184}
185
186#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
187pub struct MouseRegionId {
188    view_id: usize,
189    tag: TypeTag,
190    region_id: usize,
191}
192
193impl MouseRegionId {
194    pub(crate) fn new(tag: TypeTag, view_id: usize, region_id: usize) -> Self {
195        MouseRegionId {
196            view_id,
197            region_id,
198            tag,
199        }
200    }
201
202    pub fn view_id(&self) -> usize {
203        self.view_id
204    }
205
206    #[cfg(debug_assertions)]
207    pub fn tag_type_name(&self) -> &'static str {
208        self.tag.type_name()
209    }
210}
211
212pub type HandlerCallback = Rc<dyn Fn(MouseEvent, &mut dyn Any, &mut WindowContext, usize) -> bool>;
213
214#[derive(Clone, PartialEq, Eq, Hash)]
215pub struct HandlerKey {
216    event_kind: Discriminant<MouseEvent>,
217    button: Option<MouseButton>,
218}
219
220impl HandlerKey {
221    pub fn new(event_kind: Discriminant<MouseEvent>, button: Option<MouseButton>) -> HandlerKey {
222        HandlerKey { event_kind, button }
223    }
224}
225
226#[derive(Clone, Default)]
227pub struct HandlerSet {
228    set: HashMap<HandlerKey, SmallVec<[HandlerCallback; 1]>>,
229}
230
231impl HandlerSet {
232    pub fn capture_all() -> Self {
233        let mut set: HashMap<HandlerKey, SmallVec<[HandlerCallback; 1]>> = HashMap::default();
234
235        set.insert(
236            HandlerKey::new(MouseEvent::move_disc(), None),
237            SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
238        );
239        set.insert(
240            HandlerKey::new(MouseEvent::hover_disc(), None),
241            SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
242        );
243        for button in MouseButton::all() {
244            set.insert(
245                HandlerKey::new(MouseEvent::drag_disc(), Some(button)),
246                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
247            );
248            set.insert(
249                HandlerKey::new(MouseEvent::down_disc(), Some(button)),
250                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
251            );
252            set.insert(
253                HandlerKey::new(MouseEvent::up_disc(), Some(button)),
254                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
255            );
256            set.insert(
257                HandlerKey::new(MouseEvent::click_disc(), Some(button)),
258                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
259            );
260            set.insert(
261                HandlerKey::new(MouseEvent::click_out_disc(), Some(button)),
262                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
263            );
264            set.insert(
265                HandlerKey::new(MouseEvent::down_out_disc(), Some(button)),
266                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
267            );
268            set.insert(
269                HandlerKey::new(MouseEvent::up_out_disc(), Some(button)),
270                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
271            );
272        }
273        set.insert(
274            HandlerKey::new(MouseEvent::scroll_wheel_disc(), None),
275            SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
276        );
277
278        HandlerSet { set }
279    }
280
281    pub fn get(&self, key: &HandlerKey) -> Option<&[HandlerCallback]> {
282        self.set.get(key).map(|vec| vec.as_slice())
283    }
284
285    pub fn contains(
286        &self,
287        discriminant: Discriminant<MouseEvent>,
288        button: Option<MouseButton>,
289    ) -> bool {
290        self.set
291            .contains_key(&HandlerKey::new(discriminant, button))
292    }
293
294    fn insert(
295        &mut self,
296        event_kind: Discriminant<MouseEvent>,
297        button: Option<MouseButton>,
298        callback: HandlerCallback,
299    ) {
300        use std::collections::hash_map::Entry;
301
302        match self.set.entry(HandlerKey::new(event_kind, button)) {
303            Entry::Occupied(mut vec) => {
304                vec.get_mut().push(callback);
305            }
306
307            Entry::Vacant(entry) => {
308                entry.insert(SmallVec::from_buf([callback]));
309            }
310        }
311    }
312
313    pub fn on_move<V, F>(mut self, handler: F) -> Self
314    where
315        V: 'static,
316        F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
317    {
318        self.insert(MouseEvent::move_disc(), None,
319            Rc::new(move |region_event, view, cx, view_id| {
320                if let MouseEvent::Move(e) = region_event {
321                    let view = view.downcast_mut().unwrap();
322                    let mut cx = ViewContext::mutable(cx, view_id);
323                    let mut cx = EventContext::new(&mut cx);
324                    handler(e, view, &mut cx);
325                    cx.handled
326                } else {
327                    panic!(
328                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
329                        region_event);
330                }
331            }));
332        self
333    }
334
335    pub fn on_move_out<V, F>(mut self, handler: F) -> Self
336    where
337        V: 'static,
338        F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
339    {
340        self.insert(MouseEvent::move_out_disc(), None,
341            Rc::new(move |region_event, view, cx, view_id| {
342                if let MouseEvent::MoveOut(e) = region_event {
343                    let view = view.downcast_mut().unwrap();
344                    let mut cx = ViewContext::<V>::mutable(cx, view_id);
345                    let mut cx = EventContext::new(&mut cx);
346                    handler(e, view, &mut cx);
347                    cx.handled
348                } else {
349                    panic!(
350                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}",
351                        region_event);
352                }
353            }));
354        self
355    }
356
357    pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
358    where
359        V: 'static,
360        F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
361    {
362        self.insert(MouseEvent::down_disc(), Some(button),
363            Rc::new(move |region_event, view, cx, view_id| {
364                if let MouseEvent::Down(e) = region_event {
365                    let view = view.downcast_mut().unwrap();
366                    let mut cx = ViewContext::mutable(cx, view_id);
367                    let mut cx = EventContext::new(&mut cx);
368                    handler(e, view, &mut cx);
369                    cx.handled
370                } else {
371                    panic!(
372                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
373                        region_event);
374                }
375            }));
376        self
377    }
378
379    pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
380    where
381        V: 'static,
382        F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
383    {
384        self.insert(MouseEvent::up_disc(), Some(button),
385            Rc::new(move |region_event, view, cx, view_id| {
386                if let MouseEvent::Up(e) = region_event {
387                    let view = view.downcast_mut().unwrap();
388                    let mut cx = ViewContext::mutable(cx, view_id);
389                    let mut cx = EventContext::new(&mut cx);
390                    handler(e, view, &mut cx);
391                    cx.handled
392                } else {
393                    panic!(
394                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
395                        region_event);
396                }
397            }));
398        self
399    }
400
401    pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
402    where
403        V: 'static,
404        F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
405    {
406        self.insert(MouseEvent::click_disc(), Some(button),
407            Rc::new(move |region_event, view, cx, view_id| {
408                if let MouseEvent::Click(e) = region_event {
409                    let view = view.downcast_mut().unwrap();
410                    let mut cx = ViewContext::mutable(cx, view_id);
411                    let mut cx = EventContext::new(&mut cx);
412                    handler(e, view, &mut cx);
413                    cx.handled
414                } else {
415                    panic!(
416                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
417                        region_event);
418                }
419            }));
420        self
421    }
422
423    pub fn on_click_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
424    where
425        V: 'static,
426        F: Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
427    {
428        self.insert(MouseEvent::click_out_disc(), Some(button),
429            Rc::new(move |region_event, view, cx, view_id| {
430                if let MouseEvent::ClickOut(e) = region_event {
431                    let view = view.downcast_mut().unwrap();
432                    let mut cx = ViewContext::mutable(cx, view_id);
433                    let mut cx = EventContext::new(&mut cx);
434                    handler(e, view, &mut cx);
435                    cx.handled
436                } else {
437                    panic!(
438                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ClickOut, found {:?}",
439                        region_event);
440                }
441            }));
442        self
443    }
444
445    pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
446    where
447        V: 'static,
448        F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
449    {
450        self.insert(MouseEvent::down_out_disc(), Some(button),
451            Rc::new(move |region_event, view, cx, view_id| {
452                if let MouseEvent::DownOut(e) = region_event {
453                    let view = view.downcast_mut().unwrap();
454                    let mut cx = ViewContext::mutable(cx, view_id);
455                    let mut cx = EventContext::new(&mut cx);
456                    handler(e, view, &mut cx);
457                    cx.handled
458                } else {
459                    panic!(
460                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
461                        region_event);
462                }
463            }));
464        self
465    }
466
467    pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
468    where
469        V: 'static,
470        F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
471    {
472        self.insert(MouseEvent::up_out_disc(), Some(button),
473            Rc::new(move |region_event, view, cx, view_id| {
474                if let MouseEvent::UpOut(e) = region_event {
475                    let view = view.downcast_mut().unwrap();
476                    let mut cx = ViewContext::mutable(cx, view_id);
477                    let mut cx = EventContext::new(&mut cx);
478                    handler(e, view, &mut cx);
479                    cx.handled
480                } else {
481                    panic!(
482                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
483                        region_event);
484                }
485            }));
486        self
487    }
488
489    pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
490    where
491        V: 'static,
492        F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
493    {
494        self.insert(MouseEvent::drag_disc(), Some(button),
495            Rc::new(move |region_event, view, cx, view_id| {
496                if let MouseEvent::Drag(e) = region_event {
497                    let view = view.downcast_mut().unwrap();
498                    let mut cx = ViewContext::mutable(cx, view_id);
499                    let mut cx = EventContext::new(&mut cx);
500                    handler(e, view, &mut cx);
501                    cx.handled
502                } else {
503                    panic!(
504                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
505                        region_event);
506                }
507            }));
508        self
509    }
510
511    pub fn on_hover<V, F>(mut self, handler: F) -> Self
512    where
513        V: 'static,
514        F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
515    {
516        self.insert(MouseEvent::hover_disc(), None,
517            Rc::new(move |region_event, view, cx, view_id| {
518                if let MouseEvent::Hover(e) = region_event {
519                    let view = view.downcast_mut().unwrap();
520                    let mut cx = ViewContext::mutable(cx, view_id);
521                    let mut cx = EventContext::new(&mut cx);
522                    handler(e, view, &mut cx);
523                    cx.handled
524                } else {
525                    panic!(
526                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
527                        region_event);
528                }
529            }));
530        self
531    }
532
533    pub fn on_scroll<V, F>(mut self, handler: F) -> Self
534    where
535        V: 'static,
536        F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
537    {
538        self.insert(MouseEvent::scroll_wheel_disc(), None,
539            Rc::new(move |region_event, view, cx, view_id| {
540                if let MouseEvent::ScrollWheel(e) = region_event {
541                    let view = view.downcast_mut().unwrap();
542                    let mut cx = ViewContext::mutable(cx, view_id);
543                    let mut cx = EventContext::new(&mut cx);
544                    handler(e, view, &mut cx);
545                    cx.handled
546                } else {
547                    panic!(
548                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}",
549                        region_event
550                    );
551                }
552            }));
553        self
554    }
555}