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