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