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, 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, .. }) = self
 757                .last_prepaint_state
 758                .as_ref()
 759                .and_then(|state| state.thumb_for_position(position))
 760            {
 761                ThumbState::Hover(axis)
 762            } else {
 763                ThumbState::Inactive
 764            },
 765            window,
 766            cx,
 767        );
 768    }
 769
 770    fn set_thumb_state(&mut self, state: ThumbState, window: &mut Window, cx: &mut Context<Self>) {
 771        if self.thumb_state != state {
 772            if state == ThumbState::Inactive {
 773                self.schedule_auto_hide(window, cx);
 774            } else {
 775                self.set_visibility(self.show_state.toggle_visible(self.show_behavior), cx);
 776                self._auto_hide_task.take();
 777            }
 778            self.thumb_state = state;
 779            cx.notify();
 780        }
 781    }
 782
 783    fn update_parent_hovered(&mut self, position: &Point<Pixels>) -> ParentHoverEvent {
 784        let last_parent_hovered = self.mouse_in_parent;
 785        self.mouse_in_parent = self.parent_hovered(position);
 786        let state_changed = self.mouse_in_parent != last_parent_hovered;
 787        match (self.mouse_in_parent, state_changed) {
 788            (true, true) => ParentHoverEvent::Entered,
 789            (true, false) => ParentHoverEvent::Within,
 790            (false, true) => ParentHoverEvent::Exited,
 791            (false, false) => ParentHoverEvent::Outside,
 792        }
 793    }
 794
 795    fn parent_hovered(&self, position: &Point<Pixels>) -> bool {
 796        self.last_prepaint_state
 797            .as_ref()
 798            .is_some_and(|state| state.parent_bounds.contains(position))
 799    }
 800
 801    fn hit_for_position(&self, position: &Point<Pixels>) -> Option<&ScrollbarLayout> {
 802        self.last_prepaint_state
 803            .as_ref()
 804            .and_then(|state| state.hit_for_position(position))
 805    }
 806
 807    fn thumb_for_axis(&self, axis: ScrollbarAxis) -> Option<&ScrollbarLayout> {
 808        self.last_prepaint_state
 809            .as_ref()
 810            .and_then(|state| state.thumbs.iter().find(|thumb| thumb.axis == axis))
 811    }
 812
 813    fn thumb_ranges(
 814        &self,
 815    ) -> impl Iterator<Item = (ScrollbarAxis, Range<f32>, ReservedSpace)> + '_ {
 816        const MINIMUM_THUMB_SIZE: Pixels = px(25.);
 817        let max_offset = self.scroll_handle().max_offset();
 818        let viewport_size = self.scroll_handle().viewport().size;
 819        let current_offset = self.scroll_handle().offset();
 820
 821        [ScrollbarAxis::Horizontal, ScrollbarAxis::Vertical]
 822            .into_iter()
 823            .filter(|&axis| self.visibility.along(axis).is_visible())
 824            .flat_map(move |axis| {
 825                let max_offset = max_offset.along(axis);
 826                let viewport_size = viewport_size.along(axis);
 827                if max_offset.is_zero() || viewport_size.is_zero() {
 828                    return None;
 829                }
 830                let content_size = viewport_size + max_offset;
 831                let visible_percentage = viewport_size / content_size;
 832                let thumb_size = MINIMUM_THUMB_SIZE.max(viewport_size * visible_percentage);
 833                if thumb_size > viewport_size {
 834                    return None;
 835                }
 836                let current_offset = current_offset
 837                    .along(axis)
 838                    .clamp(-max_offset, Pixels::ZERO)
 839                    .abs();
 840                let start_offset = (current_offset / max_offset) * (viewport_size - thumb_size);
 841                let thumb_percentage_start = start_offset / viewport_size;
 842                let thumb_percentage_end = (start_offset + thumb_size) / viewport_size;
 843                Some((
 844                    axis,
 845                    thumb_percentage_start..thumb_percentage_end,
 846                    self.visibility.along(axis),
 847                ))
 848            })
 849    }
 850
 851    fn visible(&self) -> bool {
 852        self.show_state.is_visible()
 853    }
 854
 855    #[inline]
 856    fn disabled(&self) -> bool {
 857        self.show_state.is_disabled()
 858    }
 859
 860    fn notify_parent(&self, cx: &mut App) {
 861        if let Some(entity_id) = self.notify_id {
 862            cx.notify(entity_id);
 863        }
 864    }
 865}
 866
 867impl<T: ScrollableHandle> Render for ScrollbarState<T> {
 868    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 869        ScrollbarElement {
 870            state: cx.entity(),
 871            origin: Default::default(),
 872        }
 873    }
 874}
 875
 876struct ScrollbarElement<T: ScrollableHandle> {
 877    origin: Point<Pixels>,
 878    state: Entity<ScrollbarState<T>>,
 879}
 880
 881#[derive(Default, Debug, PartialEq, Eq)]
 882enum ThumbState {
 883    #[default]
 884    Inactive,
 885    Hover(ScrollbarAxis),
 886    Dragging(ScrollbarAxis, Pixels),
 887}
 888
 889impl ThumbState {
 890    fn is_dragging(&self) -> bool {
 891        matches!(*self, ThumbState::Dragging(..))
 892    }
 893}
 894
 895impl ScrollableHandle for UniformListScrollHandle {
 896    fn max_offset(&self) -> Size<Pixels> {
 897        self.0.borrow().base_handle.max_offset()
 898    }
 899
 900    fn set_offset(&self, point: Point<Pixels>) {
 901        self.0.borrow().base_handle.set_offset(point);
 902    }
 903
 904    fn offset(&self) -> Point<Pixels> {
 905        self.0.borrow().base_handle.offset()
 906    }
 907
 908    fn viewport(&self) -> Bounds<Pixels> {
 909        self.0.borrow().base_handle.bounds()
 910    }
 911}
 912
 913impl ScrollableHandle for ListState {
 914    fn max_offset(&self) -> Size<Pixels> {
 915        self.max_offset_for_scrollbar()
 916    }
 917
 918    fn set_offset(&self, point: Point<Pixels>) {
 919        self.set_offset_from_scrollbar(point);
 920    }
 921
 922    fn offset(&self) -> Point<Pixels> {
 923        self.scroll_px_offset_for_scrollbar()
 924    }
 925
 926    fn drag_started(&self) {
 927        self.scrollbar_drag_started();
 928    }
 929
 930    fn drag_ended(&self) {
 931        self.scrollbar_drag_ended();
 932    }
 933
 934    fn viewport(&self) -> Bounds<Pixels> {
 935        self.viewport_bounds()
 936    }
 937}
 938
 939impl ScrollableHandle for ScrollHandle {
 940    fn max_offset(&self) -> Size<Pixels> {
 941        self.max_offset()
 942    }
 943
 944    fn set_offset(&self, point: Point<Pixels>) {
 945        self.set_offset(point);
 946    }
 947
 948    fn offset(&self) -> Point<Pixels> {
 949        self.offset()
 950    }
 951
 952    fn viewport(&self) -> Bounds<Pixels> {
 953        self.bounds()
 954    }
 955}
 956
 957pub trait ScrollableHandle: 'static + Any + Sized {
 958    fn max_offset(&self) -> Size<Pixels>;
 959    fn set_offset(&self, point: Point<Pixels>);
 960    fn offset(&self) -> Point<Pixels>;
 961    fn viewport(&self) -> Bounds<Pixels>;
 962    fn drag_started(&self) {}
 963    fn drag_ended(&self) {}
 964
 965    fn scrollable_along(&self, axis: ScrollbarAxis) -> bool {
 966        self.max_offset().along(axis) > Pixels::ZERO
 967    }
 968    fn content_size(&self) -> Size<Pixels> {
 969        self.viewport().size + self.max_offset()
 970    }
 971}
 972
 973enum ScrollbarMouseEvent {
 974    TrackClick,
 975    ThumbDrag(Pixels),
 976}
 977
 978struct ScrollbarLayout {
 979    thumb_bounds: Bounds<Pixels>,
 980    track_bounds: Bounds<Pixels>,
 981    cursor_hitbox: Hitbox,
 982    reserved_space: ReservedSpace,
 983    track_background: Option<(Bounds<Pixels>, Hsla)>,
 984    axis: ScrollbarAxis,
 985}
 986
 987impl ScrollbarLayout {
 988    fn compute_click_offset(
 989        &self,
 990        event_position: Point<Pixels>,
 991        max_offset: Size<Pixels>,
 992        event_type: ScrollbarMouseEvent,
 993    ) -> Pixels {
 994        let Self {
 995            track_bounds,
 996            thumb_bounds,
 997            axis,
 998            ..
 999        } = self;
1000        let axis = *axis;
1001
1002        let viewport_size = track_bounds.size.along(axis);
1003        let thumb_size = thumb_bounds.size.along(axis);
1004        let thumb_offset = match event_type {
1005            ScrollbarMouseEvent::TrackClick => thumb_size / 2.,
1006            ScrollbarMouseEvent::ThumbDrag(thumb_offset) => thumb_offset,
1007        };
1008
1009        let thumb_start =
1010            (event_position.along(axis) - track_bounds.origin.along(axis) - thumb_offset)
1011                .clamp(px(0.), viewport_size - thumb_size);
1012
1013        let max_offset = max_offset.along(axis);
1014        let percentage = if viewport_size > thumb_size {
1015            thumb_start / (viewport_size - thumb_size)
1016        } else {
1017            0.
1018        };
1019
1020        -max_offset * percentage
1021    }
1022}
1023
1024impl PartialEq for ScrollbarLayout {
1025    fn eq(&self, other: &Self) -> bool {
1026        self.axis == other.axis && self.thumb_bounds == other.thumb_bounds
1027    }
1028}
1029
1030pub struct ScrollbarPrepaintState {
1031    parent_bounds: Bounds<Pixels>,
1032    thumbs: SmallVec<[ScrollbarLayout; 2]>,
1033}
1034
1035impl ScrollbarPrepaintState {
1036    fn thumb_for_position(&self, position: &Point<Pixels>) -> Option<&ScrollbarLayout> {
1037        self.thumbs
1038            .iter()
1039            .find(|info| info.thumb_bounds.contains(position))
1040    }
1041
1042    fn hit_for_position(&self, position: &Point<Pixels>) -> Option<&ScrollbarLayout> {
1043        self.thumbs.iter().find(|info| {
1044            if info.reserved_space.needs_scroll_track() {
1045                info.track_bounds.contains(position)
1046            } else {
1047                info.thumb_bounds.contains(position)
1048            }
1049        })
1050    }
1051}
1052
1053impl PartialEq for ScrollbarPrepaintState {
1054    fn eq(&self, other: &Self) -> bool {
1055        self.thumbs == other.thumbs
1056    }
1057}
1058
1059impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
1060    type RequestLayoutState = ();
1061    type PrepaintState = Option<(ScrollbarPrepaintState, Option<f32>)>;
1062
1063    fn id(&self) -> Option<ElementId> {
1064        Some(("scrollbar_animation", self.state.entity_id()).into())
1065    }
1066
1067    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1068        None
1069    }
1070
1071    fn request_layout(
1072        &mut self,
1073        _id: Option<&GlobalElementId>,
1074        _inspector_id: Option<&gpui::InspectorElementId>,
1075        window: &mut Window,
1076        cx: &mut App,
1077    ) -> (LayoutId, Self::RequestLayoutState) {
1078        let scrollbar_style = Style {
1079            position: Position::Absolute,
1080            inset: Edges::default(),
1081            size: size(relative(1.), relative(1.)).map(Into::into),
1082            ..Default::default()
1083        };
1084
1085        (window.request_layout(scrollbar_style, None, cx), ())
1086    }
1087
1088    fn prepaint(
1089        &mut self,
1090        id: Option<&GlobalElementId>,
1091        _inspector_id: Option<&gpui::InspectorElementId>,
1092        bounds: Bounds<Pixels>,
1093        _request_layout: &mut Self::RequestLayoutState,
1094        window: &mut Window,
1095        cx: &mut App,
1096    ) -> Self::PrepaintState {
1097        let prepaint_state = self
1098            .state
1099            .read(cx)
1100            .disabled()
1101            .not()
1102            .then(|| ScrollbarPrepaintState {
1103                parent_bounds: bounds,
1104                thumbs: {
1105                    let thumb_ranges = self.state.read(cx).thumb_ranges().collect::<Vec<_>>();
1106                    let width = self.state.read(cx).width.to_pixels();
1107
1108                    let additional_padding = if thumb_ranges.len() == 2 {
1109                        width
1110                    } else {
1111                        Pixels::ZERO
1112                    };
1113
1114                    thumb_ranges
1115                        .into_iter()
1116                        .map(|(axis, thumb_range, reserved_space)| {
1117                            let track_anchor = match axis {
1118                                ScrollbarAxis::Horizontal => Corner::BottomLeft,
1119                                ScrollbarAxis::Vertical => Corner::TopRight,
1120                            };
1121                            let Bounds { origin, size } = Bounds::from_corner_and_size(
1122                                track_anchor,
1123                                bounds
1124                                    .corner(track_anchor)
1125                                    .apply_along(axis.invert(), |corner| {
1126                                        corner - SCROLLBAR_PADDING
1127                                    }),
1128                                bounds.size.apply_along(axis.invert(), |_| width),
1129                            );
1130                            let scroll_track_bounds = Bounds::new(self.origin + origin, size);
1131
1132                            let padded_bounds = scroll_track_bounds.extend(match axis {
1133                                ScrollbarAxis::Horizontal => Edges {
1134                                    right: -SCROLLBAR_PADDING,
1135                                    left: -SCROLLBAR_PADDING,
1136                                    ..Default::default()
1137                                },
1138                                ScrollbarAxis::Vertical => Edges {
1139                                    top: -SCROLLBAR_PADDING,
1140                                    bottom: -SCROLLBAR_PADDING,
1141                                    ..Default::default()
1142                                },
1143                            });
1144
1145                            let available_space =
1146                                padded_bounds.size.along(axis) - additional_padding;
1147
1148                            let thumb_offset = thumb_range.start * available_space;
1149                            let thumb_end = thumb_range.end * available_space;
1150                            let thumb_bounds = Bounds::new(
1151                                padded_bounds
1152                                    .origin
1153                                    .apply_along(axis, |origin| origin + thumb_offset),
1154                                padded_bounds
1155                                    .size
1156                                    .apply_along(axis, |_| thumb_end - thumb_offset),
1157                            );
1158
1159                            ScrollbarLayout {
1160                                thumb_bounds,
1161                                track_bounds: padded_bounds,
1162                                axis,
1163                                cursor_hitbox: window.insert_hitbox(
1164                                    if reserved_space.needs_scroll_track() {
1165                                        padded_bounds
1166                                    } else {
1167                                        thumb_bounds
1168                                    },
1169                                    HitboxBehavior::BlockMouseExceptScroll,
1170                                ),
1171                                track_background: reserved_space
1172                                    .track_color()
1173                                    .map(|color| (padded_bounds.dilate(SCROLLBAR_PADDING), color)),
1174                                reserved_space,
1175                            }
1176                        })
1177                        .collect()
1178                },
1179            });
1180        if prepaint_state
1181            .as_ref()
1182            .is_some_and(|state| Some(state) != self.state.read(cx).last_prepaint_state.as_ref())
1183        {
1184            self.state
1185                .update(cx, |state, cx| state.show_scrollbars(window, cx));
1186        }
1187
1188        prepaint_state.map(|state| {
1189            let autohide_delta = self.state.read(cx).show_state.animation_progress().map(
1190                |(delta, delta_duration, should_invert)| {
1191                    window.with_element_state(id.unwrap(), |state, window| {
1192                        let state = state.unwrap_or_else(|| Instant::now());
1193                        let current = Instant::now();
1194
1195                        let new_delta = DELTA_MAX
1196                            .min(delta + (current - state).div_duration_f32(delta_duration));
1197                        self.state
1198                            .update(cx, |state, _| state.show_state.set_delta(new_delta));
1199
1200                        window.request_animation_frame();
1201                        let delta = if should_invert {
1202                            DELTA_MAX - delta
1203                        } else {
1204                            delta
1205                        };
1206                        (ease_in_out(delta), current)
1207                    })
1208                },
1209            );
1210
1211            (state, autohide_delta)
1212        })
1213    }
1214
1215    fn paint(
1216        &mut self,
1217        _id: Option<&GlobalElementId>,
1218        _inspector_id: Option<&gpui::InspectorElementId>,
1219        Bounds { origin, size }: Bounds<Pixels>,
1220        _request_layout: &mut Self::RequestLayoutState,
1221        prepaint_state: &mut Self::PrepaintState,
1222        window: &mut Window,
1223        cx: &mut App,
1224    ) {
1225        let Some((prepaint_state, autohide_fade)) = prepaint_state.take() else {
1226            return;
1227        };
1228
1229        let bounds = Bounds::new(self.origin + origin, size);
1230        window.with_content_mask(Some(ContentMask { bounds }), |window| {
1231            let colors = cx.theme().colors();
1232
1233            if self.state.read(cx).visible() {
1234                for ScrollbarLayout {
1235                    thumb_bounds,
1236                    cursor_hitbox,
1237                    axis,
1238                    reserved_space,
1239                    track_background,
1240                    ..
1241                } in &prepaint_state.thumbs
1242                {
1243                    const MAXIMUM_OPACITY: f32 = 0.7;
1244                    let thumb_state = &self.state.read(cx).thumb_state;
1245                    let (thumb_base_color, hovered) = match thumb_state {
1246                        ThumbState::Dragging(dragged_axis, _) if dragged_axis == axis => {
1247                            (colors.scrollbar_thumb_active_background, false)
1248                        }
1249                        ThumbState::Hover(hovered_axis) if hovered_axis == axis => {
1250                            (colors.scrollbar_thumb_hover_background, true)
1251                        }
1252                        _ => (colors.scrollbar_thumb_background, false),
1253                    };
1254
1255                    let blending_color = if hovered || reserved_space.needs_scroll_track() {
1256                        track_background
1257                            .map(|(_, background)| background)
1258                            .unwrap_or(colors.surface_background)
1259                    } else {
1260                        let blend_color = colors.surface_background;
1261                        blend_color.min(blend_color.alpha(MAXIMUM_OPACITY))
1262                    };
1263
1264                    let mut thumb_color = blending_color.blend(thumb_base_color);
1265
1266                    if !hovered && let Some(fade) = autohide_fade {
1267                        thumb_color.fade_out(fade);
1268                    }
1269
1270                    if let Some((track_bounds, color)) = track_background {
1271                        window.paint_quad(quad(
1272                            *track_bounds,
1273                            Corners::default(),
1274                            *color,
1275                            Edges::default(),
1276                            Hsla::transparent_black(),
1277                            BorderStyle::default(),
1278                        ));
1279                    }
1280
1281                    window.paint_quad(quad(
1282                        *thumb_bounds,
1283                        Corners::all(Pixels::MAX).clamp_radii_for_quad_size(thumb_bounds.size),
1284                        thumb_color,
1285                        Edges::default(),
1286                        Hsla::transparent_black(),
1287                        BorderStyle::default(),
1288                    ));
1289
1290                    if thumb_state.is_dragging() {
1291                        window.set_window_cursor_style(CursorStyle::Arrow);
1292                    } else {
1293                        window.set_cursor_style(CursorStyle::Arrow, cursor_hitbox);
1294                    }
1295                }
1296            }
1297
1298            self.state.update(cx, |state, _| {
1299                state.last_prepaint_state = Some(prepaint_state)
1300            });
1301
1302            window.on_mouse_event({
1303                let state = self.state.clone();
1304
1305                move |event: &MouseDownEvent, phase, window, cx| {
1306                    state.update(cx, |state, cx| {
1307                        let Some(scrollbar_layout) = (phase.capture()
1308                            && event.button == MouseButton::Left)
1309                            .then(|| state.hit_for_position(&event.position))
1310                            .flatten()
1311                        else {
1312                            return;
1313                        };
1314
1315                        let ScrollbarLayout {
1316                            thumb_bounds, axis, ..
1317                        } = scrollbar_layout;
1318
1319                        if thumb_bounds.contains(&event.position) {
1320                            let offset =
1321                                event.position.along(*axis) - thumb_bounds.origin.along(*axis);
1322                            state.set_dragging(*axis, offset, window, cx);
1323                        } else {
1324                            let scroll_handle = state.scroll_handle();
1325                            let click_offset = scrollbar_layout.compute_click_offset(
1326                                event.position,
1327                                scroll_handle.max_offset(),
1328                                ScrollbarMouseEvent::TrackClick,
1329                            );
1330                            state.set_offset(
1331                                scroll_handle.offset().apply_along(*axis, |_| click_offset),
1332                                cx,
1333                            );
1334                        };
1335
1336                        cx.stop_propagation();
1337                    });
1338                }
1339            });
1340
1341            window.on_mouse_event({
1342                let state = self.state.clone();
1343
1344                move |event: &ScrollWheelEvent, phase, window, cx| {
1345                    if phase.capture() {
1346                        state.update(cx, |state, cx| {
1347                            state.update_hovered_thumb(&event.position, window, cx)
1348                        });
1349                    }
1350                }
1351            });
1352
1353            window.on_mouse_event({
1354                let state = self.state.clone();
1355
1356                move |event: &MouseMoveEvent, phase, window, cx| {
1357                    if !phase.capture() {
1358                        return;
1359                    }
1360
1361                    match state.read(cx).thumb_state {
1362                        ThumbState::Dragging(axis, drag_state) if event.dragging() => {
1363                            if let Some(scrollbar_layout) = state.read(cx).thumb_for_axis(axis) {
1364                                let scroll_handle = state.read(cx).scroll_handle();
1365                                let drag_offset = scrollbar_layout.compute_click_offset(
1366                                    event.position,
1367                                    scroll_handle.max_offset(),
1368                                    ScrollbarMouseEvent::ThumbDrag(drag_state),
1369                                );
1370                                let new_offset =
1371                                    scroll_handle.offset().apply_along(axis, |_| drag_offset);
1372
1373                                state.update(cx, |state, cx| state.set_offset(new_offset, cx));
1374                                cx.stop_propagation();
1375                            }
1376                        }
1377                        _ => state.update(cx, |state, cx| {
1378                            match state.update_parent_hovered(&event.position) {
1379                                hover @ ParentHoverEvent::Entered
1380                                | hover @ ParentHoverEvent::Within
1381                                    if event.pressed_button.is_none() =>
1382                                {
1383                                    if matches!(hover, ParentHoverEvent::Entered) {
1384                                        state.show_scrollbars(window, cx);
1385                                    }
1386                                    state.update_hovered_thumb(&event.position, window, cx);
1387                                    if state.thumb_state != ThumbState::Inactive {
1388                                        cx.stop_propagation();
1389                                    }
1390                                }
1391                                ParentHoverEvent::Exited => {
1392                                    state.set_thumb_state(ThumbState::Inactive, window, cx);
1393                                }
1394                                _ => {}
1395                            }
1396                        }),
1397                    }
1398                }
1399            });
1400
1401            window.on_mouse_event({
1402                let state = self.state.clone();
1403                move |event: &MouseUpEvent, phase, window, cx| {
1404                    if !phase.capture() {
1405                        return;
1406                    }
1407
1408                    state.update(cx, |state, cx| {
1409                        if state.is_dragging() {
1410                            state.scroll_handle().drag_ended();
1411                        }
1412
1413                        if !state.parent_hovered(&event.position) {
1414                            state.schedule_auto_hide(window, cx);
1415                            return;
1416                        }
1417
1418                        state.update_hovered_thumb(&event.position, window, cx);
1419                    });
1420                }
1421            });
1422        })
1423    }
1424}
1425
1426impl<T: ScrollableHandle> IntoElement for ScrollbarElement<T> {
1427    type Element = Self;
1428
1429    fn into_element(self) -> Self::Element {
1430        self
1431    }
1432}