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