editor: Implement hover color for scrollbars (#28064)
This PR adds hover colors to the editor scrollbars: https://github.com/user-attachments/assets/6600810e-7e8e-4dee-9bef-b7be303b5fe0 The color used here is the existing `scrollbar_thumb_hover_background` color provided by themes. Looking forward to feedback 😄 Release Notes: - Added hover state to editor scrollbars.
This commit is contained in:
parent
55a0bb2a91
commit
3b90d62bb2
2 changed files with 109 additions and 25 deletions
|
@ -25,7 +25,7 @@ use crate::{
|
||||||
inlay_hint_settings,
|
inlay_hint_settings,
|
||||||
items::BufferSearchHighlights,
|
items::BufferSearchHighlights,
|
||||||
mouse_context_menu::{self, MenuPosition},
|
mouse_context_menu::{self, MenuPosition},
|
||||||
scroll::scroll_amount::ScrollAmount,
|
scroll::{ActiveScrollbarState, ScrollbarThumbState, scroll_amount::ScrollAmount},
|
||||||
};
|
};
|
||||||
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
|
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
|
||||||
use collections::{BTreeMap, HashMap};
|
use collections::{BTreeMap, HashMap};
|
||||||
|
@ -1457,7 +1457,7 @@ impl EditorElement {
|
||||||
// cancel the scrollbar drag.
|
// cancel the scrollbar drag.
|
||||||
if cx.has_active_drag() {
|
if cx.has_active_drag() {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.scroll_manager.reset_scrollbar_dragging_state(cx)
|
editor.scroll_manager.reset_scrollbar_state(cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1500,6 +1500,7 @@ impl EditorElement {
|
||||||
scroll_position,
|
scroll_position,
|
||||||
self.style.scrollbar_width,
|
self.style.scrollbar_width,
|
||||||
show_scrollbars,
|
show_scrollbars,
|
||||||
|
self.editor.read(cx).scroll_manager.active_scrollbar_state(),
|
||||||
window,
|
window,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -5104,7 +5105,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_scrollbars(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
|
fn paint_scrollbars(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
|
||||||
let Some(scrollbars_layout) = &layout.scrollbars_layout else {
|
let Some(scrollbars_layout) = layout.scrollbars_layout.take() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5153,10 +5154,16 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let scrollbar_thumb_color = match scrollbar_layout.thumb_state {
|
||||||
|
ScrollbarThumbState::Dragging | ScrollbarThumbState::Hovered => {
|
||||||
|
cx.theme().colors().scrollbar_thumb_hover_background
|
||||||
|
}
|
||||||
|
ScrollbarThumbState::Idle => cx.theme().colors().scrollbar_thumb_background,
|
||||||
|
};
|
||||||
window.paint_quad(quad(
|
window.paint_quad(quad(
|
||||||
thumb_bounds,
|
thumb_bounds,
|
||||||
Corners::default(),
|
Corners::default(),
|
||||||
cx.theme().colors().scrollbar_thumb_background,
|
scrollbar_thumb_color,
|
||||||
scrollbar_edges,
|
scrollbar_edges,
|
||||||
cx.theme().colors().scrollbar_thumb_border,
|
cx.theme().colors().scrollbar_thumb_border,
|
||||||
BorderStyle::Solid,
|
BorderStyle::Solid,
|
||||||
|
@ -5203,13 +5210,22 @@ impl EditorElement {
|
||||||
});
|
});
|
||||||
editor.set_scroll_position(position, window, cx);
|
editor.set_scroll_position(position, window, cx);
|
||||||
}
|
}
|
||||||
cx.stop_propagation();
|
|
||||||
} else {
|
|
||||||
editor.scroll_manager.reset_scrollbar_dragging_state(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if scrollbars_layout.get_hovered_axis(window).is_some() {
|
|
||||||
editor.scroll_manager.show_scrollbars(window, cx);
|
editor.scroll_manager.show_scrollbars(window, cx);
|
||||||
|
cx.stop_propagation();
|
||||||
|
} else if let Some((layout, axis)) = scrollbars_layout.get_hovered_axis(window)
|
||||||
|
{
|
||||||
|
if layout.thumb_bounds().contains(&event.position) {
|
||||||
|
editor
|
||||||
|
.scroll_manager
|
||||||
|
.set_hovered_scroll_thumb_axis(axis, cx);
|
||||||
|
} else {
|
||||||
|
editor.scroll_manager.reset_scrollbar_state(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.scroll_manager.show_scrollbars(window, cx);
|
||||||
|
} else {
|
||||||
|
editor.scroll_manager.reset_scrollbar_state(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
mouse_position = event.position;
|
mouse_position = event.position;
|
||||||
|
@ -5220,13 +5236,19 @@ impl EditorElement {
|
||||||
if self.editor.read(cx).scroll_manager.any_scrollbar_dragged() {
|
if self.editor.read(cx).scroll_manager.any_scrollbar_dragged() {
|
||||||
window.on_mouse_event({
|
window.on_mouse_event({
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
move |_: &MouseUpEvent, phase, _, cx| {
|
move |_: &MouseUpEvent, phase, window, cx| {
|
||||||
if phase == DispatchPhase::Capture {
|
if phase == DispatchPhase::Capture {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.scroll_manager.reset_scrollbar_dragging_state(cx);
|
if let Some((_, axis)) = scrollbars_layout.get_hovered_axis(window) {
|
||||||
|
editor
|
||||||
|
.scroll_manager
|
||||||
|
.set_hovered_scroll_thumb_axis(axis, cx);
|
||||||
|
} else {
|
||||||
|
editor.scroll_manager.reset_scrollbar_state(cx);
|
||||||
|
}
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5234,7 +5256,6 @@ impl EditorElement {
|
||||||
} else {
|
} else {
|
||||||
window.on_mouse_event({
|
window.on_mouse_event({
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let scrollbars_layout = scrollbars_layout.clone();
|
|
||||||
|
|
||||||
move |event: &MouseDownEvent, phase, window, cx| {
|
move |event: &MouseDownEvent, phase, window, cx| {
|
||||||
if phase == DispatchPhase::Capture {
|
if phase == DispatchPhase::Capture {
|
||||||
|
@ -5255,7 +5276,9 @@ impl EditorElement {
|
||||||
let thumb_bounds = scrollbar_layout.thumb_bounds();
|
let thumb_bounds = scrollbar_layout.thumb_bounds();
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.scroll_manager.set_dragged_scrollbar_axis(axis, cx);
|
editor
|
||||||
|
.scroll_manager
|
||||||
|
.set_dragged_scroll_thumb_axis(axis, cx);
|
||||||
|
|
||||||
let event_position = event.position.along(axis);
|
let event_position = event.position.along(axis);
|
||||||
|
|
||||||
|
@ -8037,6 +8060,7 @@ impl EditorScrollbars {
|
||||||
scroll_position: gpui::Point<f32>,
|
scroll_position: gpui::Point<f32>,
|
||||||
scrollbar_width: Pixels,
|
scrollbar_width: Pixels,
|
||||||
show_scrollbars: bool,
|
show_scrollbars: bool,
|
||||||
|
scrollbar_state: Option<&ActiveScrollbarState>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ScrollbarLayoutInformation {
|
let ScrollbarLayoutInformation {
|
||||||
|
@ -8082,6 +8106,10 @@ impl EditorScrollbars {
|
||||||
axis != ScrollbarAxis::Horizontal || editor_content_size < scroll_range
|
axis != ScrollbarAxis::Horizontal || editor_content_size < scroll_range
|
||||||
})
|
})
|
||||||
.map(|(editor_content_size, scroll_range)| {
|
.map(|(editor_content_size, scroll_range)| {
|
||||||
|
let thumb_state = scrollbar_state
|
||||||
|
.and_then(|state| state.thumb_state_for_axis(axis))
|
||||||
|
.unwrap_or(ScrollbarThumbState::Idle);
|
||||||
|
|
||||||
ScrollbarLayout::new(
|
ScrollbarLayout::new(
|
||||||
window.insert_hitbox(scrollbar_bounds_for(axis), false),
|
window.insert_hitbox(scrollbar_bounds_for(axis), false),
|
||||||
editor_content_size,
|
editor_content_size,
|
||||||
|
@ -8089,6 +8117,7 @@ impl EditorScrollbars {
|
||||||
glyph_grid_cell.along(axis),
|
glyph_grid_cell.along(axis),
|
||||||
content_offset.along(axis),
|
content_offset.along(axis),
|
||||||
scroll_position.along(axis),
|
scroll_position.along(axis),
|
||||||
|
thumb_state,
|
||||||
axis,
|
axis,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -8124,6 +8153,7 @@ struct ScrollbarLayout {
|
||||||
text_unit_size: Pixels,
|
text_unit_size: Pixels,
|
||||||
content_offset: Pixels,
|
content_offset: Pixels,
|
||||||
thumb_size: Pixels,
|
thumb_size: Pixels,
|
||||||
|
thumb_state: ScrollbarThumbState,
|
||||||
axis: ScrollbarAxis,
|
axis: ScrollbarAxis,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8140,6 +8170,7 @@ impl ScrollbarLayout {
|
||||||
glyph_space: Pixels,
|
glyph_space: Pixels,
|
||||||
content_offset: Pixels,
|
content_offset: Pixels,
|
||||||
scroll_position: f32,
|
scroll_position: f32,
|
||||||
|
thumb_state: ScrollbarThumbState,
|
||||||
axis: ScrollbarAxis,
|
axis: ScrollbarAxis,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let track_bounds = scrollbar_track_hitbox.bounds;
|
let track_bounds = scrollbar_track_hitbox.bounds;
|
||||||
|
@ -8164,6 +8195,7 @@ impl ScrollbarLayout {
|
||||||
text_unit_size,
|
text_unit_size,
|
||||||
content_offset,
|
content_offset,
|
||||||
thumb_size,
|
thumb_size,
|
||||||
|
thumb_state,
|
||||||
axis,
|
axis,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,29 @@ impl OngoingScroll {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ScrollbarThumbState {
|
||||||
|
Idle,
|
||||||
|
Hovered,
|
||||||
|
Dragging,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub struct ActiveScrollbarState {
|
||||||
|
axis: Axis,
|
||||||
|
thumb_state: ScrollbarThumbState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveScrollbarState {
|
||||||
|
pub fn new(axis: Axis, thumb_state: ScrollbarThumbState) -> Self {
|
||||||
|
ActiveScrollbarState { axis, thumb_state }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn thumb_state_for_axis(&self, axis: Axis) -> Option<ScrollbarThumbState> {
|
||||||
|
(self.axis == axis).then_some(self.thumb_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ScrollManager {
|
pub struct ScrollManager {
|
||||||
pub(crate) vertical_scroll_margin: f32,
|
pub(crate) vertical_scroll_margin: f32,
|
||||||
anchor: ScrollAnchor,
|
anchor: ScrollAnchor,
|
||||||
|
@ -131,7 +154,7 @@ pub struct ScrollManager {
|
||||||
last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
|
last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
|
||||||
show_scrollbars: bool,
|
show_scrollbars: bool,
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
hide_scrollbar_task: Option<Task<()>>,
|
||||||
dragging_scrollbar: Option<Axis>,
|
active_scrollbar: Option<ActiveScrollbarState>,
|
||||||
visible_line_count: Option<f32>,
|
visible_line_count: Option<f32>,
|
||||||
forbid_vertical_scroll: bool,
|
forbid_vertical_scroll: bool,
|
||||||
}
|
}
|
||||||
|
@ -145,7 +168,7 @@ impl ScrollManager {
|
||||||
autoscroll_request: None,
|
autoscroll_request: None,
|
||||||
show_scrollbars: true,
|
show_scrollbars: true,
|
||||||
hide_scrollbar_task: None,
|
hide_scrollbar_task: None,
|
||||||
dragging_scrollbar: None,
|
active_scrollbar: None,
|
||||||
last_autoscroll: None,
|
last_autoscroll: None,
|
||||||
visible_line_count: None,
|
visible_line_count: None,
|
||||||
forbid_vertical_scroll: false,
|
forbid_vertical_scroll: false,
|
||||||
|
@ -322,24 +345,53 @@ impl ScrollManager {
|
||||||
self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
|
self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
|
||||||
|
self.active_scrollbar.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
|
pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
|
||||||
self.dragging_scrollbar
|
self.active_scrollbar
|
||||||
|
.as_ref()
|
||||||
|
.map(|scrollbar| scrollbar.axis)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn any_scrollbar_dragged(&self) -> bool {
|
pub fn any_scrollbar_dragged(&self) -> bool {
|
||||||
self.dragging_scrollbar.is_some()
|
self.active_scrollbar
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dragged_scrollbar_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
|
pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
|
||||||
if self.dragging_scrollbar != Some(axis) {
|
self.update_active_scrollbar_state(
|
||||||
self.dragging_scrollbar = Some(axis);
|
Some(ActiveScrollbarState::new(
|
||||||
cx.notify();
|
axis,
|
||||||
}
|
ScrollbarThumbState::Hovered,
|
||||||
|
)),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_scrollbar_dragging_state(&mut self, cx: &mut Context<Editor>) {
|
pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
|
||||||
if self.dragging_scrollbar.is_some() {
|
self.update_active_scrollbar_state(
|
||||||
self.dragging_scrollbar = None;
|
Some(ActiveScrollbarState::new(
|
||||||
|
axis,
|
||||||
|
ScrollbarThumbState::Dragging,
|
||||||
|
)),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) {
|
||||||
|
self.update_active_scrollbar_state(None, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_active_scrollbar_state(
|
||||||
|
&mut self,
|
||||||
|
new_state: Option<ActiveScrollbarState>,
|
||||||
|
cx: &mut Context<Editor>,
|
||||||
|
) {
|
||||||
|
if self.active_scrollbar != new_state {
|
||||||
|
self.active_scrollbar = new_state;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue