scrollbar.rs

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