scrollbar.rs

   1use std::{
   2    any::Any,
   3    fmt::Debug,
   4    ops::Not,
   5    time::{Duration, Instant},
   6};
   7
   8use gpui::{
   9    Along, App, AppContext as _, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Context,
  10    Corner, Corners, CursorStyle, DispatchPhase, Div, Edges, Element, ElementId, Entity, EntityId,
  11    GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero,
  12    LayoutId, ListState, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Negate,
  13    ParentElement, Pixels, Point, Position, Render, ScrollHandle, ScrollWheelEvent, Size, Stateful,
  14    StatefulInteractiveElement, Style, Styled, Task, UniformListDecoration,
  15    UniformListScrollHandle, Window, ease_in_out, prelude::FluentBuilder as _, px, quad, relative,
  16    size,
  17};
  18use settings::SettingsStore;
  19use smallvec::SmallVec;
  20use theme::ActiveTheme as _;
  21use util::ResultExt;
  22
  23use std::ops::Range;
  24
  25use crate::scrollbars::{ScrollbarAutoHide, ScrollbarVisibility, ShowScrollbar};
  26
  27const SCROLLBAR_HIDE_DELAY_INTERVAL: Duration = Duration::from_secs(1);
  28const SCROLLBAR_HIDE_DURATION: Duration = Duration::from_millis(400);
  29const SCROLLBAR_SHOW_DURATION: Duration = Duration::from_millis(50);
  30
  31const SCROLLBAR_PADDING: Pixels = px(4.);
  32
  33pub mod scrollbars {
  34    use gpui::{App, Global};
  35    use schemars::JsonSchema;
  36    use serde::{Deserialize, Serialize};
  37    use settings::Settings;
  38
  39    /// When to show the scrollbar in the editor.
  40    ///
  41    /// Default: auto
  42    #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
  43    #[serde(rename_all = "snake_case")]
  44    pub enum ShowScrollbar {
  45        /// Show the scrollbar if there's important information or
  46        /// follow the system's configured behavior.
  47        #[default]
  48        Auto,
  49        /// Match the system's configured behavior.
  50        System,
  51        /// Always show the scrollbar.
  52        Always,
  53        /// Never show the scrollbar.
  54        Never,
  55    }
  56
  57    impl From<settings::ShowScrollbar> for ShowScrollbar {
  58        fn from(value: settings::ShowScrollbar) -> Self {
  59            match value {
  60                settings::ShowScrollbar::Auto => ShowScrollbar::Auto,
  61                settings::ShowScrollbar::System => ShowScrollbar::System,
  62                settings::ShowScrollbar::Always => ShowScrollbar::Always,
  63                settings::ShowScrollbar::Never => ShowScrollbar::Never,
  64            }
  65        }
  66    }
  67
  68    pub trait GlobalSetting {
  69        fn get_value(cx: &App) -> &Self;
  70    }
  71
  72    impl<T: Settings> GlobalSetting for T {
  73        fn get_value(cx: &App) -> &T {
  74            T::get_global(cx)
  75        }
  76    }
  77
  78    pub trait ScrollbarVisibility: GlobalSetting + 'static {
  79        fn visibility(&self, cx: &App) -> ShowScrollbar;
  80    }
  81
  82    #[derive(Default)]
  83    pub struct ScrollbarAutoHide(pub bool);
  84
  85    impl ScrollbarAutoHide {
  86        pub fn should_hide(&self) -> bool {
  87            self.0
  88        }
  89    }
  90
  91    impl Global for ScrollbarAutoHide {}
  92}
  93
  94fn get_scrollbar_state<T>(
  95    mut config: Scrollbars<T>,
  96    caller_location: &'static std::panic::Location,
  97    window: &mut Window,
  98    cx: &mut App,
  99) -> Entity<ScrollbarStateWrapper<T>>
 100where
 101    T: ScrollableHandle,
 102{
 103    let element_id = config.id.take().unwrap_or_else(|| caller_location.into());
 104
 105    window.use_keyed_state(element_id, cx, |window, cx| {
 106        let parent_id = cx.entity_id();
 107        ScrollbarStateWrapper(
 108            cx.new(|cx| ScrollbarState::new_from_config(config, parent_id, window, cx)),
 109        )
 110    })
 111}
 112
 113pub trait WithScrollbar: Sized {
 114    type Output;
 115
 116    fn custom_scrollbars<T>(
 117        self,
 118        config: Scrollbars<T>,
 119        window: &mut Window,
 120        cx: &mut App,
 121    ) -> Self::Output
 122    where
 123        T: ScrollableHandle;
 124
 125    // TODO: account for these cases properly
 126    // #[track_caller]
 127    // fn horizontal_scrollbar(self, window: &mut Window, cx: &mut App) -> Self::Output {
 128    //     self.custom_scrollbars(
 129    //         Scrollbars::new(ScrollAxes::Horizontal).ensure_id(core::panic::Location::caller()),
 130    //         window,
 131    //         cx,
 132    //     )
 133    // }
 134
 135    // #[track_caller]
 136    // fn vertical_scrollbar(self, window: &mut Window, cx: &mut App) -> Self::Output {
 137    //     self.custom_scrollbars(
 138    //         Scrollbars::new(ScrollAxes::Vertical).ensure_id(core::panic::Location::caller()),
 139    //         window,
 140    //         cx,
 141    //     )
 142    // }
 143
 144    #[track_caller]
 145    fn vertical_scrollbar_for<ScrollHandle: ScrollableHandle>(
 146        self,
 147        scroll_handle: ScrollHandle,
 148        window: &mut Window,
 149        cx: &mut App,
 150    ) -> Self::Output {
 151        self.custom_scrollbars(
 152            Scrollbars::new(ScrollAxes::Vertical)
 153                .tracked_scroll_handle(scroll_handle)
 154                .ensure_id(core::panic::Location::caller()),
 155            window,
 156            cx,
 157        )
 158    }
 159}
 160
 161impl WithScrollbar for Stateful<Div> {
 162    type Output = Self;
 163
 164    #[track_caller]
 165    fn custom_scrollbars<T>(
 166        self,
 167        config: Scrollbars<T>,
 168        window: &mut Window,
 169        cx: &mut App,
 170    ) -> Self::Output
 171    where
 172        T: ScrollableHandle,
 173    {
 174        render_scrollbar(
 175            get_scrollbar_state(config, std::panic::Location::caller(), window, cx),
 176            self,
 177            cx,
 178        )
 179    }
 180}
 181
 182impl WithScrollbar for Div {
 183    type Output = Stateful<Div>;
 184
 185    #[track_caller]
 186    fn custom_scrollbars<T>(
 187        self,
 188        config: Scrollbars<T>,
 189        window: &mut Window,
 190        cx: &mut App,
 191    ) -> Self::Output
 192    where
 193        T: ScrollableHandle,
 194    {
 195        let scrollbar = get_scrollbar_state(config, std::panic::Location::caller(), window, cx);
 196        // We know this ID stays consistent as long as the element is rendered for
 197        // consecutive frames, which is sufficient for our use case here
 198        let scrollbar_entity_id = scrollbar.entity_id();
 199
 200        render_scrollbar(
 201            scrollbar,
 202            self.id(("track-scroll", scrollbar_entity_id)),
 203            cx,
 204        )
 205    }
 206}
 207
 208fn render_scrollbar<T>(
 209    scrollbar: Entity<ScrollbarStateWrapper<T>>,
 210    div: Stateful<Div>,
 211    cx: &App,
 212) -> Stateful<Div>
 213where
 214    T: ScrollableHandle,
 215{
 216    let state = &scrollbar.read(cx).0;
 217
 218    div.when_some(state.read(cx).handle_to_track(), |this, handle| {
 219        this.track_scroll(handle).when_some(
 220            state.read(cx).visible_axes(),
 221            |this, axes| match axes {
 222                ScrollAxes::Horizontal => this.overflow_x_scroll(),
 223                ScrollAxes::Vertical => this.overflow_y_scroll(),
 224                ScrollAxes::Both => this.overflow_scroll(),
 225            },
 226        )
 227    })
 228    .when_some(
 229        state
 230            .read(cx)
 231            .space_to_reserve_for(ScrollbarAxis::Horizontal),
 232        |this, space| this.pb(space),
 233    )
 234    .when_some(
 235        state.read(cx).space_to_reserve_for(ScrollbarAxis::Vertical),
 236        |this, space| this.pr(space),
 237    )
 238    .child(state.clone())
 239}
 240
 241impl<T: ScrollableHandle> UniformListDecoration for ScrollbarStateWrapper<T> {
 242    fn compute(
 243        &self,
 244        _visible_range: Range<usize>,
 245        _bounds: Bounds<Pixels>,
 246        scroll_offset: Point<Pixels>,
 247        _item_height: Pixels,
 248        _item_count: usize,
 249        _window: &mut Window,
 250        _cx: &mut App,
 251    ) -> gpui::AnyElement {
 252        ScrollbarElement {
 253            origin: scroll_offset.negate(),
 254            state: self.0.clone(),
 255        }
 256        .into_any()
 257    }
 258}
 259
 260// impl WithScrollbar for UniformList {
 261//     type Output = Self;
 262
 263//     #[track_caller]
 264//     fn custom_scrollbars<S, T>(
 265//         self,
 266//         config: Scrollbars<S, T>,
 267//         window: &mut Window,
 268//         cx: &mut App,
 269//     ) -> Self::Output
 270//     where
 271//         S: ScrollbarVisibilitySetting,
 272//         T: ScrollableHandle,
 273//     {
 274//         let scrollbar = get_scrollbar_state(config, std::panic::Location::caller(), window, cx);
 275//         self.when_some(
 276//             scrollbar.read_with(cx, |wrapper, cx| {
 277//                 wrapper
 278//                     .0
 279//                     .read(cx)
 280//                     .handle_to_track::<UniformListScrollHandle>()
 281//                     .cloned()
 282//             }),
 283//             |this, handle| this.track_scroll(handle),
 284//         )
 285//         .with_decoration(scrollbar)
 286//     }
 287// }
 288
 289#[derive(Copy, Clone, PartialEq, Eq)]
 290enum ShowBehavior {
 291    Always,
 292    Autohide,
 293    Never,
 294}
 295
 296impl ShowBehavior {
 297    fn from_setting(setting: ShowScrollbar, cx: &mut App) -> Self {
 298        match setting {
 299            ShowScrollbar::Never => Self::Never,
 300            ShowScrollbar::Auto => Self::Autohide,
 301            ShowScrollbar::System => {
 302                if cx.default_global::<ScrollbarAutoHide>().should_hide() {
 303                    Self::Autohide
 304                } else {
 305                    Self::Always
 306                }
 307            }
 308            ShowScrollbar::Always => Self::Always,
 309        }
 310    }
 311}
 312
 313pub enum ScrollAxes {
 314    Horizontal,
 315    Vertical,
 316    Both,
 317}
 318
 319impl ScrollAxes {
 320    fn apply_to<T>(self, point: Point<T>, value: T) -> Point<T>
 321    where
 322        T: Debug + Default + PartialEq + Clone,
 323    {
 324        match self {
 325            Self::Horizontal => point.apply_along(ScrollbarAxis::Horizontal, |_| value),
 326            Self::Vertical => point.apply_along(ScrollbarAxis::Vertical, |_| value),
 327            Self::Both => Point::new(value.clone(), value),
 328        }
 329    }
 330}
 331
 332#[derive(Clone, Debug, Default, PartialEq)]
 333enum ReservedSpace {
 334    #[default]
 335    None,
 336    Thumb,
 337    Track(Hsla),
 338}
 339
 340impl ReservedSpace {
 341    fn is_visible(&self) -> bool {
 342        *self != ReservedSpace::None
 343    }
 344
 345    fn needs_scroll_track(&self) -> bool {
 346        matches!(self, ReservedSpace::Track(_))
 347    }
 348
 349    fn track_color(&self) -> Option<Hsla> {
 350        match self {
 351            ReservedSpace::Track(color) => Some(*color),
 352            _ => None,
 353        }
 354    }
 355}
 356
 357#[derive(Debug, Default, Clone, Copy)]
 358enum ScrollbarWidth {
 359    #[default]
 360    Normal,
 361    Small,
 362    XSmall,
 363}
 364
 365impl ScrollbarWidth {
 366    fn to_pixels(&self) -> Pixels {
 367        match self {
 368            ScrollbarWidth::Normal => px(8.),
 369            ScrollbarWidth::Small => px(6.),
 370            ScrollbarWidth::XSmall => px(4.),
 371        }
 372    }
 373}
 374
 375#[derive(Clone)]
 376enum Handle<T: ScrollableHandle> {
 377    Tracked(T),
 378    Untracked(fn() -> T),
 379}
 380
 381#[derive(Clone)]
 382pub struct Scrollbars<T: ScrollableHandle = ScrollHandle> {
 383    id: Option<ElementId>,
 384    get_visibility: fn(&App) -> ShowScrollbar,
 385    tracked_entity: Option<Option<EntityId>>,
 386    scrollable_handle: Handle<T>,
 387    visibility: Point<ReservedSpace>,
 388    scrollbar_width: ScrollbarWidth,
 389}
 390
 391impl Scrollbars {
 392    pub fn new(show_along: ScrollAxes) -> Self {
 393        Self::new_with_setting(show_along, |_| ShowScrollbar::default())
 394    }
 395
 396    pub fn for_settings<S: ScrollbarVisibility>() -> Scrollbars {
 397        Scrollbars::new_with_setting(ScrollAxes::Both, |cx| S::get_value(cx).visibility(cx))
 398    }
 399}
 400
 401impl Scrollbars {
 402    fn new_with_setting(show_along: ScrollAxes, get_visibility: fn(&App) -> ShowScrollbar) -> Self {
 403        Self {
 404            id: None,
 405            get_visibility,
 406            scrollable_handle: Handle::Untracked(ScrollHandle::new),
 407            tracked_entity: None,
 408            visibility: show_along.apply_to(Default::default(), ReservedSpace::Thumb),
 409            scrollbar_width: ScrollbarWidth::Normal,
 410        }
 411    }
 412}
 413
 414impl<ScrollHandle: ScrollableHandle> Scrollbars<ScrollHandle> {
 415    pub fn id(mut self, id: impl Into<ElementId>) -> Self {
 416        self.id = Some(id.into());
 417        self
 418    }
 419
 420    fn ensure_id(mut self, id: impl Into<ElementId>) -> Self {
 421        if self.id.is_none() {
 422            self.id = Some(id.into());
 423        }
 424        self
 425    }
 426
 427    /// Notify the current context whenever this scrollbar gets a scroll event
 428    pub fn notify_content(mut self) -> Self {
 429        self.tracked_entity = Some(None);
 430        self
 431    }
 432
 433    /// Set a parent model which should be notified whenever this scrollbar gets a scroll event.
 434    pub fn tracked_entity(mut self, entity_id: EntityId) -> Self {
 435        self.tracked_entity = Some(Some(entity_id));
 436        self
 437    }
 438
 439    pub fn tracked_scroll_handle<TrackedHandle: ScrollableHandle>(
 440        self,
 441        tracked_scroll_handle: TrackedHandle,
 442    ) -> Scrollbars<TrackedHandle> {
 443        let Self {
 444            id,
 445            tracked_entity: tracked_entity_id,
 446            scrollbar_width,
 447            visibility,
 448            get_visibility,
 449            ..
 450        } = self;
 451
 452        Scrollbars {
 453            scrollable_handle: Handle::Tracked(tracked_scroll_handle),
 454            id,
 455            tracked_entity: tracked_entity_id,
 456            visibility,
 457            scrollbar_width,
 458            get_visibility,
 459        }
 460    }
 461
 462    pub fn show_along(mut self, along: ScrollAxes) -> Self {
 463        self.visibility = along.apply_to(self.visibility, ReservedSpace::Thumb);
 464        self
 465    }
 466
 467    pub fn with_track_along(mut self, along: ScrollAxes, background_color: Hsla) -> Self {
 468        self.visibility = along.apply_to(self.visibility, ReservedSpace::Track(background_color));
 469        self
 470    }
 471
 472    pub fn width_sm(mut self) -> Self {
 473        self.scrollbar_width = ScrollbarWidth::Small;
 474        self
 475    }
 476
 477    pub fn width_xs(mut self) -> Self {
 478        self.scrollbar_width = ScrollbarWidth::XSmall;
 479        self
 480    }
 481}
 482
 483#[derive(PartialEq, Clone, Debug)]
 484enum VisibilityState {
 485    Visible,
 486    Animating { showing: bool, delta: f32 },
 487    Hidden,
 488    Disabled,
 489}
 490
 491const DELTA_MAX: f32 = 1.0;
 492
 493impl VisibilityState {
 494    fn from_behavior(behavior: ShowBehavior) -> Self {
 495        match behavior {
 496            ShowBehavior::Always => Self::Visible,
 497            ShowBehavior::Never => Self::Disabled,
 498            ShowBehavior::Autohide => Self::for_show(),
 499        }
 500    }
 501
 502    fn for_show() -> Self {
 503        Self::Animating {
 504            showing: true,
 505            delta: Default::default(),
 506        }
 507    }
 508
 509    fn for_autohide() -> Self {
 510        Self::Animating {
 511            showing: Default::default(),
 512            delta: Default::default(),
 513        }
 514    }
 515
 516    fn is_visible(&self) -> bool {
 517        matches!(self, Self::Visible | Self::Animating { .. })
 518    }
 519
 520    #[inline]
 521    fn is_disabled(&self) -> bool {
 522        *self == VisibilityState::Disabled
 523    }
 524
 525    fn animation_progress(&self) -> Option<(f32, Duration, bool)> {
 526        match self {
 527            Self::Animating { showing, delta } => Some((
 528                *delta,
 529                if *showing {
 530                    SCROLLBAR_SHOW_DURATION
 531                } else {
 532                    SCROLLBAR_HIDE_DURATION
 533                },
 534                *showing,
 535            )),
 536            _ => None,
 537        }
 538    }
 539
 540    fn set_delta(&mut self, new_delta: f32) {
 541        match self {
 542            Self::Animating { showing, .. } if new_delta >= DELTA_MAX => {
 543                if *showing {
 544                    *self = Self::Visible;
 545                } else {
 546                    *self = Self::Hidden;
 547                }
 548            }
 549            Self::Animating { delta, .. } => *delta = new_delta,
 550            _ => {}
 551        }
 552    }
 553
 554    fn toggle_visible(&self, show_behavior: ShowBehavior) -> Self {
 555        match self {
 556            Self::Hidden => {
 557                if show_behavior == ShowBehavior::Autohide {
 558                    Self::for_show()
 559                } else {
 560                    Self::Visible
 561                }
 562            }
 563            Self::Animating {
 564                showing: false,
 565                delta: progress,
 566            } => Self::Animating {
 567                showing: true,
 568                delta: DELTA_MAX - progress,
 569            },
 570            _ => self.clone(),
 571        }
 572    }
 573}
 574
 575enum ParentHoverEvent {
 576    Within,
 577    Entered,
 578    Exited,
 579    Outside,
 580}
 581
 582/// This is used to ensure notifies within the state do not notify the parent
 583/// unintentionally.
 584struct ScrollbarStateWrapper<T: ScrollableHandle>(Entity<ScrollbarState<T>>);
 585
 586/// A scrollbar state that should be persisted across frames.
 587struct ScrollbarState<T: ScrollableHandle = ScrollHandle> {
 588    thumb_state: ThumbState,
 589    notify_id: Option<EntityId>,
 590    manually_added: bool,
 591    scroll_handle: T,
 592    width: ScrollbarWidth,
 593    show_behavior: ShowBehavior,
 594    get_visibility: fn(&App) -> ShowScrollbar,
 595    visibility: Point<ReservedSpace>,
 596    show_state: VisibilityState,
 597    mouse_in_parent: bool,
 598    last_prepaint_state: Option<ScrollbarPrepaintState>,
 599    _auto_hide_task: Option<Task<()>>,
 600}
 601
 602impl<T: ScrollableHandle> ScrollbarState<T> {
 603    fn new_from_config(
 604        config: Scrollbars<T>,
 605        parent_id: EntityId,
 606        window: &mut Window,
 607        cx: &mut Context<Self>,
 608    ) -> Self {
 609        cx.observe_global_in::<SettingsStore>(window, Self::settings_changed)
 610            .detach();
 611
 612        let (manually_added, scroll_handle) = match config.scrollable_handle {
 613            Handle::Tracked(handle) => (true, handle),
 614            Handle::Untracked(func) => (false, func()),
 615        };
 616
 617        let show_behavior = ShowBehavior::from_setting((config.get_visibility)(cx), cx);
 618        ScrollbarState {
 619            thumb_state: Default::default(),
 620            notify_id: config.tracked_entity.map(|id| id.unwrap_or(parent_id)),
 621            manually_added,
 622            scroll_handle,
 623            width: config.scrollbar_width,
 624            visibility: config.visibility,
 625            show_behavior,
 626            get_visibility: config.get_visibility,
 627            show_state: VisibilityState::from_behavior(show_behavior),
 628            mouse_in_parent: true,
 629            last_prepaint_state: None,
 630            _auto_hide_task: None,
 631        }
 632    }
 633
 634    fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 635        self.set_show_behavior(
 636            ShowBehavior::from_setting((self.get_visibility)(cx), cx),
 637            window,
 638            cx,
 639        );
 640    }
 641
 642    /// Schedules a scrollbar auto hide if no auto hide is currently in progress yet.
 643    fn schedule_auto_hide(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 644        if self._auto_hide_task.is_none() {
 645            self._auto_hide_task = (self.visible() && self.show_behavior == ShowBehavior::Autohide)
 646                .then(|| {
 647                    cx.spawn_in(window, async move |scrollbar_state, cx| {
 648                        cx.background_executor()
 649                            .timer(SCROLLBAR_HIDE_DELAY_INTERVAL)
 650                            .await;
 651                        scrollbar_state
 652                            .update(cx, |state, cx| {
 653                                if state.thumb_state == ThumbState::Inactive {
 654                                    state.set_visibility(VisibilityState::for_autohide(), cx);
 655                                }
 656                                state._auto_hide_task.take();
 657                            })
 658                            .log_err();
 659                    })
 660                });
 661        }
 662    }
 663
 664    fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 665        let visibility = self.show_state.toggle_visible(self.show_behavior);
 666        self.set_visibility(visibility, cx);
 667        self._auto_hide_task.take();
 668        self.schedule_auto_hide(window, cx);
 669    }
 670
 671    fn set_show_behavior(
 672        &mut self,
 673        behavior: ShowBehavior,
 674        window: &mut Window,
 675        cx: &mut Context<Self>,
 676    ) {
 677        if self.show_behavior != behavior {
 678            self.show_behavior = behavior;
 679            self.set_visibility(VisibilityState::from_behavior(behavior), cx);
 680            self.schedule_auto_hide(window, cx);
 681            cx.notify();
 682        }
 683    }
 684
 685    fn set_visibility(&mut self, visibility: VisibilityState, cx: &mut Context<Self>) {
 686        if self.show_state != visibility {
 687            self.show_state = visibility;
 688            cx.notify();
 689        }
 690    }
 691
 692    #[inline]
 693    fn visible_axes(&self) -> Option<ScrollAxes> {
 694        match (&self.visibility.x, &self.visibility.y) {
 695            (ReservedSpace::None, ReservedSpace::None) => None,
 696            (ReservedSpace::None, _) => Some(ScrollAxes::Vertical),
 697            (_, ReservedSpace::None) => Some(ScrollAxes::Horizontal),
 698            _ => Some(ScrollAxes::Both),
 699        }
 700    }
 701
 702    fn space_to_reserve_for(&self, axis: ScrollbarAxis) -> Option<Pixels> {
 703        (self.show_state.is_disabled().not()
 704            && self.visibility.along(axis).needs_scroll_track()
 705            && self
 706                .scroll_handle()
 707                .max_offset()
 708                .along(axis)
 709                .is_zero()
 710                .not())
 711        .then(|| self.space_to_reserve())
 712    }
 713
 714    fn space_to_reserve(&self) -> Pixels {
 715        self.width.to_pixels() + 2 * SCROLLBAR_PADDING
 716    }
 717
 718    fn handle_to_track<Handle: ScrollableHandle>(&self) -> Option<&Handle> {
 719        (!self.manually_added)
 720            .then(|| (self.scroll_handle() as &dyn Any).downcast_ref::<Handle>())
 721            .flatten()
 722    }
 723
 724    fn scroll_handle(&self) -> &T {
 725        &self.scroll_handle
 726    }
 727
 728    fn set_offset(&mut self, offset: Point<Pixels>, cx: &mut Context<Self>) {
 729        self.scroll_handle.set_offset(offset);
 730        self.notify_parent(cx);
 731        cx.notify();
 732    }
 733
 734    fn is_dragging(&self) -> bool {
 735        self.thumb_state.is_dragging()
 736    }
 737
 738    fn set_dragging(
 739        &mut self,
 740        axis: ScrollbarAxis,
 741        drag_offset: Pixels,
 742        window: &mut Window,
 743        cx: &mut Context<Self>,
 744    ) {
 745        self.set_thumb_state(ThumbState::Dragging(axis, drag_offset), window, cx);
 746        self.scroll_handle().drag_started();
 747    }
 748
 749    fn update_hovered_thumb(
 750        &mut self,
 751        position: &Point<Pixels>,
 752        window: &mut Window,
 753        cx: &mut Context<Self>,
 754    ) {
 755        self.set_thumb_state(
 756            if let Some(&ScrollbarLayout { axis, .. }) =
 757                self.last_prepaint_state.as_ref().and_then(|state| {
 758                    state
 759                        .thumb_for_position(position)
 760                        .filter(|thumb| thumb.cursor_hitbox.is_hovered(window))
 761                })
 762            {
 763                ThumbState::Hover(axis)
 764            } else {
 765                ThumbState::Inactive
 766            },
 767            window,
 768            cx,
 769        );
 770    }
 771
 772    fn set_thumb_state(&mut self, state: ThumbState, window: &mut Window, cx: &mut Context<Self>) {
 773        if self.thumb_state != state {
 774            if state == ThumbState::Inactive {
 775                self.schedule_auto_hide(window, cx);
 776            } else {
 777                self.set_visibility(self.show_state.toggle_visible(self.show_behavior), cx);
 778                self._auto_hide_task.take();
 779            }
 780            self.thumb_state = state;
 781            cx.notify();
 782        }
 783    }
 784
 785    fn update_parent_hovered(&mut self, window: &Window) -> ParentHoverEvent {
 786        let last_parent_hovered = self.mouse_in_parent;
 787        self.mouse_in_parent = self.parent_hovered(window);
 788        let state_changed = self.mouse_in_parent != last_parent_hovered;
 789        match (self.mouse_in_parent, state_changed) {
 790            (true, true) => ParentHoverEvent::Entered,
 791            (true, false) => ParentHoverEvent::Within,
 792            (false, true) => ParentHoverEvent::Exited,
 793            (false, false) => ParentHoverEvent::Outside,
 794        }
 795    }
 796
 797    fn parent_hovered(&self, window: &Window) -> bool {
 798        self.last_prepaint_state
 799            .as_ref()
 800            .is_some_and(|state| state.parent_bounds_hitbox.is_hovered(window))
 801    }
 802
 803    fn hit_for_position(&self, position: &Point<Pixels>) -> Option<&ScrollbarLayout> {
 804        self.last_prepaint_state
 805            .as_ref()
 806            .and_then(|state| state.hit_for_position(position))
 807    }
 808
 809    fn thumb_for_axis(&self, axis: ScrollbarAxis) -> Option<&ScrollbarLayout> {
 810        self.last_prepaint_state
 811            .as_ref()
 812            .and_then(|state| state.thumbs.iter().find(|thumb| thumb.axis == axis))
 813    }
 814
 815    fn thumb_ranges(
 816        &self,
 817    ) -> impl Iterator<Item = (ScrollbarAxis, Range<f32>, ReservedSpace)> + '_ {
 818        const MINIMUM_THUMB_SIZE: Pixels = px(25.);
 819        let max_offset = self.scroll_handle().max_offset();
 820        let viewport_size = self.scroll_handle().viewport().size;
 821        let current_offset = self.scroll_handle().offset();
 822
 823        [ScrollbarAxis::Horizontal, ScrollbarAxis::Vertical]
 824            .into_iter()
 825            .filter(|&axis| self.visibility.along(axis).is_visible())
 826            .flat_map(move |axis| {
 827                let max_offset = max_offset.along(axis);
 828                let viewport_size = viewport_size.along(axis);
 829                if max_offset.is_zero() || viewport_size.is_zero() {
 830                    return None;
 831                }
 832                let content_size = viewport_size + max_offset;
 833                let visible_percentage = viewport_size / content_size;
 834                let thumb_size = MINIMUM_THUMB_SIZE.max(viewport_size * visible_percentage);
 835                if thumb_size > viewport_size {
 836                    return None;
 837                }
 838                let current_offset = current_offset
 839                    .along(axis)
 840                    .clamp(-max_offset, Pixels::ZERO)
 841                    .abs();
 842                let start_offset = (current_offset / max_offset) * (viewport_size - thumb_size);
 843                let thumb_percentage_start = start_offset / viewport_size;
 844                let thumb_percentage_end = (start_offset + thumb_size) / viewport_size;
 845                Some((
 846                    axis,
 847                    thumb_percentage_start..thumb_percentage_end,
 848                    self.visibility.along(axis),
 849                ))
 850            })
 851    }
 852
 853    fn visible(&self) -> bool {
 854        self.show_state.is_visible()
 855    }
 856
 857    #[inline]
 858    fn disabled(&self) -> bool {
 859        self.show_state.is_disabled()
 860    }
 861
 862    fn notify_parent(&self, cx: &mut App) {
 863        if let Some(entity_id) = self.notify_id {
 864            cx.notify(entity_id);
 865        }
 866    }
 867}
 868
 869impl<T: ScrollableHandle> Render for ScrollbarState<T> {
 870    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 871        ScrollbarElement {
 872            state: cx.entity(),
 873            origin: Default::default(),
 874        }
 875    }
 876}
 877
 878struct ScrollbarElement<T: ScrollableHandle> {
 879    origin: Point<Pixels>,
 880    state: Entity<ScrollbarState<T>>,
 881}
 882
 883#[derive(Default, Debug, PartialEq, Eq)]
 884enum ThumbState {
 885    #[default]
 886    Inactive,
 887    Hover(ScrollbarAxis),
 888    Dragging(ScrollbarAxis, Pixels),
 889}
 890
 891impl ThumbState {
 892    fn is_dragging(&self) -> bool {
 893        matches!(*self, ThumbState::Dragging(..))
 894    }
 895}
 896
 897impl ScrollableHandle for UniformListScrollHandle {
 898    fn max_offset(&self) -> Size<Pixels> {
 899        self.0.borrow().base_handle.max_offset()
 900    }
 901
 902    fn set_offset(&self, point: Point<Pixels>) {
 903        self.0.borrow().base_handle.set_offset(point);
 904    }
 905
 906    fn offset(&self) -> Point<Pixels> {
 907        self.0.borrow().base_handle.offset()
 908    }
 909
 910    fn viewport(&self) -> Bounds<Pixels> {
 911        self.0.borrow().base_handle.bounds()
 912    }
 913}
 914
 915impl ScrollableHandle for ListState {
 916    fn max_offset(&self) -> Size<Pixels> {
 917        self.max_offset_for_scrollbar()
 918    }
 919
 920    fn set_offset(&self, point: Point<Pixels>) {
 921        self.set_offset_from_scrollbar(point);
 922    }
 923
 924    fn offset(&self) -> Point<Pixels> {
 925        self.scroll_px_offset_for_scrollbar()
 926    }
 927
 928    fn drag_started(&self) {
 929        self.scrollbar_drag_started();
 930    }
 931
 932    fn drag_ended(&self) {
 933        self.scrollbar_drag_ended();
 934    }
 935
 936    fn viewport(&self) -> Bounds<Pixels> {
 937        self.viewport_bounds()
 938    }
 939}
 940
 941impl ScrollableHandle for ScrollHandle {
 942    fn max_offset(&self) -> Size<Pixels> {
 943        self.max_offset()
 944    }
 945
 946    fn set_offset(&self, point: Point<Pixels>) {
 947        self.set_offset(point);
 948    }
 949
 950    fn offset(&self) -> Point<Pixels> {
 951        self.offset()
 952    }
 953
 954    fn viewport(&self) -> Bounds<Pixels> {
 955        self.bounds()
 956    }
 957}
 958
 959pub trait ScrollableHandle: 'static + Any + Sized {
 960    fn max_offset(&self) -> Size<Pixels>;
 961    fn set_offset(&self, point: Point<Pixels>);
 962    fn offset(&self) -> Point<Pixels>;
 963    fn viewport(&self) -> Bounds<Pixels>;
 964    fn drag_started(&self) {}
 965    fn drag_ended(&self) {}
 966
 967    fn scrollable_along(&self, axis: ScrollbarAxis) -> bool {
 968        self.max_offset().along(axis) > Pixels::ZERO
 969    }
 970    fn content_size(&self) -> Size<Pixels> {
 971        self.viewport().size + self.max_offset()
 972    }
 973}
 974
 975enum ScrollbarMouseEvent {
 976    TrackClick,
 977    ThumbDrag(Pixels),
 978}
 979
 980struct ScrollbarLayout {
 981    thumb_bounds: Bounds<Pixels>,
 982    track_bounds: Bounds<Pixels>,
 983    cursor_hitbox: Hitbox,
 984    reserved_space: ReservedSpace,
 985    track_background: Option<(Bounds<Pixels>, Hsla)>,
 986    axis: ScrollbarAxis,
 987}
 988
 989impl ScrollbarLayout {
 990    fn compute_click_offset(
 991        &self,
 992        event_position: Point<Pixels>,
 993        max_offset: Size<Pixels>,
 994        event_type: ScrollbarMouseEvent,
 995    ) -> Pixels {
 996        let Self {
 997            track_bounds,
 998            thumb_bounds,
 999            axis,
1000            ..
1001        } = self;
1002        let axis = *axis;
1003
1004        let viewport_size = track_bounds.size.along(axis);
1005        let thumb_size = thumb_bounds.size.along(axis);
1006        let thumb_offset = match event_type {
1007            ScrollbarMouseEvent::TrackClick => thumb_size / 2.,
1008            ScrollbarMouseEvent::ThumbDrag(thumb_offset) => thumb_offset,
1009        };
1010
1011        let thumb_start =
1012            (event_position.along(axis) - track_bounds.origin.along(axis) - thumb_offset)
1013                .clamp(px(0.), viewport_size - thumb_size);
1014
1015        let max_offset = max_offset.along(axis);
1016        let percentage = if viewport_size > thumb_size {
1017            thumb_start / (viewport_size - thumb_size)
1018        } else {
1019            0.
1020        };
1021
1022        -max_offset * percentage
1023    }
1024}
1025
1026impl PartialEq for ScrollbarLayout {
1027    fn eq(&self, other: &Self) -> bool {
1028        self.axis == other.axis && self.thumb_bounds == other.thumb_bounds
1029    }
1030}
1031
1032pub struct ScrollbarPrepaintState {
1033    parent_bounds_hitbox: Hitbox,
1034    thumbs: SmallVec<[ScrollbarLayout; 2]>,
1035}
1036
1037impl ScrollbarPrepaintState {
1038    fn thumb_for_position(&self, position: &Point<Pixels>) -> Option<&ScrollbarLayout> {
1039        self.thumbs
1040            .iter()
1041            .find(|info| info.thumb_bounds.contains(position))
1042    }
1043
1044    fn hit_for_position(&self, position: &Point<Pixels>) -> Option<&ScrollbarLayout> {
1045        self.thumbs.iter().find(|info| {
1046            if info.reserved_space.needs_scroll_track() {
1047                info.track_bounds.contains(position)
1048            } else {
1049                info.thumb_bounds.contains(position)
1050            }
1051        })
1052    }
1053}
1054
1055impl PartialEq for ScrollbarPrepaintState {
1056    fn eq(&self, other: &Self) -> bool {
1057        self.thumbs == other.thumbs
1058    }
1059}
1060
1061impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
1062    type RequestLayoutState = ();
1063    type PrepaintState = Option<(ScrollbarPrepaintState, Option<f32>)>;
1064
1065    fn id(&self) -> Option<ElementId> {
1066        Some(("scrollbar_animation", self.state.entity_id()).into())
1067    }
1068
1069    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1070        None
1071    }
1072
1073    fn request_layout(
1074        &mut self,
1075        _id: Option<&GlobalElementId>,
1076        _inspector_id: Option<&gpui::InspectorElementId>,
1077        window: &mut Window,
1078        cx: &mut App,
1079    ) -> (LayoutId, Self::RequestLayoutState) {
1080        let scrollbar_style = Style {
1081            position: Position::Absolute,
1082            inset: Edges::default(),
1083            size: size(relative(1.), relative(1.)).map(Into::into),
1084            ..Default::default()
1085        };
1086
1087        (window.request_layout(scrollbar_style, None, cx), ())
1088    }
1089
1090    fn prepaint(
1091        &mut self,
1092        id: Option<&GlobalElementId>,
1093        _inspector_id: Option<&gpui::InspectorElementId>,
1094        bounds: Bounds<Pixels>,
1095        _request_layout: &mut Self::RequestLayoutState,
1096        window: &mut Window,
1097        cx: &mut App,
1098    ) -> Self::PrepaintState {
1099        let prepaint_state = self
1100            .state
1101            .read(cx)
1102            .disabled()
1103            .not()
1104            .then(|| ScrollbarPrepaintState {
1105                parent_bounds_hitbox: window.insert_hitbox(bounds, HitboxBehavior::Normal),
1106                thumbs: {
1107                    let thumb_ranges = self.state.read(cx).thumb_ranges().collect::<Vec<_>>();
1108                    let width = self.state.read(cx).width.to_pixels();
1109
1110                    let additional_padding = if thumb_ranges.len() == 2 {
1111                        width
1112                    } else {
1113                        Pixels::ZERO
1114                    };
1115
1116                    thumb_ranges
1117                        .into_iter()
1118                        .map(|(axis, thumb_range, reserved_space)| {
1119                            let track_anchor = match axis {
1120                                ScrollbarAxis::Horizontal => Corner::BottomLeft,
1121                                ScrollbarAxis::Vertical => Corner::TopRight,
1122                            };
1123                            let Bounds { origin, size } = Bounds::from_corner_and_size(
1124                                track_anchor,
1125                                bounds
1126                                    .corner(track_anchor)
1127                                    .apply_along(axis.invert(), |corner| {
1128                                        corner - SCROLLBAR_PADDING
1129                                    }),
1130                                bounds.size.apply_along(axis.invert(), |_| width),
1131                            );
1132                            let scroll_track_bounds = Bounds::new(self.origin + origin, size);
1133
1134                            let padded_bounds = scroll_track_bounds.extend(match axis {
1135                                ScrollbarAxis::Horizontal => Edges {
1136                                    right: -SCROLLBAR_PADDING,
1137                                    left: -SCROLLBAR_PADDING,
1138                                    ..Default::default()
1139                                },
1140                                ScrollbarAxis::Vertical => Edges {
1141                                    top: -SCROLLBAR_PADDING,
1142                                    bottom: -SCROLLBAR_PADDING,
1143                                    ..Default::default()
1144                                },
1145                            });
1146
1147                            let available_space =
1148                                padded_bounds.size.along(axis) - additional_padding;
1149
1150                            let thumb_offset = thumb_range.start * available_space;
1151                            let thumb_end = thumb_range.end * available_space;
1152                            let thumb_bounds = Bounds::new(
1153                                padded_bounds
1154                                    .origin
1155                                    .apply_along(axis, |origin| origin + thumb_offset),
1156                                padded_bounds
1157                                    .size
1158                                    .apply_along(axis, |_| thumb_end - thumb_offset),
1159                            );
1160
1161                            ScrollbarLayout {
1162                                thumb_bounds,
1163                                track_bounds: padded_bounds,
1164                                axis,
1165                                cursor_hitbox: window.insert_hitbox(
1166                                    if reserved_space.needs_scroll_track() {
1167                                        padded_bounds
1168                                    } else {
1169                                        thumb_bounds
1170                                    },
1171                                    HitboxBehavior::BlockMouseExceptScroll,
1172                                ),
1173                                track_background: reserved_space
1174                                    .track_color()
1175                                    .map(|color| (padded_bounds.dilate(SCROLLBAR_PADDING), color)),
1176                                reserved_space,
1177                            }
1178                        })
1179                        .collect()
1180                },
1181            });
1182        if prepaint_state
1183            .as_ref()
1184            .is_some_and(|state| Some(state) != self.state.read(cx).last_prepaint_state.as_ref())
1185        {
1186            self.state
1187                .update(cx, |state, cx| state.show_scrollbars(window, cx));
1188        }
1189
1190        prepaint_state.map(|state| {
1191            let autohide_delta = self.state.read(cx).show_state.animation_progress().map(
1192                |(delta, delta_duration, should_invert)| {
1193                    window.with_element_state(id.unwrap(), |state, window| {
1194                        let state = state.unwrap_or_else(|| Instant::now());
1195                        let current = Instant::now();
1196
1197                        let new_delta = DELTA_MAX
1198                            .min(delta + (current - state).div_duration_f32(delta_duration));
1199                        self.state
1200                            .update(cx, |state, _| state.show_state.set_delta(new_delta));
1201
1202                        window.request_animation_frame();
1203                        let delta = if should_invert {
1204                            DELTA_MAX - delta
1205                        } else {
1206                            delta
1207                        };
1208                        (ease_in_out(delta), current)
1209                    })
1210                },
1211            );
1212
1213            (state, autohide_delta)
1214        })
1215    }
1216
1217    fn paint(
1218        &mut self,
1219        _id: Option<&GlobalElementId>,
1220        _inspector_id: Option<&gpui::InspectorElementId>,
1221        Bounds { origin, size }: Bounds<Pixels>,
1222        _request_layout: &mut Self::RequestLayoutState,
1223        prepaint_state: &mut Self::PrepaintState,
1224        window: &mut Window,
1225        cx: &mut App,
1226    ) {
1227        let Some((prepaint_state, autohide_fade)) = prepaint_state.take() else {
1228            return;
1229        };
1230
1231        let bounds = Bounds::new(self.origin + origin, size);
1232        window.with_content_mask(Some(ContentMask { bounds }), |window| {
1233            let colors = cx.theme().colors();
1234
1235            let capture_phase;
1236
1237            if self.state.read(cx).visible() {
1238                let thumb_state = &self.state.read(cx).thumb_state;
1239
1240                if thumb_state.is_dragging() {
1241                    capture_phase = DispatchPhase::Capture;
1242                } else {
1243                    capture_phase = DispatchPhase::Bubble;
1244                }
1245
1246                for ScrollbarLayout {
1247                    thumb_bounds,
1248                    cursor_hitbox,
1249                    axis,
1250                    reserved_space,
1251                    track_background,
1252                    ..
1253                } in &prepaint_state.thumbs
1254                {
1255                    const MAXIMUM_OPACITY: f32 = 0.7;
1256                    let (thumb_base_color, hovered) = match thumb_state {
1257                        ThumbState::Dragging(dragged_axis, _) if dragged_axis == axis => {
1258                            (colors.scrollbar_thumb_active_background, false)
1259                        }
1260                        ThumbState::Hover(hovered_axis) if hovered_axis == axis => {
1261                            (colors.scrollbar_thumb_hover_background, true)
1262                        }
1263                        _ => (colors.scrollbar_thumb_background, false),
1264                    };
1265
1266                    let blending_color = if hovered || reserved_space.needs_scroll_track() {
1267                        track_background
1268                            .map(|(_, background)| background)
1269                            .unwrap_or(colors.surface_background)
1270                    } else {
1271                        let blend_color = colors.surface_background;
1272                        blend_color.min(blend_color.alpha(MAXIMUM_OPACITY))
1273                    };
1274
1275                    let mut thumb_color = blending_color.blend(thumb_base_color);
1276
1277                    if !hovered && let Some(fade) = autohide_fade {
1278                        thumb_color.fade_out(fade);
1279                    }
1280
1281                    if let Some((track_bounds, color)) = track_background {
1282                        window.paint_quad(quad(
1283                            *track_bounds,
1284                            Corners::default(),
1285                            *color,
1286                            Edges::default(),
1287                            Hsla::transparent_black(),
1288                            BorderStyle::default(),
1289                        ));
1290                    }
1291
1292                    window.paint_quad(quad(
1293                        *thumb_bounds,
1294                        Corners::all(Pixels::MAX).clamp_radii_for_quad_size(thumb_bounds.size),
1295                        thumb_color,
1296                        Edges::default(),
1297                        Hsla::transparent_black(),
1298                        BorderStyle::default(),
1299                    ));
1300
1301                    if thumb_state.is_dragging() {
1302                        window.set_window_cursor_style(CursorStyle::Arrow);
1303                    } else {
1304                        window.set_cursor_style(CursorStyle::Arrow, cursor_hitbox);
1305                    }
1306                }
1307            } else {
1308                capture_phase = DispatchPhase::Bubble;
1309            }
1310
1311            self.state.update(cx, |state, _| {
1312                state.last_prepaint_state = Some(prepaint_state)
1313            });
1314
1315            window.on_mouse_event({
1316                let state = self.state.clone();
1317
1318                move |event: &MouseDownEvent, phase, window, cx| {
1319                    state.update(cx, |state, cx| {
1320                        let Some(scrollbar_layout) = (phase == capture_phase
1321                            && event.button == MouseButton::Left)
1322                            .then(|| state.hit_for_position(&event.position))
1323                            .flatten()
1324                        else {
1325                            return;
1326                        };
1327
1328                        let ScrollbarLayout {
1329                            thumb_bounds, axis, ..
1330                        } = scrollbar_layout;
1331
1332                        if thumb_bounds.contains(&event.position) {
1333                            let offset =
1334                                event.position.along(*axis) - thumb_bounds.origin.along(*axis);
1335                            state.set_dragging(*axis, offset, window, cx);
1336                        } else {
1337                            let scroll_handle = state.scroll_handle();
1338                            let click_offset = scrollbar_layout.compute_click_offset(
1339                                event.position,
1340                                scroll_handle.max_offset(),
1341                                ScrollbarMouseEvent::TrackClick,
1342                            );
1343                            state.set_offset(
1344                                scroll_handle.offset().apply_along(*axis, |_| click_offset),
1345                                cx,
1346                            );
1347                        };
1348
1349                        cx.stop_propagation();
1350                    });
1351                }
1352            });
1353
1354            window.on_mouse_event({
1355                let state = self.state.clone();
1356
1357                move |event: &ScrollWheelEvent, phase, window, cx| {
1358                    state.update(cx, |state, cx| {
1359                        if phase.capture() && state.parent_hovered(window) {
1360                            state.update_hovered_thumb(&event.position, window, cx)
1361                        }
1362                    });
1363                }
1364            });
1365
1366            window.on_mouse_event({
1367                let state = self.state.clone();
1368
1369                move |event: &MouseMoveEvent, phase, window, cx| {
1370                    if phase != capture_phase {
1371                        return;
1372                    }
1373
1374                    match state.read(cx).thumb_state {
1375                        ThumbState::Dragging(axis, drag_state) if event.dragging() => {
1376                            if let Some(scrollbar_layout) = state.read(cx).thumb_for_axis(axis) {
1377                                let scroll_handle = state.read(cx).scroll_handle();
1378                                let drag_offset = scrollbar_layout.compute_click_offset(
1379                                    event.position,
1380                                    scroll_handle.max_offset(),
1381                                    ScrollbarMouseEvent::ThumbDrag(drag_state),
1382                                );
1383                                let new_offset =
1384                                    scroll_handle.offset().apply_along(axis, |_| drag_offset);
1385
1386                                state.update(cx, |state, cx| state.set_offset(new_offset, cx));
1387                                cx.stop_propagation();
1388                            }
1389                        }
1390                        _ => state.update(cx, |state, cx| {
1391                            match state.update_parent_hovered(window) {
1392                                hover @ ParentHoverEvent::Entered
1393                                | hover @ ParentHoverEvent::Within
1394                                    if event.pressed_button.is_none() =>
1395                                {
1396                                    if matches!(hover, ParentHoverEvent::Entered) {
1397                                        state.show_scrollbars(window, cx);
1398                                    }
1399                                    state.update_hovered_thumb(&event.position, window, cx);
1400                                    if state.thumb_state != ThumbState::Inactive {
1401                                        cx.stop_propagation();
1402                                    }
1403                                }
1404                                ParentHoverEvent::Exited => {
1405                                    state.set_thumb_state(ThumbState::Inactive, window, cx);
1406                                }
1407                                _ => {}
1408                            }
1409                        }),
1410                    }
1411                }
1412            });
1413
1414            window.on_mouse_event({
1415                let state = self.state.clone();
1416                move |event: &MouseUpEvent, phase, window, cx| {
1417                    if phase != capture_phase {
1418                        return;
1419                    }
1420
1421                    state.update(cx, |state, cx| {
1422                        if state.is_dragging() {
1423                            state.scroll_handle().drag_ended();
1424                        }
1425
1426                        if !state.parent_hovered(window) {
1427                            state.schedule_auto_hide(window, cx);
1428                            return;
1429                        }
1430
1431                        state.update_hovered_thumb(&event.position, window, cx);
1432                    });
1433                }
1434            });
1435        })
1436    }
1437}
1438
1439impl<T: ScrollableHandle> IntoElement for ScrollbarElement<T> {
1440    type Element = Self;
1441
1442    fn into_element(self) -> Self::Element {
1443        self
1444    }
1445}