interactive.rs

   1use crate::{
   2    div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds,
   3    Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch,
   4    Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
   5    StyleRefinement, Task, View, ViewContext,
   6};
   7use collections::HashMap;
   8use derive_more::{Deref, DerefMut};
   9use parking_lot::Mutex;
  10use refineable::Refineable;
  11use smallvec::SmallVec;
  12use std::{
  13    any::{Any, TypeId},
  14    fmt::Debug,
  15    marker::PhantomData,
  16    mem,
  17    ops::Deref,
  18    path::PathBuf,
  19    sync::Arc,
  20    time::Duration,
  21};
  22
  23const DRAG_THRESHOLD: f64 = 2.;
  24const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
  25const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
  26
  27pub trait StatelessInteractive<V: 'static>: Element<V> {
  28    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V>;
  29
  30    fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
  31    where
  32        Self: Sized,
  33    {
  34        self.stateless_interactivity().hover_style = f(StyleRefinement::default());
  35        self
  36    }
  37
  38    fn group_hover(
  39        mut self,
  40        group_name: impl Into<SharedString>,
  41        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
  42    ) -> Self
  43    where
  44        Self: Sized,
  45    {
  46        self.stateless_interactivity().group_hover_style = Some(GroupStyle {
  47            group: group_name.into(),
  48            style: f(StyleRefinement::default()),
  49        });
  50        self
  51    }
  52
  53    fn on_mouse_down(
  54        mut self,
  55        button: MouseButton,
  56        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
  57    ) -> Self
  58    where
  59        Self: Sized,
  60    {
  61        self.stateless_interactivity()
  62            .mouse_down_listeners
  63            .push(Box::new(move |view, event, bounds, phase, cx| {
  64                if phase == DispatchPhase::Bubble
  65                    && event.button == button
  66                    && bounds.contains_point(&event.position)
  67                {
  68                    handler(view, event, cx)
  69                }
  70            }));
  71        self
  72    }
  73
  74    fn on_mouse_up(
  75        mut self,
  76        button: MouseButton,
  77        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
  78    ) -> Self
  79    where
  80        Self: Sized,
  81    {
  82        self.stateless_interactivity()
  83            .mouse_up_listeners
  84            .push(Box::new(move |view, event, bounds, phase, cx| {
  85                if phase == DispatchPhase::Bubble
  86                    && event.button == button
  87                    && bounds.contains_point(&event.position)
  88                {
  89                    handler(view, event, cx)
  90                }
  91            }));
  92        self
  93    }
  94
  95    fn on_mouse_down_out(
  96        mut self,
  97        button: MouseButton,
  98        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
  99    ) -> Self
 100    where
 101        Self: Sized,
 102    {
 103        self.stateless_interactivity()
 104            .mouse_down_listeners
 105            .push(Box::new(move |view, event, bounds, phase, cx| {
 106                if phase == DispatchPhase::Capture
 107                    && event.button == button
 108                    && !bounds.contains_point(&event.position)
 109                {
 110                    handler(view, event, cx)
 111                }
 112            }));
 113        self
 114    }
 115
 116    fn on_mouse_up_out(
 117        mut self,
 118        button: MouseButton,
 119        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
 120    ) -> Self
 121    where
 122        Self: Sized,
 123    {
 124        self.stateless_interactivity()
 125            .mouse_up_listeners
 126            .push(Box::new(move |view, event, bounds, phase, cx| {
 127                if phase == DispatchPhase::Capture
 128                    && event.button == button
 129                    && !bounds.contains_point(&event.position)
 130                {
 131                    handler(view, event, cx);
 132                }
 133            }));
 134        self
 135    }
 136
 137    fn on_mouse_move(
 138        mut self,
 139        handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static,
 140    ) -> Self
 141    where
 142        Self: Sized,
 143    {
 144        self.stateless_interactivity()
 145            .mouse_move_listeners
 146            .push(Box::new(move |view, event, bounds, phase, cx| {
 147                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
 148                    handler(view, event, cx);
 149                }
 150            }));
 151        self
 152    }
 153
 154    fn on_scroll_wheel(
 155        mut self,
 156        handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static,
 157    ) -> Self
 158    where
 159        Self: Sized,
 160    {
 161        self.stateless_interactivity()
 162            .scroll_wheel_listeners
 163            .push(Box::new(move |view, event, bounds, phase, cx| {
 164                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
 165                    handler(view, event, cx);
 166                }
 167            }));
 168        self
 169    }
 170
 171    fn context<C>(mut self, context: C) -> Self
 172    where
 173        Self: Sized,
 174        C: TryInto<DispatchContext>,
 175        C::Error: Debug,
 176    {
 177        self.stateless_interactivity().dispatch_context =
 178            context.try_into().expect("invalid dispatch context");
 179        self
 180    }
 181
 182    /// Capture the given action, fires during the capture phase
 183    fn capture_action<A: 'static>(
 184        mut self,
 185        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
 186    ) -> Self
 187    where
 188        Self: Sized,
 189    {
 190        self.stateless_interactivity().key_listeners.push((
 191            TypeId::of::<A>(),
 192            Box::new(move |view, event, _, phase, cx| {
 193                let event = event.downcast_ref().unwrap();
 194                if phase == DispatchPhase::Capture {
 195                    listener(view, event, cx)
 196                }
 197                None
 198            }),
 199        ));
 200        self
 201    }
 202
 203    /// Add a listener for the given action, fires during the bubble event phase
 204    fn on_action<A: 'static>(
 205        mut self,
 206        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
 207    ) -> Self
 208    where
 209        Self: Sized,
 210    {
 211        self.stateless_interactivity().key_listeners.push((
 212            TypeId::of::<A>(),
 213            Box::new(move |view, event, _, phase, cx| {
 214                let event = event.downcast_ref().unwrap();
 215                if phase == DispatchPhase::Bubble {
 216                    listener(view, event, cx)
 217                }
 218
 219                None
 220            }),
 221        ));
 222        self
 223    }
 224
 225    fn on_key_down(
 226        mut self,
 227        listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
 228    ) -> Self
 229    where
 230        Self: Sized,
 231    {
 232        self.stateless_interactivity().key_listeners.push((
 233            TypeId::of::<KeyDownEvent>(),
 234            Box::new(move |view, event, _, phase, cx| {
 235                let event = event.downcast_ref().unwrap();
 236                listener(view, event, phase, cx);
 237                None
 238            }),
 239        ));
 240        self
 241    }
 242
 243    fn on_key_up(
 244        mut self,
 245        listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
 246    ) -> Self
 247    where
 248        Self: Sized,
 249    {
 250        self.stateless_interactivity().key_listeners.push((
 251            TypeId::of::<KeyUpEvent>(),
 252            Box::new(move |view, event, _, phase, cx| {
 253                let event = event.downcast_ref().unwrap();
 254                listener(view, event, phase, cx);
 255                None
 256            }),
 257        ));
 258        self
 259    }
 260
 261    fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
 262    where
 263        Self: Sized,
 264    {
 265        self.stateless_interactivity()
 266            .drag_over_styles
 267            .push((TypeId::of::<S>(), f(StyleRefinement::default())));
 268        self
 269    }
 270
 271    fn group_drag_over<S: 'static>(
 272        mut self,
 273        group_name: impl Into<SharedString>,
 274        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
 275    ) -> Self
 276    where
 277        Self: Sized,
 278    {
 279        self.stateless_interactivity().group_drag_over_styles.push((
 280            TypeId::of::<S>(),
 281            GroupStyle {
 282                group: group_name.into(),
 283                style: f(StyleRefinement::default()),
 284            },
 285        ));
 286        self
 287    }
 288
 289    fn on_drop<W: 'static>(
 290        mut self,
 291        listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static,
 292    ) -> Self
 293    where
 294        Self: Sized,
 295    {
 296        self.stateless_interactivity().drop_listeners.push((
 297            TypeId::of::<W>(),
 298            Box::new(move |view, dragged_view, cx| {
 299                listener(view, dragged_view.downcast().unwrap(), cx);
 300            }),
 301        ));
 302        self
 303    }
 304}
 305
 306pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
 307    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V>;
 308
 309    fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
 310    where
 311        Self: Sized,
 312    {
 313        self.stateful_interactivity().active_style = f(StyleRefinement::default());
 314        self
 315    }
 316
 317    fn group_active(
 318        mut self,
 319        group_name: impl Into<SharedString>,
 320        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
 321    ) -> Self
 322    where
 323        Self: Sized,
 324    {
 325        self.stateful_interactivity().group_active_style = Some(GroupStyle {
 326            group: group_name.into(),
 327            style: f(StyleRefinement::default()),
 328        });
 329        self
 330    }
 331
 332    fn on_click(
 333        mut self,
 334        listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static,
 335    ) -> Self
 336    where
 337        Self: Sized,
 338    {
 339        self.stateful_interactivity()
 340            .click_listeners
 341            .push(Box::new(move |view, event, cx| listener(view, event, cx)));
 342        self
 343    }
 344
 345    fn on_drag<W>(
 346        mut self,
 347        listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
 348    ) -> Self
 349    where
 350        Self: Sized,
 351        W: 'static + Render,
 352    {
 353        debug_assert!(
 354            self.stateful_interactivity().drag_listener.is_none(),
 355            "calling on_drag more than once on the same element is not supported"
 356        );
 357        self.stateful_interactivity().drag_listener =
 358            Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
 359                view: listener(view_state, cx).into(),
 360                cursor_offset,
 361            }));
 362        self
 363    }
 364
 365    fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self
 366    where
 367        Self: Sized,
 368    {
 369        debug_assert!(
 370            self.stateful_interactivity().hover_listener.is_none(),
 371            "calling on_hover more than once on the same element is not supported"
 372        );
 373        self.stateful_interactivity().hover_listener = Some(Box::new(listener));
 374        self
 375    }
 376
 377    fn tooltip<W>(
 378        mut self,
 379        build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
 380    ) -> Self
 381    where
 382        Self: Sized,
 383        W: 'static + Render,
 384    {
 385        debug_assert!(
 386            self.stateful_interactivity().tooltip_builder.is_none(),
 387            "calling tooltip more than once on the same element is not supported"
 388        );
 389        self.stateful_interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| {
 390            build_tooltip(view_state, cx).into()
 391        }));
 392
 393        self
 394    }
 395}
 396
 397pub trait ElementInteractivity<V: 'static>: 'static {
 398    fn as_stateless(&self) -> &StatelessInteractivity<V>;
 399    fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V>;
 400    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
 401    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
 402
 403    fn initialize<R>(
 404        &mut self,
 405        cx: &mut ViewContext<V>,
 406        f: impl FnOnce(&mut ViewContext<V>) -> R,
 407    ) -> R {
 408        if let Some(stateful) = self.as_stateful_mut() {
 409            cx.with_element_id(stateful.id.clone(), |global_id, cx| {
 410                stateful.key_listeners.push((
 411                    TypeId::of::<KeyDownEvent>(),
 412                    Box::new(move |_, key_down, context, phase, cx| {
 413                        if phase == DispatchPhase::Bubble {
 414                            let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
 415                            if let KeyMatch::Some(action) =
 416                                cx.match_keystroke(&global_id, &key_down.keystroke, context)
 417                            {
 418                                return Some(action);
 419                            }
 420                        }
 421
 422                        None
 423                    }),
 424                ));
 425                let result = stateful.stateless.initialize(cx, f);
 426                stateful.key_listeners.pop();
 427                result
 428            })
 429        } else {
 430            let stateless = self.as_stateless_mut();
 431            cx.with_key_dispatch_context(stateless.dispatch_context.clone(), |cx| {
 432                cx.with_key_listeners(mem::take(&mut stateless.key_listeners), f)
 433            })
 434        }
 435    }
 436
 437    fn refine_style(
 438        &self,
 439        style: &mut Style,
 440        bounds: Bounds<Pixels>,
 441        element_state: &InteractiveElementState,
 442        cx: &mut ViewContext<V>,
 443    ) {
 444        let mouse_position = cx.mouse_position();
 445        let stateless = self.as_stateless();
 446        if let Some(group_hover) = stateless.group_hover_style.as_ref() {
 447            if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
 448                if group_bounds.contains_point(&mouse_position) {
 449                    style.refine(&group_hover.style);
 450                }
 451            }
 452        }
 453        if bounds.contains_point(&mouse_position) {
 454            style.refine(&stateless.hover_style);
 455        }
 456
 457        if let Some(drag) = cx.active_drag.take() {
 458            for (state_type, group_drag_style) in &self.as_stateless().group_drag_over_styles {
 459                if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
 460                    if *state_type == drag.view.entity_type()
 461                        && group_bounds.contains_point(&mouse_position)
 462                    {
 463                        style.refine(&group_drag_style.style);
 464                    }
 465                }
 466            }
 467
 468            for (state_type, drag_over_style) in &self.as_stateless().drag_over_styles {
 469                if *state_type == drag.view.entity_type() && bounds.contains_point(&mouse_position)
 470                {
 471                    style.refine(drag_over_style);
 472                }
 473            }
 474
 475            cx.active_drag = Some(drag);
 476        }
 477
 478        if let Some(stateful) = self.as_stateful() {
 479            let active_state = element_state.active_state.lock();
 480            if active_state.group {
 481                if let Some(group_style) = stateful.group_active_style.as_ref() {
 482                    style.refine(&group_style.style);
 483                }
 484            }
 485            if active_state.element {
 486                style.refine(&stateful.active_style);
 487            }
 488        }
 489    }
 490
 491    fn paint(
 492        &mut self,
 493        bounds: Bounds<Pixels>,
 494        content_size: Size<Pixels>,
 495        overflow: Point<Overflow>,
 496        element_state: &mut InteractiveElementState,
 497        cx: &mut ViewContext<V>,
 498    ) {
 499        let stateless = self.as_stateless_mut();
 500        for listener in stateless.mouse_down_listeners.drain(..) {
 501            cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
 502                listener(state, event, &bounds, phase, cx);
 503            })
 504        }
 505
 506        for listener in stateless.mouse_up_listeners.drain(..) {
 507            cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
 508                listener(state, event, &bounds, phase, cx);
 509            })
 510        }
 511
 512        for listener in stateless.mouse_move_listeners.drain(..) {
 513            cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
 514                listener(state, event, &bounds, phase, cx);
 515            })
 516        }
 517
 518        for listener in stateless.scroll_wheel_listeners.drain(..) {
 519            cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
 520                listener(state, event, &bounds, phase, cx);
 521            })
 522        }
 523
 524        let hover_group_bounds = stateless
 525            .group_hover_style
 526            .as_ref()
 527            .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
 528
 529        if let Some(group_bounds) = hover_group_bounds {
 530            let hovered = group_bounds.contains_point(&cx.mouse_position());
 531            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
 532                if phase == DispatchPhase::Capture {
 533                    if group_bounds.contains_point(&event.position) != hovered {
 534                        cx.notify();
 535                    }
 536                }
 537            });
 538        }
 539
 540        if stateless.hover_style.is_some()
 541            || (cx.active_drag.is_some() && !stateless.drag_over_styles.is_empty())
 542        {
 543            let hovered = bounds.contains_point(&cx.mouse_position());
 544            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
 545                if phase == DispatchPhase::Capture {
 546                    if bounds.contains_point(&event.position) != hovered {
 547                        cx.notify();
 548                    }
 549                }
 550            });
 551        }
 552
 553        if cx.active_drag.is_some() {
 554            let drop_listeners = mem::take(&mut stateless.drop_listeners);
 555            cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
 556                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
 557                    if let Some(drag_state_type) =
 558                        cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
 559                    {
 560                        for (drop_state_type, listener) in &drop_listeners {
 561                            if *drop_state_type == drag_state_type {
 562                                let drag = cx
 563                                    .active_drag
 564                                    .take()
 565                                    .expect("checked for type drag state type above");
 566                                listener(view, drag.view.clone(), cx);
 567                                cx.notify();
 568                                cx.stop_propagation();
 569                            }
 570                        }
 571                    }
 572                }
 573            });
 574        }
 575
 576        if let Some(stateful) = self.as_stateful_mut() {
 577            let click_listeners = mem::take(&mut stateful.click_listeners);
 578            let drag_listener = mem::take(&mut stateful.drag_listener);
 579
 580            if !click_listeners.is_empty() || drag_listener.is_some() {
 581                let pending_mouse_down = element_state.pending_mouse_down.clone();
 582                let mouse_down = pending_mouse_down.lock().clone();
 583                if let Some(mouse_down) = mouse_down {
 584                    if let Some(drag_listener) = drag_listener {
 585                        let active_state = element_state.active_state.clone();
 586
 587                        cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
 588                            if cx.active_drag.is_some() {
 589                                if phase == DispatchPhase::Capture {
 590                                    cx.notify();
 591                                }
 592                            } else if phase == DispatchPhase::Bubble
 593                                && bounds.contains_point(&event.position)
 594                                && (event.position - mouse_down.position).magnitude()
 595                                    > DRAG_THRESHOLD
 596                            {
 597                                *active_state.lock() = ActiveState::default();
 598                                let cursor_offset = event.position - bounds.origin;
 599                                let drag = drag_listener(view_state, cursor_offset, cx);
 600                                cx.active_drag = Some(drag);
 601                                cx.notify();
 602                                cx.stop_propagation();
 603                            }
 604                        });
 605                    }
 606
 607                    cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| {
 608                        if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position)
 609                        {
 610                            let mouse_click = ClickEvent {
 611                                down: mouse_down.clone(),
 612                                up: event.clone(),
 613                            };
 614                            for listener in &click_listeners {
 615                                listener(view_state, &mouse_click, cx);
 616                            }
 617                        }
 618                        *pending_mouse_down.lock() = None;
 619                    });
 620                } else {
 621                    cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| {
 622                        if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position)
 623                        {
 624                            *pending_mouse_down.lock() = Some(event.clone());
 625                        }
 626                    });
 627                }
 628            }
 629
 630            if let Some(hover_listener) = stateful.hover_listener.take() {
 631                let was_hovered = element_state.hover_state.clone();
 632                let has_mouse_down = element_state.pending_mouse_down.clone();
 633
 634                cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
 635                    if phase != DispatchPhase::Bubble {
 636                        return;
 637                    }
 638                    let is_hovered =
 639                        bounds.contains_point(&event.position) && has_mouse_down.lock().is_none();
 640                    let mut was_hovered = was_hovered.lock();
 641
 642                    if is_hovered != was_hovered.clone() {
 643                        *was_hovered = is_hovered;
 644                        drop(was_hovered);
 645
 646                        hover_listener(view_state, is_hovered, cx);
 647                    }
 648                });
 649            }
 650
 651            if let Some(tooltip_builder) = stateful.tooltip_builder.take() {
 652                let active_tooltip = element_state.active_tooltip.clone();
 653                let pending_mouse_down = element_state.pending_mouse_down.clone();
 654
 655                cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
 656                    if phase != DispatchPhase::Bubble {
 657                        return;
 658                    }
 659
 660                    let is_hovered = bounds.contains_point(&event.position)
 661                        && pending_mouse_down.lock().is_none();
 662                    if !is_hovered {
 663                        active_tooltip.lock().take();
 664                        return;
 665                    }
 666
 667                    if active_tooltip.lock().is_none() {
 668                        let task = cx.spawn({
 669                            let active_tooltip = active_tooltip.clone();
 670                            let tooltip_builder = tooltip_builder.clone();
 671
 672                            move |view, mut cx| async move {
 673                                cx.background_executor().timer(TOOLTIP_DELAY).await;
 674                                view.update(&mut cx, move |view_state, cx| {
 675                                    active_tooltip.lock().replace(ActiveTooltip {
 676                                        waiting: None,
 677                                        tooltip: Some(AnyTooltip {
 678                                            view: tooltip_builder(view_state, cx),
 679                                            cursor_offset: cx.mouse_position() + TOOLTIP_OFFSET,
 680                                        }),
 681                                    });
 682                                    cx.notify();
 683                                })
 684                                .ok();
 685                            }
 686                        });
 687                        active_tooltip.lock().replace(ActiveTooltip {
 688                            waiting: Some(task),
 689                            tooltip: None,
 690                        });
 691                    }
 692                });
 693
 694                if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() {
 695                    if active_tooltip.tooltip.is_some() {
 696                        cx.active_tooltip = active_tooltip.tooltip.clone()
 697                    }
 698                }
 699            }
 700
 701            let active_state = element_state.active_state.clone();
 702            if active_state.lock().is_none() {
 703                let active_group_bounds = stateful
 704                    .group_active_style
 705                    .as_ref()
 706                    .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
 707                cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
 708                    if phase == DispatchPhase::Bubble {
 709                        let group = active_group_bounds
 710                            .map_or(false, |bounds| bounds.contains_point(&down.position));
 711                        let element = bounds.contains_point(&down.position);
 712                        if group || element {
 713                            *active_state.lock() = ActiveState { group, element };
 714                            cx.notify();
 715                        }
 716                    }
 717                });
 718            } else {
 719                cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
 720                    if phase == DispatchPhase::Capture {
 721                        *active_state.lock() = ActiveState::default();
 722                        cx.notify();
 723                    }
 724                });
 725            }
 726
 727            if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
 728                let scroll_offset = element_state
 729                    .scroll_offset
 730                    .get_or_insert_with(Arc::default)
 731                    .clone();
 732                let line_height = cx.line_height();
 733                let scroll_max = (content_size - bounds.size).max(&Size::default());
 734
 735                cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| {
 736                    if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
 737                        let mut scroll_offset = scroll_offset.lock();
 738                        let old_scroll_offset = *scroll_offset;
 739                        let delta = event.delta.pixel_delta(line_height);
 740
 741                        if overflow.x == Overflow::Scroll {
 742                            scroll_offset.x =
 743                                (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.));
 744                        }
 745
 746                        if overflow.y == Overflow::Scroll {
 747                            scroll_offset.y =
 748                                (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.));
 749                        }
 750
 751                        if *scroll_offset != old_scroll_offset {
 752                            cx.notify();
 753                            cx.stop_propagation();
 754                        }
 755                    }
 756                });
 757            }
 758        }
 759    }
 760}
 761
 762#[derive(Deref, DerefMut)]
 763pub struct StatefulInteractivity<V> {
 764    pub id: ElementId,
 765    #[deref]
 766    #[deref_mut]
 767    stateless: StatelessInteractivity<V>,
 768    click_listeners: SmallVec<[ClickListener<V>; 2]>,
 769    active_style: StyleRefinement,
 770    group_active_style: Option<GroupStyle>,
 771    drag_listener: Option<DragListener<V>>,
 772    hover_listener: Option<HoverListener<V>>,
 773    tooltip_builder: Option<TooltipBuilder<V>>,
 774}
 775
 776impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
 777    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
 778        Some(self)
 779    }
 780
 781    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
 782        Some(self)
 783    }
 784
 785    fn as_stateless(&self) -> &StatelessInteractivity<V> {
 786        &self.stateless
 787    }
 788
 789    fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
 790        &mut self.stateless
 791    }
 792}
 793
 794impl<V> From<ElementId> for StatefulInteractivity<V> {
 795    fn from(id: ElementId) -> Self {
 796        Self {
 797            id,
 798            stateless: StatelessInteractivity::default(),
 799            click_listeners: SmallVec::new(),
 800            drag_listener: None,
 801            hover_listener: None,
 802            tooltip_builder: None,
 803            active_style: StyleRefinement::default(),
 804            group_active_style: None,
 805        }
 806    }
 807}
 808
 809type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
 810
 811pub struct StatelessInteractivity<V> {
 812    pub dispatch_context: DispatchContext,
 813    pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
 814    pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
 815    pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
 816    pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
 817    pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
 818    pub hover_style: StyleRefinement,
 819    pub group_hover_style: Option<GroupStyle>,
 820    drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
 821    group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
 822    drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
 823}
 824
 825impl<V> StatelessInteractivity<V> {
 826    pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteractivity<V> {
 827        StatefulInteractivity {
 828            id: id.into(),
 829            stateless: self,
 830            click_listeners: SmallVec::new(),
 831            drag_listener: None,
 832            hover_listener: None,
 833            tooltip_builder: None,
 834            active_style: StyleRefinement::default(),
 835            group_active_style: None,
 836        }
 837    }
 838}
 839
 840pub struct GroupStyle {
 841    pub group: SharedString,
 842    pub style: StyleRefinement,
 843}
 844
 845#[derive(Default)]
 846pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
 847
 848impl GroupBounds {
 849    pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
 850        cx.default_global::<Self>()
 851            .0
 852            .get(name)
 853            .and_then(|bounds_stack| bounds_stack.last())
 854            .cloned()
 855    }
 856
 857    pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
 858        cx.default_global::<Self>()
 859            .0
 860            .entry(name)
 861            .or_default()
 862            .push(bounds);
 863    }
 864
 865    pub fn pop(name: &SharedString, cx: &mut AppContext) {
 866        cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
 867    }
 868}
 869
 870#[derive(Copy, Clone, Default, Eq, PartialEq)]
 871struct ActiveState {
 872    pub group: bool,
 873    pub element: bool,
 874}
 875
 876impl ActiveState {
 877    pub fn is_none(&self) -> bool {
 878        !self.group && !self.element
 879    }
 880}
 881
 882#[derive(Default)]
 883pub struct InteractiveElementState {
 884    active_state: Arc<Mutex<ActiveState>>,
 885    hover_state: Arc<Mutex<bool>>,
 886    pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
 887    scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
 888    active_tooltip: Arc<Mutex<Option<ActiveTooltip>>>,
 889}
 890
 891struct ActiveTooltip {
 892    #[allow(unused)] // used to drop the task
 893    waiting: Option<Task<()>>,
 894    tooltip: Option<AnyTooltip>,
 895}
 896
 897impl InteractiveElementState {
 898    pub fn scroll_offset(&self) -> Option<Point<Pixels>> {
 899        self.scroll_offset
 900            .as_ref()
 901            .map(|offset| offset.lock().clone())
 902    }
 903
 904    pub fn track_scroll_offset(&mut self) -> Arc<Mutex<Point<Pixels>>> {
 905        self.scroll_offset
 906            .get_or_insert_with(|| Arc::new(Mutex::new(Default::default())))
 907            .clone()
 908    }
 909}
 910
 911impl<V> Default for StatelessInteractivity<V> {
 912    fn default() -> Self {
 913        Self {
 914            dispatch_context: DispatchContext::default(),
 915            mouse_down_listeners: SmallVec::new(),
 916            mouse_up_listeners: SmallVec::new(),
 917            mouse_move_listeners: SmallVec::new(),
 918            scroll_wheel_listeners: SmallVec::new(),
 919            key_listeners: SmallVec::new(),
 920            hover_style: StyleRefinement::default(),
 921            group_hover_style: None,
 922            drag_over_styles: SmallVec::new(),
 923            group_drag_over_styles: SmallVec::new(),
 924            drop_listeners: SmallVec::new(),
 925        }
 926    }
 927}
 928
 929impl<V: 'static> ElementInteractivity<V> for StatelessInteractivity<V> {
 930    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
 931        None
 932    }
 933
 934    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
 935        None
 936    }
 937
 938    fn as_stateless(&self) -> &StatelessInteractivity<V> {
 939        self
 940    }
 941
 942    fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
 943        self
 944    }
 945}
 946
 947#[derive(Clone, Debug, Eq, PartialEq)]
 948pub struct KeyDownEvent {
 949    pub keystroke: Keystroke,
 950    pub is_held: bool,
 951}
 952
 953#[derive(Clone, Debug)]
 954pub struct KeyUpEvent {
 955    pub keystroke: Keystroke,
 956}
 957
 958#[derive(Clone, Debug, Default)]
 959pub struct ModifiersChangedEvent {
 960    pub modifiers: Modifiers,
 961}
 962
 963impl Deref for ModifiersChangedEvent {
 964    type Target = Modifiers;
 965
 966    fn deref(&self) -> &Self::Target {
 967        &self.modifiers
 968    }
 969}
 970
 971/// The phase of a touch motion event.
 972/// Based on the winit enum of the same name.
 973#[derive(Clone, Copy, Debug)]
 974pub enum TouchPhase {
 975    Started,
 976    Moved,
 977    Ended,
 978}
 979
 980#[derive(Clone, Debug, Default)]
 981pub struct MouseDownEvent {
 982    pub button: MouseButton,
 983    pub position: Point<Pixels>,
 984    pub modifiers: Modifiers,
 985    pub click_count: usize,
 986}
 987
 988#[derive(Clone, Debug, Default)]
 989pub struct MouseUpEvent {
 990    pub button: MouseButton,
 991    pub position: Point<Pixels>,
 992    pub modifiers: Modifiers,
 993    pub click_count: usize,
 994}
 995
 996#[derive(Clone, Debug, Default)]
 997pub struct ClickEvent {
 998    pub down: MouseDownEvent,
 999    pub up: MouseUpEvent,
1000}
1001
1002pub struct Drag<S, R, V, E>
1003where
1004    R: Fn(&mut V, &mut ViewContext<V>) -> E,
1005    V: 'static,
1006    E: Component<()>,
1007{
1008    pub state: S,
1009    pub render_drag_handle: R,
1010    view_type: PhantomData<V>,
1011}
1012
1013impl<S, R, V, E> Drag<S, R, V, E>
1014where
1015    R: Fn(&mut V, &mut ViewContext<V>) -> E,
1016    V: 'static,
1017    E: Component<()>,
1018{
1019    pub fn new(state: S, render_drag_handle: R) -> Self {
1020        Drag {
1021            state,
1022            render_drag_handle,
1023            view_type: PhantomData,
1024        }
1025    }
1026}
1027
1028// impl<S, R, V, E> Render for Drag<S, R, V, E> {
1029//     // fn render(&mut self, cx: ViewContext<Self>) ->
1030// }
1031
1032#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
1033pub enum MouseButton {
1034    Left,
1035    Right,
1036    Middle,
1037    Navigate(NavigationDirection),
1038}
1039
1040impl MouseButton {
1041    pub fn all() -> Vec<Self> {
1042        vec![
1043            MouseButton::Left,
1044            MouseButton::Right,
1045            MouseButton::Middle,
1046            MouseButton::Navigate(NavigationDirection::Back),
1047            MouseButton::Navigate(NavigationDirection::Forward),
1048        ]
1049    }
1050}
1051
1052impl Default for MouseButton {
1053    fn default() -> Self {
1054        Self::Left
1055    }
1056}
1057
1058#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
1059pub enum NavigationDirection {
1060    Back,
1061    Forward,
1062}
1063
1064impl Default for NavigationDirection {
1065    fn default() -> Self {
1066        Self::Back
1067    }
1068}
1069
1070#[derive(Clone, Debug, Default)]
1071pub struct MouseMoveEvent {
1072    pub position: Point<Pixels>,
1073    pub pressed_button: Option<MouseButton>,
1074    pub modifiers: Modifiers,
1075}
1076
1077#[derive(Clone, Debug)]
1078pub struct ScrollWheelEvent {
1079    pub position: Point<Pixels>,
1080    pub delta: ScrollDelta,
1081    pub modifiers: Modifiers,
1082    pub touch_phase: TouchPhase,
1083}
1084
1085impl Deref for ScrollWheelEvent {
1086    type Target = Modifiers;
1087
1088    fn deref(&self) -> &Self::Target {
1089        &self.modifiers
1090    }
1091}
1092
1093#[derive(Clone, Copy, Debug)]
1094pub enum ScrollDelta {
1095    Pixels(Point<Pixels>),
1096    Lines(Point<f32>),
1097}
1098
1099impl Default for ScrollDelta {
1100    fn default() -> Self {
1101        Self::Lines(Default::default())
1102    }
1103}
1104
1105impl ScrollDelta {
1106    pub fn precise(&self) -> bool {
1107        match self {
1108            ScrollDelta::Pixels(_) => true,
1109            ScrollDelta::Lines(_) => false,
1110        }
1111    }
1112
1113    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
1114        match self {
1115            ScrollDelta::Pixels(delta) => *delta,
1116            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
1117        }
1118    }
1119}
1120
1121#[derive(Clone, Debug, Default)]
1122pub struct MouseExitEvent {
1123    pub position: Point<Pixels>,
1124    pub pressed_button: Option<MouseButton>,
1125    pub modifiers: Modifiers,
1126}
1127
1128impl Deref for MouseExitEvent {
1129    type Target = Modifiers;
1130
1131    fn deref(&self) -> &Self::Target {
1132        &self.modifiers
1133    }
1134}
1135
1136#[derive(Debug, Clone, Default)]
1137pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
1138
1139impl Render for ExternalPaths {
1140    type Element = Div<Self>;
1141
1142    fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
1143        div() // Intentionally left empty because the platform will render icons for the dragged files
1144    }
1145}
1146
1147#[derive(Debug, Clone)]
1148pub enum FileDropEvent {
1149    Entered {
1150        position: Point<Pixels>,
1151        files: ExternalPaths,
1152    },
1153    Pending {
1154        position: Point<Pixels>,
1155    },
1156    Submit {
1157        position: Point<Pixels>,
1158    },
1159    Exited,
1160}
1161
1162#[derive(Clone, Debug)]
1163pub enum InputEvent {
1164    KeyDown(KeyDownEvent),
1165    KeyUp(KeyUpEvent),
1166    ModifiersChanged(ModifiersChangedEvent),
1167    MouseDown(MouseDownEvent),
1168    MouseUp(MouseUpEvent),
1169    MouseMove(MouseMoveEvent),
1170    MouseExited(MouseExitEvent),
1171    ScrollWheel(ScrollWheelEvent),
1172    FileDrop(FileDropEvent),
1173}
1174
1175impl InputEvent {
1176    pub fn position(&self) -> Option<Point<Pixels>> {
1177        match self {
1178            InputEvent::KeyDown { .. } => None,
1179            InputEvent::KeyUp { .. } => None,
1180            InputEvent::ModifiersChanged { .. } => None,
1181            InputEvent::MouseDown(event) => Some(event.position),
1182            InputEvent::MouseUp(event) => Some(event.position),
1183            InputEvent::MouseMove(event) => Some(event.position),
1184            InputEvent::MouseExited(event) => Some(event.position),
1185            InputEvent::ScrollWheel(event) => Some(event.position),
1186            InputEvent::FileDrop(FileDropEvent::Exited) => None,
1187            InputEvent::FileDrop(
1188                FileDropEvent::Entered { position, .. }
1189                | FileDropEvent::Pending { position, .. }
1190                | FileDropEvent::Submit { position, .. },
1191            ) => Some(*position),
1192        }
1193    }
1194
1195    pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
1196        match self {
1197            InputEvent::KeyDown { .. } => None,
1198            InputEvent::KeyUp { .. } => None,
1199            InputEvent::ModifiersChanged { .. } => None,
1200            InputEvent::MouseDown(event) => Some(event),
1201            InputEvent::MouseUp(event) => Some(event),
1202            InputEvent::MouseMove(event) => Some(event),
1203            InputEvent::MouseExited(event) => Some(event),
1204            InputEvent::ScrollWheel(event) => Some(event),
1205            InputEvent::FileDrop(event) => Some(event),
1206        }
1207    }
1208
1209    pub fn keyboard_event<'a>(&'a self) -> Option<&'a dyn Any> {
1210        match self {
1211            InputEvent::KeyDown(event) => Some(event),
1212            InputEvent::KeyUp(event) => Some(event),
1213            InputEvent::ModifiersChanged(event) => Some(event),
1214            InputEvent::MouseDown(_) => None,
1215            InputEvent::MouseUp(_) => None,
1216            InputEvent::MouseMove(_) => None,
1217            InputEvent::MouseExited(_) => None,
1218            InputEvent::ScrollWheel(_) => None,
1219            InputEvent::FileDrop(_) => None,
1220        }
1221    }
1222}
1223
1224pub struct FocusEvent {
1225    pub blurred: Option<FocusHandle>,
1226    pub focused: Option<FocusHandle>,
1227}
1228
1229pub type MouseDownListener<V> = Box<
1230    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
1231>;
1232pub type MouseUpListener<V> = Box<
1233    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
1234>;
1235
1236pub type MouseMoveListener<V> = Box<
1237    dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
1238>;
1239
1240pub type ScrollWheelListener<V> = Box<
1241    dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
1242        + 'static,
1243>;
1244
1245pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static>;
1246
1247pub(crate) type DragListener<V> =
1248    Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
1249
1250pub(crate) type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
1251
1252pub(crate) type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
1253
1254pub type KeyListener<V> = Box<
1255    dyn Fn(
1256            &mut V,
1257            &dyn Any,
1258            &[&DispatchContext],
1259            DispatchPhase,
1260            &mut ViewContext<V>,
1261        ) -> Option<Box<dyn Action>>
1262        + 'static,
1263>;
1264
1265#[cfg(test)]
1266mod test {
1267    use crate::{
1268        self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render,
1269        StatefulInteractivity, StatelessInteractive, TestAppContext, VisualContext,
1270    };
1271
1272    struct TestView {
1273        saw_key_down: bool,
1274        saw_action: bool,
1275        focus_handle: FocusHandle,
1276    }
1277
1278    actions!(TestAction);
1279
1280    impl Render for TestView {
1281        type Element = Div<Self, StatefulInteractivity<Self>>;
1282
1283        fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
1284            div().id("testview").child(
1285                div()
1286                    .on_key_down(|this: &mut TestView, _, _, _| {
1287                        dbg!("ola!");
1288                        this.saw_key_down = true
1289                    })
1290                    .on_action(|this: &mut TestView, _: &TestAction, _| {
1291                        dbg!("ola!");
1292                        this.saw_action = true
1293                    })
1294                    .track_focus(&self.focus_handle),
1295            )
1296        }
1297    }
1298
1299    #[gpui::test]
1300    fn test_on_events(cx: &mut TestAppContext) {
1301        let window = cx.update(|cx| {
1302            cx.open_window(Default::default(), |cx| {
1303                cx.build_view(|cx| TestView {
1304                    saw_key_down: false,
1305                    saw_action: false,
1306                    focus_handle: cx.focus_handle(),
1307                })
1308            })
1309        });
1310
1311        cx.update(|cx| {
1312            cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, None)]);
1313        });
1314
1315        window
1316            .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
1317            .unwrap();
1318
1319        cx.dispatch_keystroke(*window, Keystroke::parse("space").unwrap(), false);
1320        cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
1321
1322        window
1323            .update(cx, |test_view, _| {
1324                assert!(test_view.saw_key_down || test_view.saw_action);
1325                assert!(test_view.saw_key_down);
1326                assert!(test_view.saw_action);
1327            })
1328            .unwrap();
1329    }
1330}