WIP

MrSubidubi created

Change summary

crates/terminal_view/src/terminal_view.rs |   5 
crates/ui/src/components/scrollbar.rs     | 165 +++++++++++++++++-------
2 files changed, 117 insertions(+), 53 deletions(-)

Detailed changes

crates/terminal_view/src/terminal_view.rs 🔗

@@ -1257,12 +1257,15 @@ impl Render for TerminalView {
                         self.mode.clone(),
                     ))
                     .when(self.content_mode(window, cx).is_scrollable(), |div| {
+                        let colors = cx.theme().colors();
                         div.custom_scrollbars(
                             Scrollbars::for_settings::<TerminalScrollbarSettingsWrapper>()
                                 .show_along(ScrollAxes::Vertical)
+                                .style(ui::ScrollbarStyle::Editor)
                                 .with_stable_track_along(
                                     ScrollAxes::Vertical,
-                                    cx.theme().colors().editor_background,
+                                    colors.editor_background,
+                                    Some(colors.border),
                                 )
                                 .tracked_scroll_handle(&self.scroll_handle),
                             window,

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

@@ -102,6 +102,7 @@ where
 {
     let element_id = config.id.take().unwrap_or_else(|| caller_location.into());
     let track_color = config.track_color;
+    let border_color = config.border_color;
 
     let state = window.use_keyed_state(element_id, cx, |window, cx| {
         let parent_id = cx.entity_id();
@@ -111,9 +112,9 @@ where
     });
 
     state.update(cx, |state, cx| {
-        state
-            .0
-            .update(cx, |state, _cx| state.update_track_color(track_color))
+        state.0.update(cx, |state, _cx| {
+            state.update_colors(track_color, border_color)
+        })
     });
     state
 }
@@ -352,7 +353,7 @@ impl ReservedSpace {
     }
 
     fn needs_scroll_track(&self) -> bool {
-        *self == ReservedSpace::Track
+        matches!(self, Self::Track | Self::StableTrack)
     }
 
     fn needs_space_reserved(&self, max_offset: Pixels) -> bool {
@@ -388,6 +389,13 @@ enum Handle<T: ScrollableHandle> {
     Untracked(fn() -> T),
 }
 
+#[derive(Clone, Copy, Default, PartialEq)]
+pub enum ScrollbarStyle {
+    #[default]
+    Rounded,
+    Editor,
+}
+
 #[derive(Clone)]
 pub struct Scrollbars<T: ScrollableHandle = ScrollHandle> {
     id: Option<ElementId>,
@@ -395,7 +403,9 @@ pub struct Scrollbars<T: ScrollableHandle = ScrollHandle> {
     tracked_entity: Option<Option<EntityId>>,
     scrollable_handle: Handle<T>,
     visibility: Point<ReservedSpace>,
+    style: Option<ScrollbarStyle>,
     track_color: Option<Hsla>,
+    border_color: Option<Hsla>,
     scrollbar_width: ScrollbarWidth,
 }
 
@@ -421,7 +431,9 @@ impl Scrollbars {
             scrollable_handle: Handle::Untracked(ScrollHandle::new),
             tracked_entity: None,
             visibility: show_along.apply_to(Default::default(), ReservedSpace::Thumb),
+            style: None,
             track_color: None,
+            border_color: None,
             scrollbar_width: ScrollbarWidth::Normal,
         }
     }
@@ -463,6 +475,8 @@ impl<ScrollHandle: ScrollableHandle> Scrollbars<ScrollHandle> {
             visibility,
             get_visibility,
             track_color,
+            border_color,
+            style,
             ..
         } = self;
 
@@ -473,7 +487,9 @@ impl<ScrollHandle: ScrollableHandle> Scrollbars<ScrollHandle> {
             visibility,
             scrollbar_width,
             track_color,
+            border_color,
             get_visibility,
+            style,
         }
     }
 
@@ -482,15 +498,26 @@ impl<ScrollHandle: ScrollableHandle> Scrollbars<ScrollHandle> {
         self
     }
 
+    pub fn style(mut self, style: ScrollbarStyle) -> Self {
+        self.style = Some(style);
+        self
+    }
+
     pub fn with_track_along(mut self, along: ScrollAxes, background_color: Hsla) -> Self {
         self.visibility = along.apply_to(self.visibility, ReservedSpace::Track);
         self.track_color = Some(background_color);
         self
     }
 
-    pub fn with_stable_track_along(mut self, along: ScrollAxes, background_color: Hsla) -> Self {
+    pub fn with_stable_track_along(
+        mut self,
+        along: ScrollAxes,
+        background_color: Hsla,
+        track_border_color: Option<Hsla>,
+    ) -> Self {
         self.visibility = along.apply_to(self.visibility, ReservedSpace::StableTrack);
         self.track_color = Some(background_color);
+        self.border_color = track_border_color;
         self
     }
 
@@ -604,6 +631,12 @@ enum ParentHoverEvent {
     Outside,
 }
 
+#[derive(Clone)]
+struct TrackColors {
+    background: Hsla,
+    border: Option<Hsla>,
+}
+
 /// This is used to ensure notifies within the state do not notify the parent
 /// unintentionally.
 struct ScrollbarStateWrapper<T: ScrollableHandle>(Entity<ScrollbarState<T>>);
@@ -618,8 +651,9 @@ struct ScrollbarState<T: ScrollableHandle = ScrollHandle> {
     show_behavior: ShowBehavior,
     get_visibility: fn(&App) -> ShowScrollbar,
     visibility: Point<ReservedSpace>,
-    track_color: Option<Hsla>,
+    track_color: Option<TrackColors>,
     show_state: VisibilityState,
+    style: ScrollbarStyle,
     mouse_in_parent: bool,
     last_prepaint_state: Option<ScrollbarPrepaintState>,
     _auto_hide_task: Option<Task<()>>,
@@ -648,9 +682,13 @@ impl<T: ScrollableHandle> ScrollbarState<T> {
             scroll_handle,
             width: config.scrollbar_width,
             visibility: config.visibility,
-            track_color: config.track_color,
+            track_color: config.track_color.map(|color| TrackColors {
+                background: color,
+                border: config.border_color,
+            }),
             show_behavior,
             get_visibility: config.get_visibility,
+            style: config.style.unwrap_or_default(),
             show_state: VisibilityState::from_behavior(show_behavior),
             mouse_in_parent: true,
             last_prepaint_state: None,
@@ -818,8 +856,11 @@ impl<T: ScrollableHandle> ScrollbarState<T> {
         }
     }
 
-    fn update_track_color(&mut self, track_color: Option<Hsla>) {
-        self.track_color = track_color;
+    fn update_colors(&mut self, track_color: Option<Hsla>, border_color: Option<Hsla>) {
+        self.track_color = track_color.map(|color| TrackColors {
+            background: color,
+            border: border_color,
+        });
     }
 
     fn parent_hovered(&self, window: &Window) -> bool {
@@ -1010,7 +1051,7 @@ struct ScrollbarLayout {
     track_bounds: Bounds<Pixels>,
     cursor_hitbox: Hitbox,
     reserved_space: ReservedSpace,
-    track_background: Option<(Bounds<Pixels>, Hsla)>,
+    track_config: Option<(Bounds<Pixels>, TrackColors)>,
     axis: ScrollbarAxis,
 }
 
@@ -1134,7 +1175,7 @@ impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
                     let state = self.state.read(cx);
                     let thumb_ranges = state.thumb_ranges().collect::<Vec<_>>();
                     let width = state.width.to_pixels();
-                    let track_color = state.track_color;
+                    let track_color = state.track_color.as_ref();
 
                     let additional_padding = if thumb_ranges.len() == 2 {
                         width
@@ -1149,40 +1190,34 @@ impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
                                 ScrollbarAxis::Horizontal => Corner::BottomLeft,
                                 ScrollbarAxis::Vertical => Corner::TopRight,
                             };
-                            let Bounds { origin, size } = Bounds::from_corner_and_size(
+
+                            let scroll_track_bounds = Bounds::from_corner_and_size(
                                 track_anchor,
+                                self.origin + bounds.corner(track_anchor),
                                 bounds
-                                    .corner(track_anchor)
-                                    .apply_along(axis.invert(), |corner| {
-                                        corner - SCROLLBAR_PADDING
-                                    }),
-                                bounds.size.apply_along(axis.invert(), |_| width),
+                                    .size
+                                    .apply_along(axis.invert(), |_| width + 2 * SCROLLBAR_PADDING),
                             );
-                            let scroll_track_bounds = Bounds::new(self.origin + origin, size);
 
-                            let padded_bounds = scroll_track_bounds.extend(match axis {
-                                ScrollbarAxis::Horizontal => Edges {
-                                    right: -SCROLLBAR_PADDING,
-                                    left: -SCROLLBAR_PADDING,
-                                    ..Default::default()
-                                },
-                                ScrollbarAxis::Vertical => Edges {
-                                    top: -SCROLLBAR_PADDING,
-                                    bottom: -SCROLLBAR_PADDING,
-                                    ..Default::default()
-                                },
-                            });
+                            // Rounded style needs a bit of padding, whereas for editor scrolbars,
+                            // we want the full length of the track
+                            let thumb_container_bounds = match state.style {
+                                ScrollbarStyle::Rounded => {
+                                    scroll_track_bounds.dilate(-SCROLLBAR_PADDING)
+                                }
+                                ScrollbarStyle::Editor => scroll_track_bounds,
+                            };
 
                             let available_space =
-                                padded_bounds.size.along(axis) - additional_padding;
+                                thumb_container_bounds.size.along(axis) - additional_padding;
 
                             let thumb_offset = thumb_range.start * available_space;
                             let thumb_end = thumb_range.end * available_space;
                             let thumb_bounds = Bounds::new(
-                                padded_bounds
+                                thumb_container_bounds
                                     .origin
                                     .apply_along(axis, |origin| origin + thumb_offset),
-                                padded_bounds
+                                thumb_container_bounds
                                     .size
                                     .apply_along(axis, |_| thumb_end - thumb_offset),
                             );
@@ -1191,19 +1226,19 @@ impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
 
                             ScrollbarLayout {
                                 thumb_bounds,
-                                track_bounds: padded_bounds,
+                                track_bounds: thumb_container_bounds,
                                 axis,
                                 cursor_hitbox: window.insert_hitbox(
                                     if needs_scroll_track {
-                                        padded_bounds
+                                        thumb_container_bounds
                                     } else {
                                         thumb_bounds
                                     },
                                     HitboxBehavior::BlockMouseExceptScroll,
                                 ),
-                                track_background: track_color
+                                track_config: track_color
                                     .filter(|_| needs_scroll_track)
-                                    .map(|color| (padded_bounds.dilate(SCROLLBAR_PADDING), color)),
+                                    .map(|color| (scroll_track_bounds, color.clone())),
                                 reserved_space,
                             }
                         })
@@ -1267,7 +1302,9 @@ impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
             let capture_phase;
 
             if self.state.read(cx).visible() {
-                let thumb_state = &self.state.read(cx).thumb_state;
+                let state = self.state.read(cx);
+                let thumb_state = &state.thumb_state;
+                let style = state.style;
 
                 if thumb_state.is_dragging() {
                     capture_phase = DispatchPhase::Capture;
@@ -1280,7 +1317,7 @@ impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
                     cursor_hitbox,
                     axis,
                     reserved_space,
-                    track_background,
+                    track_config,
                     ..
                 } in &prepaint_state.thumbs
                 {
@@ -1295,12 +1332,14 @@ impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
                         _ => (colors.scrollbar_thumb_background, false),
                     };
 
+                    let blend_color = track_config
+                        .as_ref()
+                        .map(|(_, colors)| colors.background)
+                        .unwrap_or(colors.surface_background);
+
                     let blending_color = if hovered || reserved_space.needs_scroll_track() {
-                        track_background
-                            .map(|(_, background)| background)
-                            .unwrap_or(colors.surface_background)
+                        blend_color
                     } else {
-                        let blend_color = colors.surface_background;
                         blend_color.min(blend_color.alpha(MAXIMUM_OPACITY))
                     };
 
@@ -1310,25 +1349,47 @@ impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
                         thumb_color.fade_out(fade);
                     }
 
-                    if let Some((track_bounds, color)) = track_background {
-                        let mut color = *color;
+                    if let Some((track_bounds, colors)) = track_config {
+                        let mut track_color = colors.background;
                         if let Some(fade) = autohide_fade {
-                            color.fade_out(fade);
+                            track_color.fade_out(fade);
                         }
 
+                        let has_border = colors.border.is_some();
+
+                        let (track_bounds, border_edges) = if has_border {
+                            let edges = match axis {
+                                ScrollbarAxis::Horizontal => Edges {
+                                    top: px(1.),
+                                    ..Default::default()
+                                },
+                                ScrollbarAxis::Vertical => Edges {
+                                    left: px(1.),
+                                    ..Default::default()
+                                },
+                            };
+                            (track_bounds.extend(edges), edges)
+                        } else {
+                            (*track_bounds, Edges::default())
+                        };
+
                         window.paint_quad(quad(
-                            *track_bounds,
+                            track_bounds,
                             Corners::default(),
-                            color,
-                            Edges::default(),
-                            Hsla::transparent_black(),
-                            BorderStyle::default(),
+                            track_color,
+                            border_edges,
+                            colors.border.unwrap_or_else(Hsla::transparent_black),
+                            BorderStyle::Solid,
                         ));
                     }
 
                     window.paint_quad(quad(
                         *thumb_bounds,
-                        Corners::all(Pixels::MAX).clamp_radii_for_quad_size(thumb_bounds.size),
+                        match style {
+                            ScrollbarStyle::Rounded => Corners::all(Pixels::MAX)
+                                .clamp_radii_for_quad_size(thumb_bounds.size),
+                            ScrollbarStyle::Editor => Corners::default(),
+                        },
                         thumb_color,
                         Edges::default(),
                         Hsla::transparent_black(),