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