Thread view scrollbar (#35655)

Agus Zubiaga and David Kleingeld created

This also adds a convenient `Scrollbar:auto_hide` function so that we
don't have to handle that at the callsite.

Release Notes:

- N/A

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>

Change summary

crates/agent_ui/src/acp/thread_view.rs                    |  50 +++
crates/agent_ui/src/active_thread.rs                      | 109 ++------
crates/debugger_ui/src/session/running/breakpoint_list.rs |  97 ++-----
crates/debugger_ui/src/session/running/memory_view.rs     | 103 ++-----
crates/ui/src/components/scrollbar.rs                     | 114 +++++++-
5 files changed, 239 insertions(+), 234 deletions(-)

Detailed changes

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -21,10 +21,10 @@ use editor::{
 use file_icons::FileIcons;
 use gpui::{
     Action, Animation, AnimationExt, App, BorderStyle, EdgesRefinement, Empty, Entity, EntityId,
-    FocusHandle, Focusable, Hsla, Length, ListOffset, ListState, PlatformDisplay, SharedString,
-    StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, Transformation,
-    UnderlineStyle, WeakEntity, Window, WindowHandle, div, linear_color_stop, linear_gradient,
-    list, percentage, point, prelude::*, pulsating_between,
+    FocusHandle, Focusable, Hsla, Length, ListOffset, ListState, MouseButton, PlatformDisplay,
+    SharedString, Stateful, StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement,
+    Transformation, UnderlineStyle, WeakEntity, Window, WindowHandle, div, linear_color_stop,
+    linear_gradient, list, percentage, point, prelude::*, pulsating_between,
 };
 use language::language_settings::SoftWrap;
 use language::{Buffer, Language};
@@ -34,7 +34,9 @@ use project::Project;
 use settings::Settings as _;
 use text::{Anchor, BufferSnapshot};
 use theme::ThemeSettings;
-use ui::{Disclosure, Divider, DividerColor, KeyBinding, Tooltip, prelude::*};
+use ui::{
+    Disclosure, Divider, DividerColor, KeyBinding, Scrollbar, ScrollbarState, Tooltip, prelude::*,
+};
 use util::ResultExt;
 use workspace::{CollaboratorId, Workspace};
 use zed_actions::agent::{Chat, NextHistoryMessage, PreviousHistoryMessage};
@@ -69,6 +71,7 @@ pub struct AcpThreadView {
     notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
     last_error: Option<Entity<Markdown>>,
     list_state: ListState,
+    scrollbar_state: ScrollbarState,
     auth_task: Option<Task<()>>,
     expanded_tool_calls: HashSet<acp::ToolCallId>,
     expanded_thinking_blocks: HashSet<(usize, usize)>,
@@ -187,7 +190,8 @@ impl AcpThreadView {
             notifications: Vec::new(),
             notification_subscriptions: HashMap::default(),
             diff_editors: Default::default(),
-            list_state: list_state,
+            list_state: list_state.clone(),
+            scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
             last_error: None,
             auth_task: None,
             expanded_tool_calls: HashSet::default(),
@@ -2479,6 +2483,39 @@ impl AcpThreadView {
             .child(open_as_markdown)
             .child(scroll_to_top)
     }
+
+    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
+        div()
+            .id("acp-thread-scrollbar")
+            .occlude()
+            .on_mouse_move(cx.listener(|_, _, _, cx| {
+                cx.notify();
+                cx.stop_propagation()
+            }))
+            .on_hover(|_, _, cx| {
+                cx.stop_propagation();
+            })
+            .on_any_mouse_down(|_, _, cx| {
+                cx.stop_propagation();
+            })
+            .on_mouse_up(
+                MouseButton::Left,
+                cx.listener(|_, _, _, cx| {
+                    cx.stop_propagation();
+                }),
+            )
+            .on_scroll_wheel(cx.listener(|_, _, _, cx| {
+                cx.notify();
+            }))
+            .h_full()
+            .absolute()
+            .right_1()
+            .top_1()
+            .bottom_0()
+            .w(px(12.))
+            .cursor_default()
+            .children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
+    }
 }
 
 impl Focusable for AcpThreadView {
@@ -2553,6 +2590,7 @@ impl Render for AcpThreadView {
                                 .flex_grow()
                                 .into_any(),
                             )
+                            .child(self.render_vertical_scrollbar(cx))
                             .children(match thread_clone.read(cx).status() {
                                 ThreadStatus::Idle | ThreadStatus::WaitingForToolConfirmation => {
                                     None

crates/agent_ui/src/active_thread.rs 🔗

@@ -69,8 +69,6 @@ pub struct ActiveThread {
     messages: Vec<MessageId>,
     list_state: ListState,
     scrollbar_state: ScrollbarState,
-    show_scrollbar: bool,
-    hide_scrollbar_task: Option<Task<()>>,
     rendered_messages_by_id: HashMap<MessageId, RenderedMessage>,
     rendered_tool_uses: HashMap<LanguageModelToolUseId, RenderedToolUse>,
     editing_message: Option<(MessageId, EditingMessageState)>,
@@ -805,9 +803,7 @@ impl ActiveThread {
             expanded_thinking_segments: HashMap::default(),
             expanded_code_blocks: HashMap::default(),
             list_state: list_state.clone(),
-            scrollbar_state: ScrollbarState::new(list_state),
-            show_scrollbar: false,
-            hide_scrollbar_task: None,
+            scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
             editing_message: None,
             last_error: None,
             copied_code_block_ids: HashSet::default(),
@@ -3502,60 +3498,37 @@ impl ActiveThread {
         }
     }
 
-    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
-        if !self.show_scrollbar && !self.scrollbar_state.is_dragging() {
-            return None;
-        }
-
-        Some(
-            div()
-                .occlude()
-                .id("active-thread-scrollbar")
-                .on_mouse_move(cx.listener(|_, _, _, cx| {
-                    cx.notify();
-                    cx.stop_propagation()
-                }))
-                .on_hover(|_, _, cx| {
-                    cx.stop_propagation();
-                })
-                .on_any_mouse_down(|_, _, cx| {
+    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
+        div()
+            .occlude()
+            .id("active-thread-scrollbar")
+            .on_mouse_move(cx.listener(|_, _, _, cx| {
+                cx.notify();
+                cx.stop_propagation()
+            }))
+            .on_hover(|_, _, cx| {
+                cx.stop_propagation();
+            })
+            .on_any_mouse_down(|_, _, cx| {
+                cx.stop_propagation();
+            })
+            .on_mouse_up(
+                MouseButton::Left,
+                cx.listener(|_, _, _, cx| {
                     cx.stop_propagation();
-                })
-                .on_mouse_up(
-                    MouseButton::Left,
-                    cx.listener(|_, _, _, cx| {
-                        cx.stop_propagation();
-                    }),
-                )
-                .on_scroll_wheel(cx.listener(|_, _, _, cx| {
-                    cx.notify();
-                }))
-                .h_full()
-                .absolute()
-                .right_1()
-                .top_1()
-                .bottom_0()
-                .w(px(12.))
-                .cursor_default()
-                .children(Scrollbar::vertical(self.scrollbar_state.clone())),
-        )
-    }
-
-    fn hide_scrollbar_later(&mut self, cx: &mut Context<Self>) {
-        const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
-        self.hide_scrollbar_task = Some(cx.spawn(async move |thread, cx| {
-            cx.background_executor()
-                .timer(SCROLLBAR_SHOW_INTERVAL)
-                .await;
-            thread
-                .update(cx, |thread, cx| {
-                    if !thread.scrollbar_state.is_dragging() {
-                        thread.show_scrollbar = false;
-                        cx.notify();
-                    }
-                })
-                .log_err();
-        }))
+                }),
+            )
+            .on_scroll_wheel(cx.listener(|_, _, _, cx| {
+                cx.notify();
+            }))
+            .h_full()
+            .absolute()
+            .right_1()
+            .top_1()
+            .bottom_0()
+            .w(px(12.))
+            .cursor_default()
+            .children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
     }
 
     pub fn is_codeblock_expanded(&self, message_id: MessageId, ix: usize) -> bool {
@@ -3596,26 +3569,8 @@ impl Render for ActiveThread {
             .size_full()
             .relative()
             .bg(cx.theme().colors().panel_background)
-            .on_mouse_move(cx.listener(|this, _, _, cx| {
-                this.show_scrollbar = true;
-                this.hide_scrollbar_later(cx);
-                cx.notify();
-            }))
-            .on_scroll_wheel(cx.listener(|this, _, _, cx| {
-                this.show_scrollbar = true;
-                this.hide_scrollbar_later(cx);
-                cx.notify();
-            }))
-            .on_mouse_up(
-                MouseButton::Left,
-                cx.listener(|this, _, _, cx| {
-                    this.hide_scrollbar_later(cx);
-                }),
-            )
             .child(list(self.list_state.clone(), cx.processor(Self::render_message)).flex_grow())
-            .when_some(self.render_vertical_scrollbar(cx), |this, scrollbar| {
-                this.child(scrollbar)
-            })
+            .child(self.render_vertical_scrollbar(cx))
     }
 }
 

crates/debugger_ui/src/session/running/breakpoint_list.rs 🔗

@@ -29,7 +29,6 @@ use ui::{
     Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Toggleable,
     Tooltip, Window, div, h_flex, px, v_flex,
 };
-use util::ResultExt;
 use workspace::Workspace;
 use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
 
@@ -56,8 +55,6 @@ pub(crate) struct BreakpointList {
     scrollbar_state: ScrollbarState,
     breakpoints: Vec<BreakpointEntry>,
     session: Option<Entity<Session>>,
-    hide_scrollbar_task: Option<Task<()>>,
-    show_scrollbar: bool,
     focus_handle: FocusHandle,
     scroll_handle: UniformListScrollHandle,
     selected_ix: Option<usize>,
@@ -103,8 +100,6 @@ impl BreakpointList {
                 worktree_store,
                 scrollbar_state,
                 breakpoints: Default::default(),
-                hide_scrollbar_task: None,
-                show_scrollbar: false,
                 workspace,
                 session,
                 focus_handle,
@@ -565,21 +560,6 @@ impl BreakpointList {
         Ok(())
     }
 
-    fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
-        self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {
-            cx.background_executor()
-                .timer(SCROLLBAR_SHOW_INTERVAL)
-                .await;
-            panel
-                .update(cx, |panel, cx| {
-                    panel.show_scrollbar = false;
-                    cx.notify();
-                })
-                .log_err();
-        }))
-    }
-
     fn render_list(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
         let selected_ix = self.selected_ix;
         let focus_handle = self.focus_handle.clone();
@@ -614,43 +594,39 @@ impl BreakpointList {
         .flex_grow()
     }
 
-    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
-        if !(self.show_scrollbar || self.scrollbar_state.is_dragging()) {
-            return None;
-        }
-        Some(
-            div()
-                .occlude()
-                .id("breakpoint-list-vertical-scrollbar")
-                .on_mouse_move(cx.listener(|_, _, _, cx| {
-                    cx.notify();
-                    cx.stop_propagation()
-                }))
-                .on_hover(|_, _, cx| {
-                    cx.stop_propagation();
-                })
-                .on_any_mouse_down(|_, _, cx| {
+    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
+        div()
+            .occlude()
+            .id("breakpoint-list-vertical-scrollbar")
+            .on_mouse_move(cx.listener(|_, _, _, cx| {
+                cx.notify();
+                cx.stop_propagation()
+            }))
+            .on_hover(|_, _, cx| {
+                cx.stop_propagation();
+            })
+            .on_any_mouse_down(|_, _, cx| {
+                cx.stop_propagation();
+            })
+            .on_mouse_up(
+                MouseButton::Left,
+                cx.listener(|_, _, _, cx| {
                     cx.stop_propagation();
-                })
-                .on_mouse_up(
-                    MouseButton::Left,
-                    cx.listener(|_, _, _, cx| {
-                        cx.stop_propagation();
-                    }),
-                )
-                .on_scroll_wheel(cx.listener(|_, _, _, cx| {
-                    cx.notify();
-                }))
-                .h_full()
-                .absolute()
-                .right_1()
-                .top_1()
-                .bottom_0()
-                .w(px(12.))
-                .cursor_default()
-                .children(Scrollbar::vertical(self.scrollbar_state.clone())),
-        )
+                }),
+            )
+            .on_scroll_wheel(cx.listener(|_, _, _, cx| {
+                cx.notify();
+            }))
+            .h_full()
+            .absolute()
+            .right_1()
+            .top_1()
+            .bottom_0()
+            .w(px(12.))
+            .cursor_default()
+            .children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
     }
+
     pub(crate) fn render_control_strip(&self) -> AnyElement {
         let selection_kind = self.selection_kind();
         let focus_handle = self.focus_handle.clone();
@@ -819,15 +795,6 @@ impl Render for BreakpointList {
             .id("breakpoint-list")
             .key_context("BreakpointList")
             .track_focus(&self.focus_handle)
-            .on_hover(cx.listener(|this, hovered, window, cx| {
-                if *hovered {
-                    this.show_scrollbar = true;
-                    this.hide_scrollbar_task.take();
-                    cx.notify();
-                } else if !this.focus_handle.contains_focused(window, cx) {
-                    this.hide_scrollbar(window, cx);
-                }
-            }))
             .on_action(cx.listener(Self::select_next))
             .on_action(cx.listener(Self::select_previous))
             .on_action(cx.listener(Self::select_first))
@@ -844,7 +811,7 @@ impl Render for BreakpointList {
                 v_flex()
                     .size_full()
                     .child(self.render_list(cx))
-                    .children(self.render_vertical_scrollbar(cx)),
+                    .child(self.render_vertical_scrollbar(cx)),
             )
             .when_some(self.strip_mode, |this, _| {
                 this.child(Divider::horizontal()).child(

crates/debugger_ui/src/session/running/memory_view.rs 🔗

@@ -23,7 +23,6 @@ use ui::{
     ParentElement, Pixels, PopoverMenuHandle, Render, Scrollbar, ScrollbarState, SharedString,
     StatefulInteractiveElement, Styled, TextSize, Tooltip, Window, div, h_flex, px, v_flex,
 };
-use util::ResultExt;
 use workspace::Workspace;
 
 use crate::{ToggleDataBreakpoint, session::running::stack_frame_list::StackFrameList};
@@ -34,9 +33,7 @@ pub(crate) struct MemoryView {
     workspace: WeakEntity<Workspace>,
     scroll_handle: UniformListScrollHandle,
     scroll_state: ScrollbarState,
-    show_scrollbar: bool,
     stack_frame_list: WeakEntity<StackFrameList>,
-    hide_scrollbar_task: Option<Task<()>>,
     focus_handle: FocusHandle,
     view_state: ViewState,
     query_editor: Entity<Editor>,
@@ -150,8 +147,6 @@ impl MemoryView {
             scroll_state,
             scroll_handle,
             stack_frame_list,
-            show_scrollbar: false,
-            hide_scrollbar_task: None,
             focus_handle: cx.focus_handle(),
             view_state,
             query_editor,
@@ -168,61 +163,42 @@ impl MemoryView {
         .detach();
         this
     }
-    fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
-        self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {
-            cx.background_executor()
-                .timer(SCROLLBAR_SHOW_INTERVAL)
-                .await;
-            panel
-                .update(cx, |panel, cx| {
-                    panel.show_scrollbar = false;
-                    cx.notify();
-                })
-                .log_err();
-        }))
-    }
 
-    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
-        if !(self.show_scrollbar || self.scroll_state.is_dragging()) {
-            return None;
-        }
-        Some(
-            div()
-                .occlude()
-                .id("memory-view-vertical-scrollbar")
-                .on_drag_move(cx.listener(|this, evt, _, cx| {
-                    let did_handle = this.handle_scroll_drag(evt);
-                    cx.notify();
-                    if did_handle {
-                        cx.stop_propagation()
-                    }
-                }))
-                .on_drag(ScrollbarDragging, |_, _, _, cx| cx.new(|_| Empty))
-                .on_hover(|_, _, cx| {
-                    cx.stop_propagation();
-                })
-                .on_any_mouse_down(|_, _, cx| {
+    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
+        div()
+            .occlude()
+            .id("memory-view-vertical-scrollbar")
+            .on_drag_move(cx.listener(|this, evt, _, cx| {
+                let did_handle = this.handle_scroll_drag(evt);
+                cx.notify();
+                if did_handle {
+                    cx.stop_propagation()
+                }
+            }))
+            .on_drag(ScrollbarDragging, |_, _, _, cx| cx.new(|_| Empty))
+            .on_hover(|_, _, cx| {
+                cx.stop_propagation();
+            })
+            .on_any_mouse_down(|_, _, cx| {
+                cx.stop_propagation();
+            })
+            .on_mouse_up(
+                MouseButton::Left,
+                cx.listener(|_, _, _, cx| {
                     cx.stop_propagation();
-                })
-                .on_mouse_up(
-                    MouseButton::Left,
-                    cx.listener(|_, _, _, cx| {
-                        cx.stop_propagation();
-                    }),
-                )
-                .on_scroll_wheel(cx.listener(|_, _, _, cx| {
-                    cx.notify();
-                }))
-                .h_full()
-                .absolute()
-                .right_1()
-                .top_1()
-                .bottom_0()
-                .w(px(12.))
-                .cursor_default()
-                .children(Scrollbar::vertical(self.scroll_state.clone())),
-        )
+                }),
+            )
+            .on_scroll_wheel(cx.listener(|_, _, _, cx| {
+                cx.notify();
+            }))
+            .h_full()
+            .absolute()
+            .right_1()
+            .top_1()
+            .bottom_0()
+            .w(px(12.))
+            .cursor_default()
+            .children(Scrollbar::vertical(self.scroll_state.clone()).map(|s| s.auto_hide(cx)))
     }
 
     fn render_memory(&self, cx: &mut Context<Self>) -> UniformList {
@@ -920,15 +896,6 @@ impl Render for MemoryView {
             .on_action(cx.listener(Self::page_up))
             .size_full()
             .track_focus(&self.focus_handle)
-            .on_hover(cx.listener(|this, hovered, window, cx| {
-                if *hovered {
-                    this.show_scrollbar = true;
-                    this.hide_scrollbar_task.take();
-                    cx.notify();
-                } else if !this.focus_handle.contains_focused(window, cx) {
-                    this.hide_scrollbar(window, cx);
-                }
-            }))
             .child(
                 h_flex()
                     .w_full()
@@ -978,7 +945,7 @@ impl Render for MemoryView {
                         )
                         .with_priority(1)
                     }))
-                    .children(self.render_vertical_scrollbar(cx)),
+                    .child(self.render_vertical_scrollbar(cx)),
             )
     }
 }

crates/ui/src/components/scrollbar.rs 🔗

@@ -1,11 +1,20 @@
-use std::{any::Any, cell::Cell, fmt::Debug, ops::Range, rc::Rc, sync::Arc};
+use std::{
+    any::Any,
+    cell::{Cell, RefCell},
+    fmt::Debug,
+    ops::Range,
+    rc::Rc,
+    sync::Arc,
+    time::Duration,
+};
 
 use crate::{IntoElement, prelude::*, px, relative};
 use gpui::{
     Along, App, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Corners, CursorStyle,
     Edges, Element, ElementId, Entity, EntityId, GlobalElementId, Hitbox, HitboxBehavior, Hsla,
     IsZero, LayoutId, ListState, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
-    Point, ScrollHandle, ScrollWheelEvent, Size, Style, UniformListScrollHandle, Window, quad,
+    Point, ScrollHandle, ScrollWheelEvent, Size, Style, Task, UniformListScrollHandle, Window,
+    quad,
 };
 
 pub struct Scrollbar {
@@ -108,6 +117,25 @@ pub struct ScrollbarState {
     thumb_state: Rc<Cell<ThumbState>>,
     parent_id: Option<EntityId>,
     scroll_handle: Arc<dyn ScrollableHandle>,
+    auto_hide: Rc<RefCell<AutoHide>>,
+}
+
+#[derive(Debug)]
+enum AutoHide {
+    Disabled,
+    Hidden {
+        parent_id: EntityId,
+    },
+    Visible {
+        parent_id: EntityId,
+        _task: Task<()>,
+    },
+}
+
+impl AutoHide {
+    fn is_hidden(&self) -> bool {
+        matches!(self, AutoHide::Hidden { .. })
+    }
 }
 
 impl ScrollbarState {
@@ -116,6 +144,7 @@ impl ScrollbarState {
             thumb_state: Default::default(),
             parent_id: None,
             scroll_handle: Arc::new(scroll),
+            auto_hide: Rc::new(RefCell::new(AutoHide::Disabled)),
         }
     }
 
@@ -174,6 +203,38 @@ impl ScrollbarState {
         let thumb_percentage_end = (start_offset + thumb_size) / viewport_size;
         Some(thumb_percentage_start..thumb_percentage_end)
     }
+
+    fn show_temporarily(&self, parent_id: EntityId, cx: &mut App) {
+        const SHOW_INTERVAL: Duration = Duration::from_secs(1);
+
+        let auto_hide = self.auto_hide.clone();
+        auto_hide.replace(AutoHide::Visible {
+            parent_id,
+            _task: cx.spawn({
+                let this = auto_hide.clone();
+                async move |cx| {
+                    cx.background_executor().timer(SHOW_INTERVAL).await;
+                    this.replace(AutoHide::Hidden { parent_id });
+                    cx.update(|cx| {
+                        cx.notify(parent_id);
+                    })
+                    .ok();
+                }
+            }),
+        });
+    }
+
+    fn unhide(&self, position: &Point<Pixels>, cx: &mut App) {
+        let parent_id = match &*self.auto_hide.borrow() {
+            AutoHide::Disabled => return,
+            AutoHide::Hidden { parent_id } => *parent_id,
+            AutoHide::Visible { parent_id, _task } => *parent_id,
+        };
+
+        if self.scroll_handle().viewport().contains(position) {
+            self.show_temporarily(parent_id, cx);
+        }
+    }
 }
 
 impl Scrollbar {
@@ -189,6 +250,14 @@ impl Scrollbar {
         let thumb = state.thumb_range(kind)?;
         Some(Self { thumb, state, kind })
     }
+
+    /// Automatically hide the scrollbar when idle
+    pub fn auto_hide<V: 'static>(self, cx: &mut Context<V>) -> Self {
+        if matches!(*self.state.auto_hide.borrow(), AutoHide::Disabled) {
+            self.state.show_temporarily(cx.entity_id(), cx);
+        }
+        self
+    }
 }
 
 impl Element for Scrollbar {
@@ -284,16 +353,18 @@ impl Element for Scrollbar {
                     .apply_along(axis.invert(), |width| width / 1.5),
             );
 
-            let corners = Corners::all(thumb_bounds.size.along(axis.invert()) / 2.0);
-
-            window.paint_quad(quad(
-                thumb_bounds,
-                corners,
-                thumb_background,
-                Edges::default(),
-                Hsla::transparent_black(),
-                BorderStyle::default(),
-            ));
+            if thumb_state.is_dragging() || !self.state.auto_hide.borrow().is_hidden() {
+                let corners = Corners::all(thumb_bounds.size.along(axis.invert()) / 2.0);
+
+                window.paint_quad(quad(
+                    thumb_bounds,
+                    corners,
+                    thumb_background,
+                    Edges::default(),
+                    Hsla::transparent_black(),
+                    BorderStyle::default(),
+                ));
+            }
 
             if thumb_state.is_dragging() {
                 window.set_window_cursor_style(CursorStyle::Arrow);
@@ -361,13 +432,18 @@ impl Element for Scrollbar {
             });
 
             window.on_mouse_event({
+                let state = self.state.clone();
                 let scroll_handle = self.state.scroll_handle().clone();
-                move |event: &ScrollWheelEvent, phase, window, _| {
-                    if phase.bubble() && bounds.contains(&event.position) {
-                        let current_offset = scroll_handle.offset();
-                        scroll_handle.set_offset(
-                            current_offset + event.delta.pixel_delta(window.line_height()),
-                        );
+                move |event: &ScrollWheelEvent, phase, window, cx| {
+                    if phase.bubble() {
+                        state.unhide(&event.position, cx);
+
+                        if bounds.contains(&event.position) {
+                            let current_offset = scroll_handle.offset();
+                            scroll_handle.set_offset(
+                                current_offset + event.delta.pixel_delta(window.line_height()),
+                            );
+                        }
                     }
                 }
             });
@@ -376,6 +452,8 @@ impl Element for Scrollbar {
                 let state = self.state.clone();
                 move |event: &MouseMoveEvent, phase, window, cx| {
                     if phase.bubble() {
+                        state.unhide(&event.position, cx);
+
                         match state.thumb_state.get() {
                             ThumbState::Dragging(drag_state) if event.dragging() => {
                                 let scroll_handle = state.scroll_handle();