diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 652755a3cf7444d121df8b0cfa6b89db51e12777..069e6030c889d4bcac567ee6b9de7526ed9802b4 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/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::() .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, diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index e7c04ff9c7e85ba702f65f6c4347de304412f452..50b8800fdaf471982a50adfc60cf793bf4c728b6 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/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 { Untracked(fn() -> T), } +#[derive(Clone, Copy, Default, PartialEq)] +pub enum ScrollbarStyle { + #[default] + Rounded, + Editor, +} + #[derive(Clone)] pub struct Scrollbars { id: Option, @@ -395,7 +403,9 @@ pub struct Scrollbars { tracked_entity: Option>, scrollable_handle: Handle, visibility: Point, + style: Option, track_color: Option, + border_color: Option, 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 Scrollbars { visibility, get_visibility, track_color, + border_color, + style, .. } = self; @@ -473,7 +487,9 @@ impl Scrollbars { visibility, scrollbar_width, track_color, + border_color, get_visibility, + style, } } @@ -482,15 +498,26 @@ impl Scrollbars { 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, + ) -> 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, +} + /// This is used to ensure notifies within the state do not notify the parent /// unintentionally. struct ScrollbarStateWrapper(Entity>); @@ -618,8 +651,9 @@ struct ScrollbarState { show_behavior: ShowBehavior, get_visibility: fn(&App) -> ShowScrollbar, visibility: Point, - track_color: Option, + track_color: Option, show_state: VisibilityState, + style: ScrollbarStyle, mouse_in_parent: bool, last_prepaint_state: Option, _auto_hide_task: Option>, @@ -648,9 +682,13 @@ impl ScrollbarState { 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 ScrollbarState { } } - fn update_track_color(&mut self, track_color: Option) { - self.track_color = track_color; + fn update_colors(&mut self, track_color: Option, border_color: Option) { + 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, cursor_hitbox: Hitbox, reserved_space: ReservedSpace, - track_background: Option<(Bounds, Hsla)>, + track_config: Option<(Bounds, TrackColors)>, axis: ScrollbarAxis, } @@ -1134,7 +1175,7 @@ impl Element for ScrollbarElement { let state = self.state.read(cx); let thumb_ranges = state.thumb_ranges().collect::>(); 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 Element for ScrollbarElement { 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 Element for ScrollbarElement { 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 Element for ScrollbarElement { 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 Element for ScrollbarElement { cursor_hitbox, axis, reserved_space, - track_background, + track_config, .. } in &prepaint_state.thumbs { @@ -1295,12 +1332,14 @@ impl Element for ScrollbarElement { _ => (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 Element for ScrollbarElement { 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(),