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