diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ecddfc24b4..bdb503ab6a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1565,11 +1565,13 @@ impl EditorElement { .map(|vertical_scrollbar| vertical_scrollbar.hitbox.origin) .unwrap_or_else(|| editor_bounds.top_right()); + let thumb_state = self + .editor + .read_with(cx, |editor, _| editor.scroll_manager.minimap_thumb_state()); + let show_thumb = match minimap_settings.thumb { MinimapThumb::Always => true, - MinimapThumb::Hover => self.editor.update(cx, |editor, _| { - editor.scroll_manager.minimap_thumb_visible() - }), + MinimapThumb::Hover => thumb_state.is_some(), }; let minimap_bounds = Bounds::from_corner_and_size( @@ -1610,7 +1612,8 @@ impl EditorElement { scroll_position, minimap_scroll_top, show_thumb, - ); + ) + .with_thumb_state(thumb_state); minimap_editor.update(cx, |editor, cx| { editor.set_scroll_position(point(0., minimap_scroll_top), window, cx) @@ -5703,10 +5706,7 @@ impl EditorElement { .get_hovered_axis(window) .filter(|_| !event.dragging()) { - if layout - .thumb_bounds - .is_some_and(|bounds| bounds.contains(&event.position)) - { + if layout.thumb_hovered(&event.position) { editor .scroll_manager .set_hovered_scroll_thumb_axis(axis, cx); @@ -6115,6 +6115,17 @@ impl EditorElement { window.with_element_namespace("minimap", |window| { layout.minimap.paint(window, cx); if let Some(thumb_bounds) = layout.thumb_layout.thumb_bounds { + let minimap_thumb_color = match layout.thumb_layout.thumb_state { + ScrollbarThumbState::Idle => { + cx.theme().colors().minimap_thumb_background + } + ScrollbarThumbState::Hovered => { + cx.theme().colors().minimap_thumb_hover_background + } + ScrollbarThumbState::Dragging => { + cx.theme().colors().minimap_thumb_active_background + } + }; let minimap_thumb_border = match layout.thumb_border_style { MinimapThumbBorder::Full => Edges::all(ScrollbarLayout::BORDER_WIDTH), MinimapThumbBorder::LeftOnly => Edges { @@ -6140,9 +6151,9 @@ impl EditorElement { window.paint_quad(quad( thumb_bounds, Corners::default(), - cx.theme().colors().scrollbar_thumb_background, + minimap_thumb_color, minimap_thumb_border, - cx.theme().colors().scrollbar_thumb_border, + cx.theme().colors().minimap_thumb_border, BorderStyle::Solid, )); }); @@ -6187,10 +6198,15 @@ impl EditorElement { } cx.stop_propagation(); } else { - editor.scroll_manager.set_is_dragging_minimap(false, cx); - if minimap_hitbox.is_hovered(window) { - editor.scroll_manager.show_minimap_thumb(cx); + editor.scroll_manager.set_is_hovering_minimap_thumb( + !event.dragging() + && layout + .thumb_layout + .thumb_bounds + .is_some_and(|bounds| bounds.contains(&event.position)), + cx, + ); // Stop hover events from propagating to the // underlying editor if the minimap hitbox is hovered @@ -6209,13 +6225,23 @@ impl EditorElement { if self.editor.read(cx).scroll_manager.is_dragging_minimap() { window.on_mouse_event({ let editor = self.editor.clone(); - move |_: &MouseUpEvent, phase, _, cx| { + move |event: &MouseUpEvent, phase, window, cx| { if phase == DispatchPhase::Capture { return; } editor.update(cx, |editor, cx| { - editor.scroll_manager.set_is_dragging_minimap(false, cx); + if minimap_hitbox.is_hovered(window) { + editor.scroll_manager.set_is_hovering_minimap_thumb( + layout + .thumb_layout + .thumb_bounds + .is_some_and(|bounds| bounds.contains(&event.position)), + cx, + ); + } else { + editor.scroll_manager.hide_minimap_thumb(cx); + } cx.stop_propagation(); }); } @@ -6254,7 +6280,7 @@ impl EditorElement { editor.set_scroll_position(scroll_position, window, cx); } - editor.scroll_manager.set_is_dragging_minimap(true, cx); + editor.scroll_manager.set_is_dragging_minimap(cx); cx.stop_propagation(); }); } @@ -8821,10 +8847,6 @@ impl EditorScrollbars { axis != ScrollbarAxis::Horizontal || viewport_size < scroll_range }) .map(|(viewport_size, scroll_range)| { - let thumb_state = scrollbar_state - .and_then(|state| state.thumb_state_for_axis(axis)) - .unwrap_or(ScrollbarThumbState::Idle); - ScrollbarLayout::new( window.insert_hitbox(scrollbar_bounds_for(axis), false), viewport_size, @@ -8833,9 +8855,11 @@ impl EditorScrollbars { content_offset.along(axis), scroll_position.along(axis), show_scrollbars, - thumb_state, axis, ) + .with_thumb_state( + scrollbar_state.and_then(|state| state.thumb_state_for_axis(axis)), + ) }) }; @@ -8885,7 +8909,6 @@ impl ScrollbarLayout { content_offset: Pixels, scroll_position: f32, show_thumb: bool, - thumb_state: ScrollbarThumbState, axis: ScrollbarAxis, ) -> Self { let track_bounds = scrollbar_track_hitbox.bounds; @@ -8902,7 +8925,6 @@ impl ScrollbarLayout { content_offset, scroll_position, show_thumb, - thumb_state, axis, ) } @@ -8944,7 +8966,6 @@ impl ScrollbarLayout { track_top_offset, scroll_position, show_thumb, - ScrollbarThumbState::Idle, ScrollbarAxis::Vertical, ) } @@ -8958,7 +8979,6 @@ impl ScrollbarLayout { content_offset: Pixels, scroll_position: f32, show_thumb: bool, - thumb_state: ScrollbarThumbState, axis: ScrollbarAxis, ) -> Self { let text_units_per_page = viewport_size / glyph_space; @@ -8996,7 +9016,18 @@ impl ScrollbarLayout { visible_range, text_unit_size, thumb_bounds, - thumb_state, + thumb_state: Default::default(), + } + } + + fn with_thumb_state(self, thumb_state: Option) -> Self { + if let Some(thumb_state) = thumb_state { + Self { + thumb_state, + ..self + } + } else { + self } } @@ -9017,6 +9048,11 @@ impl ScrollbarLayout { ) } + fn thumb_hovered(&self, position: &gpui::Point) -> bool { + self.thumb_bounds + .is_some_and(|bounds| bounds.contains(position)) + } + fn marker_quads_for_ranges( &self, row_ranges: impl IntoIterator>, diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index e03ee55e61..a8081b95bd 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -123,8 +123,9 @@ impl OngoingScroll { } } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Default, PartialEq, Eq)] pub enum ScrollbarThumbState { + #[default] Idle, Hovered, Dragging, @@ -157,8 +158,7 @@ pub struct ScrollManager { active_scrollbar: Option, visible_line_count: Option, forbid_vertical_scroll: bool, - dragging_minimap: bool, - show_minimap_thumb: bool, + minimap_thumb_state: Option, } impl ScrollManager { @@ -174,8 +174,7 @@ impl ScrollManager { last_autoscroll: None, visible_line_count: None, forbid_vertical_scroll: false, - dragging_minimap: false, - show_minimap_thumb: false, + minimap_thumb_state: None, } } @@ -345,24 +344,6 @@ impl ScrollManager { self.show_scrollbars } - pub fn show_minimap_thumb(&mut self, cx: &mut Context) { - if !self.show_minimap_thumb { - self.show_minimap_thumb = true; - cx.notify(); - } - } - - pub fn hide_minimap_thumb(&mut self, cx: &mut Context) { - if self.show_minimap_thumb { - self.show_minimap_thumb = false; - cx.notify(); - } - } - - pub fn minimap_thumb_visible(&mut self) -> bool { - self.show_minimap_thumb - } - pub fn autoscroll_request(&self) -> Option { self.autoscroll_request.map(|(autoscroll, _)| autoscroll) } @@ -419,13 +400,43 @@ impl ScrollManager { } } - pub fn is_dragging_minimap(&self) -> bool { - self.dragging_minimap + pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context) { + self.update_minimap_thumb_state( + Some(if hovered { + ScrollbarThumbState::Hovered + } else { + ScrollbarThumbState::Idle + }), + cx, + ); } - pub fn set_is_dragging_minimap(&mut self, dragging: bool, cx: &mut Context) { - self.dragging_minimap = dragging; - cx.notify(); + pub fn set_is_dragging_minimap(&mut self, cx: &mut Context) { + self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx); + } + + pub fn hide_minimap_thumb(&mut self, cx: &mut Context) { + self.update_minimap_thumb_state(None, cx); + } + + pub fn is_dragging_minimap(&self) -> bool { + self.minimap_thumb_state + .is_some_and(|state| state == ScrollbarThumbState::Dragging) + } + + fn update_minimap_thumb_state( + &mut self, + thumb_state: Option, + cx: &mut Context, + ) { + if self.minimap_thumb_state != thumb_state { + self.minimap_thumb_state = thumb_state; + cx.notify(); + } + } + + pub fn minimap_thumb_state(&self) -> Option { + self.minimap_thumb_state } pub fn clamp_scroll_left(&mut self, max: f32) -> bool { diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 1af59c6776..cc4ad01e4d 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -90,6 +90,10 @@ impl ThemeColors { scrollbar_thumb_border: gpui::transparent_black(), scrollbar_track_background: gpui::transparent_black(), scrollbar_track_border: neutral().light().step_5(), + minimap_thumb_background: neutral().light_alpha().step_3().alpha(0.7), + minimap_thumb_hover_background: neutral().light_alpha().step_4().alpha(0.7), + minimap_thumb_active_background: neutral().light_alpha().step_5().alpha(0.7), + minimap_thumb_border: gpui::transparent_black(), editor_foreground: neutral().light().step_12(), editor_background: neutral().light().step_1(), editor_gutter_background: neutral().light().step_1(), @@ -211,6 +215,10 @@ impl ThemeColors { scrollbar_thumb_border: gpui::transparent_black(), scrollbar_track_background: gpui::transparent_black(), scrollbar_track_border: neutral().dark().step_5(), + minimap_thumb_background: neutral().dark_alpha().step_3().alpha(0.7), + minimap_thumb_hover_background: neutral().dark_alpha().step_4().alpha(0.7), + minimap_thumb_active_background: neutral().dark_alpha().step_5().alpha(0.7), + minimap_thumb_border: gpui::transparent_black(), editor_foreground: neutral().dark().step_12(), editor_background: neutral().dark().step_1(), editor_gutter_background: neutral().dark().step_1(), diff --git a/crates/theme/src/fallback_themes.rs b/crates/theme/src/fallback_themes.rs index d907da645b..941a1901bb 100644 --- a/crates/theme/src/fallback_themes.rs +++ b/crates/theme/src/fallback_themes.rs @@ -199,6 +199,10 @@ pub(crate) fn zed_default_dark() -> Theme { scrollbar_thumb_border: hsla(228. / 360., 8. / 100., 25. / 100., 1.), scrollbar_track_background: gpui::transparent_black(), scrollbar_track_border: hsla(228. / 360., 8. / 100., 25. / 100., 1.), + minimap_thumb_background: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 0.7), + minimap_thumb_hover_background: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 0.7), + minimap_thumb_active_background: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 0.7), + minimap_thumb_border: hsla(228. / 360., 8. / 100., 25. / 100., 1.), editor_foreground: hsla(218. / 360., 14. / 100., 71. / 100., 1.), link_text_hover: blue, version_control_added: ADDED_COLOR, diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index 242091d40a..32810c2ae7 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -28,6 +28,18 @@ pub(crate) fn try_parse_color(color: &str) -> Result { Ok(hsla) } +fn ensure_non_opaque(color: Hsla) -> Hsla { + const MAXIMUM_OPACITY: f32 = 0.7; + if color.a <= MAXIMUM_OPACITY { + color + } else { + Hsla { + a: MAXIMUM_OPACITY, + ..color + } + } +} + #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum AppearanceContent { @@ -374,6 +386,22 @@ pub struct ThemeColorsContent { #[serde(rename = "scrollbar.track.border")] pub scrollbar_track_border: Option, + /// The color of the minimap thumb. + #[serde(rename = "minimap.thumb.background")] + pub minimap_thumb_background: Option, + + /// The color of the minimap thumb when hovered over. + #[serde(rename = "minimap.thumb.hover_background")] + pub minimap_thumb_hover_background: Option, + + /// The color of the minimap thumb whilst being actively dragged. + #[serde(rename = "minimap.thumb.active_background")] + pub minimap_thumb_active_background: Option, + + /// The border color of the minimap thumb. + #[serde(rename = "minimap.thumb.border")] + pub minimap_thumb_border: Option, + #[serde(rename = "editor.foreground")] pub editor_foreground: Option, @@ -635,6 +663,19 @@ impl ThemeColorsContent { .as_ref() .and_then(|color| try_parse_color(color).ok()) }); + let scrollbar_thumb_hover_background = self + .scrollbar_thumb_hover_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()); + let scrollbar_thumb_active_background = self + .scrollbar_thumb_active_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_background); + let scrollbar_thumb_border = self + .scrollbar_thumb_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()); ThemeColorsRefinement { border, border_variant: self @@ -819,19 +860,9 @@ impl ThemeColorsContent { .and_then(|color| try_parse_color(color).ok()) .or(border), scrollbar_thumb_background, - scrollbar_thumb_hover_background: self - .scrollbar_thumb_hover_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - scrollbar_thumb_active_background: self - .scrollbar_thumb_active_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()) - .or(scrollbar_thumb_background), - scrollbar_thumb_border: self - .scrollbar_thumb_border - .as_ref() - .and_then(|color| try_parse_color(color).ok()), + scrollbar_thumb_hover_background, + scrollbar_thumb_active_background, + scrollbar_thumb_border, scrollbar_track_background: self .scrollbar_track_background .as_ref() @@ -840,6 +871,26 @@ impl ThemeColorsContent { .scrollbar_track_border .as_ref() .and_then(|color| try_parse_color(color).ok()), + minimap_thumb_background: self + .minimap_thumb_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_background.map(ensure_non_opaque)), + minimap_thumb_hover_background: self + .minimap_thumb_hover_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_hover_background.map(ensure_non_opaque)), + minimap_thumb_active_background: self + .minimap_thumb_active_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_active_background.map(ensure_non_opaque)), + minimap_thumb_border: self + .minimap_thumb_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()) + .or(scrollbar_thumb_border), editor_foreground: self .editor_foreground .as_ref() diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index 3d0df27985..a66f206744 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -143,6 +143,14 @@ pub struct ThemeColors { pub scrollbar_track_background: Hsla, /// The border color of the scrollbar track. pub scrollbar_track_border: Hsla, + /// The color of the minimap thumb. + pub minimap_thumb_background: Hsla, + /// The color of the minimap thumb when hovered over. + pub minimap_thumb_hover_background: Hsla, + /// The color of the minimap thumb whilst being actively dragged. + pub minimap_thumb_active_background: Hsla, + /// The border color of the minimap thumb. + pub minimap_thumb_border: Hsla, // === // Editor @@ -327,6 +335,10 @@ pub enum ThemeColorField { ScrollbarThumbBorder, ScrollbarTrackBackground, ScrollbarTrackBorder, + MinimapThumbBackground, + MinimapThumbHoverBackground, + MinimapThumbActiveBackground, + MinimapThumbBorder, EditorForeground, EditorBackground, EditorGutterBackground, @@ -437,6 +449,10 @@ impl ThemeColors { ThemeColorField::ScrollbarThumbBorder => self.scrollbar_thumb_border, ThemeColorField::ScrollbarTrackBackground => self.scrollbar_track_background, ThemeColorField::ScrollbarTrackBorder => self.scrollbar_track_border, + ThemeColorField::MinimapThumbBackground => self.minimap_thumb_background, + ThemeColorField::MinimapThumbHoverBackground => self.minimap_thumb_hover_background, + ThemeColorField::MinimapThumbActiveBackground => self.minimap_thumb_active_background, + ThemeColorField::MinimapThumbBorder => self.minimap_thumb_border, ThemeColorField::EditorForeground => self.editor_foreground, ThemeColorField::EditorBackground => self.editor_background, ThemeColorField::EditorGutterBackground => self.editor_gutter_background, diff --git a/crates/theme_importer/src/vscode/converter.rs b/crates/theme_importer/src/vscode/converter.rs index 99f7625896..9a17a4cdd2 100644 --- a/crates/theme_importer/src/vscode/converter.rs +++ b/crates/theme_importer/src/vscode/converter.rs @@ -174,6 +174,7 @@ impl VsCodeThemeConverter { scrollbar_thumb_border: vscode_scrollbar_slider_background.clone(), scrollbar_track_background: vscode_editor_background.clone(), scrollbar_track_border: vscode_colors.editor_overview_ruler.border.clone(), + minimap_thumb_background: vscode_colors.minimap_slider.background.clone(), editor_foreground: vscode_editor_foreground .clone() .or(vscode_token_colors_foreground.clone()),