diff --git a/crates/agent_ui/src/threads_archive_view.rs b/crates/agent_ui/src/threads_archive_view.rs index 892c6b0c0e..8f124d7233 100644 --- a/crates/agent_ui/src/threads_archive_view.rs +++ b/crates/agent_ui/src/threads_archive_view.rs @@ -1052,9 +1052,7 @@ impl Render for ThreadsArchiveView { .size_full(), ) .custom_scrollbars( - Scrollbars::new(ScrollAxes::Vertical) - .tracked_scroll_handle(&self.list_state) - .width_sm(), + Scrollbars::new(ScrollAxes::Vertical).tracked_scroll_handle(&self.list_state), window, cx, ) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d980984c72..4b719b1474 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -208,7 +208,7 @@ pub enum SplitSide { } impl EditorElement { - pub(crate) const SCROLLBAR_WIDTH: Pixels = px(15.); + pub(crate) const SCROLLBAR_WIDTH: Pixels = ui::EDITOR_SCROLLBAR_WIDTH; pub fn new(editor: &Entity, style: EditorStyle) -> Self { Self { diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index df6403460d..44a1bc4b38 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -17,12 +17,12 @@ use crate::PinchEvent; use crate::{ - AbsoluteLength, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, - DispatchPhase, Display, Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, - Hitbox, HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, - KeyDownEvent, KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, - MouseButton, MouseClickEvent, MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent, - Overflow, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style, + Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase, + Display, Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox, + HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, + KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton, + MouseClickEvent, MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent, Overflow, + ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, TooltipId, Visibility, Window, WindowControlArea, point, px, size, }; @@ -1200,15 +1200,6 @@ pub trait StatefulInteractiveElement: InteractiveElement { self } - /// Set the space to be reserved for rendering the scrollbar. - /// - /// This will only affect the layout of the element when overflow for this element is set to - /// `Overflow::Scroll`. - fn scrollbar_width(mut self, width: impl Into) -> Self { - self.interactivity().base_style.scrollbar_width = Some(width.into()); - self - } - /// Track the scroll state of this element with the given handle. fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self { self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone()); diff --git a/crates/gpui/src/styled.rs b/crates/gpui/src/styled.rs index e3c79fdb40..e090ba973f 100644 --- a/crates/gpui/src/styled.rs +++ b/crates/gpui/src/styled.rs @@ -61,6 +61,15 @@ pub trait Styled: Sized { self } + /// Set the space to be reserved for rendering the scrollbar. + /// + /// This will only affect the layout of the element when overflow for this element is set to + /// `Overflow::Scroll`. + fn scrollbar_width(mut self, width: impl Into) -> Self { + self.style().scrollbar_width = Some(width.into()); + self + } + /// Sets the whitespace of the element to `normal`. /// [Docs](https://tailwindcss.com/docs/whitespace#normal) fn whitespace_normal(mut self) -> Self { diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index cff9da17ac..f2f90db1e6 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -1102,8 +1102,7 @@ impl Render for Picker { .children(self.delegate.render_header(window, cx)) .child(self.render_element_container(cx)) .when(self.show_scrollbar, |this| { - let base_scrollbar_config = - Scrollbars::new(ScrollAxes::Vertical).width_sm(); + let base_scrollbar_config = Scrollbars::new(ScrollAxes::Vertical); this.map(|this| match &self.element_container { ElementContainer::List(state) => this.custom_scrollbars( diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index ebf2518d68..aa07b8c3ee 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -5012,8 +5012,7 @@ impl Render for Sidebar { .when_some(sticky_header, |this, header| this.child(header)) .custom_scrollbars( Scrollbars::new(ScrollAxes::Vertical) - .tracked_scroll_handle(&self.list_state) - .width_sm(), + .tracked_scroll_handle(&self.list_state), window, cx, ), diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index aba0040f48..36bac03312 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1279,12 +1279,13 @@ 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) - .with_track_along( + .with_stable_track_along( ScrollAxes::Vertical, - cx.theme().colors().editor_background, + colors.editor_background, ) .tracked_scroll_handle(&self.scroll_handle), window, diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index 77ceae9a34..3e2c8c8b9a 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -27,7 +27,9 @@ const SCROLLBAR_HIDE_DELAY_INTERVAL: Duration = Duration::from_secs(1); const SCROLLBAR_HIDE_DURATION: Duration = Duration::from_millis(400); const SCROLLBAR_SHOW_DURATION: Duration = Duration::from_millis(50); +pub const EDITOR_SCROLLBAR_WIDTH: Pixels = ScrollbarStyle::Editor.to_pixels(); const SCROLLBAR_PADDING: Pixels = px(4.); +const BORDER_WIDTH: Pixels = px(1.); pub mod scrollbars { use gpui::{App, Global}; @@ -79,6 +81,7 @@ where { let element_id = config.id.take().unwrap_or_else(|| caller_location.into()); let track_color = config.track_color; + let has_border = config.border; let state = window.use_keyed_state(element_id, cx, |_, cx| { let parent_id = cx.entity_id(); @@ -86,9 +89,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, has_border) + }) }); state } @@ -318,6 +321,7 @@ enum ReservedSpace { None, Thumb, Track, + StableTrack, } impl ReservedSpace { @@ -326,24 +330,14 @@ impl ReservedSpace { } fn needs_scroll_track(&self) -> bool { - *self == ReservedSpace::Track + matches!(self, Self::Track | Self::StableTrack) } -} -#[derive(Debug, Default, Clone, Copy)] -enum ScrollbarWidth { - #[default] - Normal, - Small, - XSmall, -} - -impl ScrollbarWidth { - fn to_pixels(&self) -> Pixels { + fn needs_space_reserved(&self, max_offset: Pixels) -> bool { match self { - ScrollbarWidth::Normal => px(8.), - ScrollbarWidth::Small => px(6.), - ScrollbarWidth::XSmall => px(4.), + Self::StableTrack => true, + Self::Track => !max_offset.is_zero(), + _ => false, } } } @@ -354,6 +348,22 @@ enum Handle { Untracked(fn() -> T), } +#[derive(Clone, Copy, Default, PartialEq)] +pub enum ScrollbarStyle { + #[default] + Regular, + Editor, +} + +impl ScrollbarStyle { + pub const fn to_pixels(&self) -> Pixels { + match self { + ScrollbarStyle::Regular => px(6.), + ScrollbarStyle::Editor => px(15.), + } + } +} + #[derive(Clone)] pub struct Scrollbars { id: Option, @@ -361,8 +371,9 @@ pub struct Scrollbars { tracked_entity: Option>, scrollable_handle: Handle, visibility: Point, + style: Option, track_color: Option, - scrollbar_width: ScrollbarWidth, + border: bool, } impl Scrollbars { @@ -387,8 +398,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, - scrollbar_width: ScrollbarWidth::Normal, + border: false, } } } @@ -425,10 +437,11 @@ impl Scrollbars { let Self { id, tracked_entity: tracked_entity_id, - scrollbar_width, visibility, get_visibility, track_color, + border, + style, .. } = self; @@ -437,9 +450,10 @@ impl Scrollbars { id, tracked_entity: tracked_entity_id, visibility, - scrollbar_width, track_color, + border, get_visibility, + style, } } @@ -448,19 +462,21 @@ 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 width_sm(mut self) -> Self { - self.scrollbar_width = ScrollbarWidth::Small; - self - } - - pub fn width_xs(mut self) -> Self { - self.scrollbar_width = ScrollbarWidth::XSmall; + pub fn with_stable_track_along(mut self, along: ScrollAxes, background_color: Hsla) -> Self { + self.visibility = along.apply_to(self.visibility, ReservedSpace::StableTrack); + self.track_color = Some(background_color); + self.border = true; self } } @@ -469,10 +485,20 @@ impl Scrollbars { enum VisibilityState { Visible, Animating { showing: bool, delta: f32 }, + ThumbHidden, Hidden, Disabled, } +enum AnimationState { + InProgress { + current_delta: f32, + animation_duration: Duration, + showing: bool, + }, + Stale, +} + const DELTA_MAX: f32 = 1.0; impl VisibilityState { @@ -499,7 +525,10 @@ impl VisibilityState { } fn is_visible(&self) -> bool { - matches!(self, Self::Visible | Self::Animating { .. }) + matches!( + self, + Self::Visible | Self::Animating { .. } | Self::ThumbHidden + ) } #[inline] @@ -507,26 +536,29 @@ impl VisibilityState { *self == VisibilityState::Disabled } - fn animation_progress(&self) -> Option<(f32, Duration, bool)> { + fn animation_state(&self) -> Option { match self { - Self::Animating { showing, delta } => Some(( - *delta, - if *showing { + Self::ThumbHidden => Some(AnimationState::Stale), + Self::Animating { showing, delta } => Some(AnimationState::InProgress { + current_delta: *delta, + animation_duration: if *showing { SCROLLBAR_SHOW_DURATION } else { SCROLLBAR_HIDE_DURATION }, - *showing, - )), + showing: *showing, + }), _ => None, } } - fn set_delta(&mut self, new_delta: f32) { + fn set_delta(&mut self, new_delta: f32, keep_track_visible: bool) { match self { - Self::Animating { showing, .. } if new_delta >= DELTA_MAX => { + Self::Animating { showing, delta } if new_delta >= DELTA_MAX => { if *showing { *self = Self::Visible; + } else if keep_track_visible { + *self = Self::ThumbHidden; } else { *self = Self::Hidden; } @@ -538,7 +570,7 @@ impl VisibilityState { fn toggle_visible(&self, show_behavior: ShowBehavior) -> Self { match self { - Self::Hidden => { + Self::Hidden | Self::ThumbHidden => { if show_behavior == ShowBehavior::Autohide { Self::for_show() } else { @@ -564,6 +596,12 @@ enum ParentHoverEvent { Outside, } +#[derive(Clone)] +struct TrackColors { + background: Hsla, + has_border: bool, +} + pub fn on_new_scrollbars(cx: &mut App) { cx.observe_new::(|_, window, cx| { if let Some(window) = window { @@ -584,12 +622,12 @@ struct ScrollbarState { notify_id: Option, manually_added: bool, scroll_handle: T, - width: ScrollbarWidth, 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>, @@ -608,11 +646,14 @@ impl ScrollbarState { notify_id: config.tracked_entity.map(|id| id.unwrap_or(parent_id)), manually_added, scroll_handle, - width: config.scrollbar_width, visibility: config.visibility, - track_color: config.track_color, + track_color: config.track_color.map(|color| TrackColors { + background: color, + has_border: config.border, + }), 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, @@ -690,18 +731,15 @@ impl ScrollbarState { fn space_to_reserve_for(&self, axis: ScrollbarAxis) -> Option { (self.show_state.is_disabled().not() - && self.visibility.along(axis).needs_scroll_track() && self - .scroll_handle() - .max_offset() + .visibility .along(axis) - .is_zero() - .not()) + .needs_space_reserved(self.scroll_handle().max_offset().along(axis))) .then(|| self.space_to_reserve()) } fn space_to_reserve(&self) -> Pixels { - self.width.to_pixels() + 2 * SCROLLBAR_PADDING + self.style.to_pixels() + 2 * SCROLLBAR_PADDING } fn handle_to_track(&self) -> Option<&Handle> { @@ -783,8 +821,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, has_border: bool) { + self.track_color = track_color.map(|color| TrackColors { + background: color, + has_border, + }); } fn parent_hovered(&self, window: &Window) -> bool { @@ -975,7 +1016,7 @@ struct ScrollbarLayout { track_bounds: Bounds, cursor_hitbox: Hitbox, reserved_space: ReservedSpace, - track_background: Option<(Bounds, Hsla)>, + track_config: Option<(Bounds, TrackColors)>, axis: ScrollbarAxis, } @@ -1100,93 +1141,110 @@ impl Element for ScrollbarElement { window: &mut Window, cx: &mut App, ) -> Self::PrepaintState { - let prepaint_state = self - .state - .read(cx) - .disabled() - .not() - .then(|| ScrollbarPrepaintState { - thumbs: { - 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 prepaint_state = + self.state + .read(cx) + .disabled() + .not() + .then(|| ScrollbarPrepaintState { + thumbs: { + let state = self.state.read(cx); + let thumb_ranges = state.thumb_ranges().collect::>(); + let width = state.style.to_pixels(); + let track_color = state.track_color.as_ref(); - let additional_padding = if thumb_ranges.len() == 2 { - width - } else { - Pixels::ZERO - }; + let additional_padding = if thumb_ranges.len() == 2 { + width + } else { + Pixels::ZERO + }; - thumb_ranges - .into_iter() - .map(|(axis, thumb_range, reserved_space)| { - let track_anchor = match axis { - ScrollbarAxis::Horizontal => Anchor::BottomLeft, - ScrollbarAxis::Vertical => Anchor::TopRight, - }; - let Bounds { origin, size } = Bounds::from_anchor_and_size( - track_anchor, - bounds - .corner(track_anchor) - .apply_along(axis.invert(), |corner| { - corner - SCROLLBAR_PADDING + thumb_ranges + .into_iter() + .map(|(axis, thumb_range, reserved_space)| { + let track_anchor = match axis { + ScrollbarAxis::Horizontal => Anchor::BottomLeft, + ScrollbarAxis::Vertical => Anchor::TopRight, + }; + + let scroll_track_bounds = Bounds::from_anchor_and_size( + track_anchor, + self.origin + bounds.corner(track_anchor), + bounds.size.apply_along(axis.invert(), |_| { + width + + match state.style { + ScrollbarStyle::Regular => 2 * SCROLLBAR_PADDING, + ScrollbarStyle::Editor => Pixels::ZERO, + } }), - bounds.size.apply_along(axis.invert(), |_| width), - ); - 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() - }, - }); + let has_border = + track_color.is_some_and(|track_colors| track_colors.has_border); - let available_space = - padded_bounds.size.along(axis) - additional_padding; + // Rounded style needs a bit of padding, whereas for editor scrollbars, + // we want the full length of the track + let thumb_container_bounds = match state.style { + ScrollbarStyle::Regular => { + scroll_track_bounds.dilate(-SCROLLBAR_PADDING) + } + ScrollbarStyle::Editor if has_border => scroll_track_bounds + .extend(match axis { + ScrollbarAxis::Horizontal => Edges { + top: -BORDER_WIDTH, + ..Default::default() + }, - let thumb_offset = thumb_range.start * available_space; - let thumb_end = thumb_range.end * available_space; - let thumb_bounds = Bounds::new( - padded_bounds - .origin - .apply_along(axis, |origin| origin + thumb_offset), - padded_bounds - .size - .apply_along(axis, |_| thumb_end - thumb_offset), - ); + ScrollbarAxis::Vertical => Edges { + left: -BORDER_WIDTH, + ..Default::default() + }, + }), + ScrollbarStyle::Editor => scroll_track_bounds, + }; - let needs_scroll_track = reserved_space.needs_scroll_track(); + let available_space = + thumb_container_bounds.size.along(axis) - additional_padding; - ScrollbarLayout { - thumb_bounds, - track_bounds: padded_bounds, - axis, - cursor_hitbox: window.insert_hitbox( - if needs_scroll_track { - padded_bounds - } else { - thumb_bounds - }, - HitboxBehavior::BlockMouseExceptScroll, - ), - track_background: track_color - .filter(|_| needs_scroll_track) - .map(|color| (padded_bounds.dilate(SCROLLBAR_PADDING), color)), - reserved_space, - } - }) - .collect() - }, - parent_bounds_hitbox: window.insert_hitbox(bounds, HitboxBehavior::Normal), - }); + let thumb_offset = thumb_range.start * available_space; + let thumb_end = thumb_range.end * available_space; + let thumb_bounds = Bounds::new( + thumb_container_bounds + .origin + .apply_along(axis, |origin| origin + thumb_offset), + thumb_container_bounds + .size + .apply_along(axis, |_| thumb_end - thumb_offset), + ); + + let needs_scroll_track = reserved_space.needs_scroll_track(); + + ScrollbarLayout { + thumb_bounds, + track_bounds: thumb_container_bounds, + axis, + cursor_hitbox: window.insert_hitbox( + if needs_scroll_track { + if has_border && state.style == ScrollbarStyle::Editor { + scroll_track_bounds + } else { + thumb_container_bounds + } + } else { + thumb_bounds + }, + HitboxBehavior::BlockMouseExceptScroll, + ), + track_config: track_color + .filter(|_| needs_scroll_track) + .map(|color| (scroll_track_bounds, color.clone())), + reserved_space, + } + }) + .collect() + }, + parent_bounds_hitbox: window.insert_hitbox(bounds, HitboxBehavior::Normal), + }); if prepaint_state .as_ref() .is_some_and(|state| Some(state) != self.state.read(cx).last_prepaint_state.as_ref()) @@ -1196,27 +1254,41 @@ impl Element for ScrollbarElement { } prepaint_state.map(|state| { - let autohide_delta = self.state.read(cx).show_state.animation_progress().map( - |(delta, delta_duration, should_invert)| { - window.with_element_state(id.unwrap(), |state, window| { + let autohide_delta = self + .state + .read(cx) + .show_state + .animation_state() + .map(|state| match state { + AnimationState::InProgress { + current_delta, + animation_duration: delta_duration, + showing: should_invert, + } => window.with_element_state(id.unwrap(), |state, window| { let state = state.unwrap_or_else(|| Instant::now()); let current = Instant::now(); - let new_delta = DELTA_MAX - .min(delta + (current - state).div_duration_f32(delta_duration)); - self.state - .update(cx, |state, _| state.show_state.set_delta(new_delta)); + let new_delta = DELTA_MAX.min( + current_delta + (current - state).div_duration_f32(delta_duration), + ); + self.state.update(cx, |state, _| { + let has_border = state + .track_color + .as_ref() + .is_some_and(|track_colors| track_colors.has_border); + state.show_state.set_delta(new_delta, has_border) + }); window.request_animation_frame(); let delta = if should_invert { - DELTA_MAX - delta + DELTA_MAX - current_delta } else { - delta + current_delta }; (ease_in_out(delta), current) - }) - }, - ); + }), + AnimationState::Stale => 1.0, + }); (state, autohide_delta) }) @@ -1243,7 +1315,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; @@ -1256,7 +1330,7 @@ impl Element for ScrollbarElement { cursor_hitbox, axis, reserved_space, - track_background, + track_config, .. } in &prepaint_state.thumbs { @@ -1271,12 +1345,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)) }; @@ -1286,25 +1362,52 @@ impl Element for ScrollbarElement { thumb_color.fade_out(fade); } - if let Some((track_bounds, color)) = track_background { - let mut color = *color; - if let Some(fade) = autohide_fade { - color.fade_out(fade); + if let Some((track_bounds, colors)) = track_config { + let has_border = colors.has_border; + + let mut track_color = colors.background; + if let Some(fade) = autohide_fade + && !has_border + { + track_color.fade_out(fade); } + let border_edges = has_border + .then(|| match axis { + ScrollbarAxis::Horizontal => Edges { + top: BORDER_WIDTH, + ..Default::default() + }, + ScrollbarAxis::Vertical => Edges { + left: BORDER_WIDTH, + ..Default::default() + }, + }) + .unwrap_or_default(); + + let border_color = if has_border { + cx.theme().colors().border_variant.opacity(0.6) + } else { + Hsla::transparent_black() + }; + window.paint_quad(quad( *track_bounds, Corners::default(), - color, - Edges::default(), - Hsla::transparent_black(), - BorderStyle::default(), + track_color, + border_edges, + border_color, + BorderStyle::Solid, )); } window.paint_quad(quad( *thumb_bounds, - Corners::all(Pixels::MAX).clamp_radii_for_quad_size(thumb_bounds.size), + match style { + ScrollbarStyle::Regular => Corners::all(Pixels::MAX) + .clamp_radii_for_quad_size(thumb_bounds.size), + ScrollbarStyle::Editor => Corners::default(), + }, thumb_color, Edges::default(), Hsla::transparent_black(),