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