interactive.rs

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