editor: Add horizontal scrollbar (#19495)
 Closes #4427 Release Notes: - Added a horizontal scrollbar to the editor panel - Added `axis` option to `scrollbar` in the Zed configuration, which can forcefully disable either the horizontal or vertical scrollbar - Added `horizontal_scroll_margin` equivalent to `vertical_scroll_margin` in the Zed configuration Rough Edges: This feature seems mostly stable from my testing. I've been using a development build for about a week with no issues. Any feedback would be appreciated. There are a few things to note as well: 1. Scrolling to the lower right occasionally causes scrollbar clipping on my end, but it isn't consistent and it isn't major. Some more testing would definitely be a good idea. [FIXED] 2. Documentation may need to be modified 3. I added an `AxisPair` type to the `editor` crate to manage values that have a horizontal and vertical variant. I'm not sure if that's the optimal way to do it, but I didn't see a good alternative. The `Point` type would technically work, but it may cause confusion. --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
parent
6fa5a17586
commit
ed3e647ed7
6 changed files with 696 additions and 203 deletions
|
@ -254,7 +254,14 @@
|
|||
// Whether to show selected symbol occurrences in the scrollbar.
|
||||
"selected_symbol": true,
|
||||
// Whether to show diagnostic indicators in the scrollbar.
|
||||
"diagnostics": true
|
||||
"diagnostics": true,
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
"axes": {
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
"horizontal": true,
|
||||
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
"vertical": true
|
||||
}
|
||||
},
|
||||
// Enable middle-click paste on Linux.
|
||||
"middle_click_paste": true,
|
||||
|
@ -304,6 +311,8 @@
|
|||
"vertical_scroll_margin": 3,
|
||||
// Whether to scroll when clicking near the edge of the visible text area.
|
||||
"autoscroll_on_clicks": false,
|
||||
// The number of characters to keep on either side when scrolling with the mouse
|
||||
"horizontal_scroll_margin": 5,
|
||||
// Scroll sensitivity multiplier. This multiplier is applied
|
||||
// to both the horizontal and vertical delta values while scrolling.
|
||||
"scroll_sensitivity": 1.0,
|
||||
|
|
|
@ -18,6 +18,7 @@ pub struct EditorSettings {
|
|||
pub scroll_beyond_last_line: ScrollBeyondLastLine,
|
||||
pub vertical_scroll_margin: f32,
|
||||
pub autoscroll_on_clicks: bool,
|
||||
pub horizontal_scroll_margin: f32,
|
||||
pub scroll_sensitivity: f32,
|
||||
pub relative_line_numbers: bool,
|
||||
pub seed_search_query_from_cursor: SeedQuerySetting,
|
||||
|
@ -105,6 +106,7 @@ pub struct Scrollbar {
|
|||
pub search_results: bool,
|
||||
pub diagnostics: bool,
|
||||
pub cursors: bool,
|
||||
pub axes: ScrollbarAxes,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
|
@ -132,6 +134,21 @@ pub enum ShowScrollbar {
|
|||
Never,
|
||||
}
|
||||
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct ScrollbarAxes {
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
///
|
||||
/// Default: true
|
||||
pub horizontal: bool,
|
||||
|
||||
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
///
|
||||
/// Default: true
|
||||
pub vertical: bool,
|
||||
}
|
||||
|
||||
/// The key to use for adding multiple cursors
|
||||
///
|
||||
/// Default: alt
|
||||
|
@ -219,6 +236,10 @@ pub struct EditorSettingsContent {
|
|||
///
|
||||
/// Default: false
|
||||
pub autoscroll_on_clicks: Option<bool>,
|
||||
/// The number of characters to keep on either side when scrolling with the mouse.
|
||||
///
|
||||
/// Default: 5.
|
||||
pub horizontal_scroll_margin: Option<f32>,
|
||||
/// Scroll sensitivity multiplier. This multiplier is applied
|
||||
/// to both the horizontal and vertical delta values while scrolling.
|
||||
///
|
||||
|
@ -328,6 +349,22 @@ pub struct ScrollbarContent {
|
|||
///
|
||||
/// Default: true
|
||||
pub cursors: Option<bool>,
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
pub axes: Option<ScrollbarAxesContent>,
|
||||
}
|
||||
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct ScrollbarAxesContent {
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
///
|
||||
/// Default: true
|
||||
horizontal: Option<bool>,
|
||||
|
||||
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
///
|
||||
/// Default: true
|
||||
vertical: Option<bool>,
|
||||
}
|
||||
|
||||
/// Gutter related settings
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::{
|
|||
hunk_status,
|
||||
items::BufferSearchHighlights,
|
||||
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
||||
scroll::scroll_amount::ScrollAmount,
|
||||
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
|
||||
BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
|
||||
DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
|
||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||
|
@ -31,7 +31,7 @@ use file_icons::FileIcons;
|
|||
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||
use gpui::{
|
||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||
transparent_black, Action, AnyElement, AvailableSpace, Bounds, ClickEvent, ClipboardItem,
|
||||
transparent_black, Action, AnyElement, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem,
|
||||
ContentMask, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler,
|
||||
Entity, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
||||
|
@ -154,7 +154,7 @@ pub struct EditorElement {
|
|||
type DisplayRowDelta = u32;
|
||||
|
||||
impl EditorElement {
|
||||
pub(crate) const SCROLLBAR_WIDTH: Pixels = px(13.);
|
||||
pub(crate) const SCROLLBAR_WIDTH: Pixels = px(15.);
|
||||
|
||||
pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
|
||||
Self {
|
||||
|
@ -714,9 +714,24 @@ impl EditorElement {
|
|||
scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
|
||||
}
|
||||
|
||||
let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
|
||||
let left = text_bounds.origin.x + horizontal_margin;
|
||||
let right = text_bounds.top_right().x - horizontal_margin;
|
||||
// We need horizontal width of text
|
||||
let style = editor.style.clone().unwrap_or_default();
|
||||
let font_id = cx.text_system().resolve_font(&style.text.font());
|
||||
let font_size = style.text.font_size.to_pixels(cx.rem_size());
|
||||
let em_width = cx
|
||||
.text_system()
|
||||
.typographic_bounds(font_id, font_size, 'm')
|
||||
.unwrap()
|
||||
.size
|
||||
.width;
|
||||
|
||||
let scroll_margin_x = EditorSettings::get_global(cx).horizontal_scroll_margin;
|
||||
|
||||
let scroll_space: Pixels = scroll_margin_x * em_width;
|
||||
|
||||
let left = text_bounds.origin.x + scroll_space;
|
||||
let right = text_bounds.top_right().x - scroll_space;
|
||||
|
||||
if event.position.x < left {
|
||||
scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
|
||||
}
|
||||
|
@ -1161,15 +1176,20 @@ impl EditorElement {
|
|||
cursor_layouts
|
||||
}
|
||||
|
||||
fn layout_scrollbar(
|
||||
fn layout_scrollbars(
|
||||
&self,
|
||||
snapshot: &EditorSnapshot,
|
||||
bounds: Bounds<Pixels>,
|
||||
scrollbar_range_data: ScrollbarRangeData,
|
||||
scroll_position: gpui::Point<f32>,
|
||||
rows_per_page: f32,
|
||||
non_visible_cursors: bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<ScrollbarLayout> {
|
||||
) -> AxisPair<Option<ScrollbarLayout>> {
|
||||
let letter_size = scrollbar_range_data.letter_size;
|
||||
let text_units_per_page = axis_pair(
|
||||
scrollbar_range_data.scrollbar_bounds.size.width / letter_size.width,
|
||||
scrollbar_range_data.scrollbar_bounds.size.height / letter_size.height,
|
||||
);
|
||||
|
||||
let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
|
||||
let show_scrollbars = match scrollbar_settings.show {
|
||||
ShowScrollbar::Auto => {
|
||||
|
@ -1197,45 +1217,139 @@ impl EditorElement {
|
|||
ShowScrollbar::Always => true,
|
||||
ShowScrollbar::Never => false,
|
||||
};
|
||||
|
||||
let axes: AxisPair<bool> = scrollbar_settings.axes.into();
|
||||
|
||||
if snapshot.mode != EditorMode::Full {
|
||||
return None;
|
||||
return axis_pair(None, None);
|
||||
}
|
||||
|
||||
let visible_row_range = scroll_position.y..scroll_position.y + rows_per_page;
|
||||
let visible_range = axis_pair(
|
||||
axes.horizontal
|
||||
.then(|| scroll_position.x..scroll_position.x + text_units_per_page.horizontal),
|
||||
axes.vertical
|
||||
.then(|| scroll_position.y..scroll_position.y + text_units_per_page.vertical),
|
||||
);
|
||||
|
||||
// If a drag took place after we started dragging the scrollbar,
|
||||
// cancel the scrollbar drag.
|
||||
if cx.has_active_drag() {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
||||
editor
|
||||
.scroll_manager
|
||||
.set_is_dragging_scrollbar(Axis::Horizontal, false, cx);
|
||||
editor
|
||||
.scroll_manager
|
||||
.set_is_dragging_scrollbar(Axis::Vertical, false, cx);
|
||||
});
|
||||
}
|
||||
|
||||
let track_bounds = Bounds::from_corners(
|
||||
point(self.scrollbar_left(&bounds), bounds.origin.y),
|
||||
point(bounds.bottom_right().x, bounds.bottom_left().y),
|
||||
let text_bounds = scrollbar_range_data.scrollbar_bounds;
|
||||
|
||||
let track_bounds = axis_pair(
|
||||
axes.horizontal.then(|| {
|
||||
Bounds::from_corners(
|
||||
point(
|
||||
text_bounds.bottom_left().x,
|
||||
text_bounds.bottom_left().y - self.style.scrollbar_width,
|
||||
),
|
||||
point(
|
||||
text_bounds.bottom_right().x
|
||||
- if axes.vertical {
|
||||
self.style.scrollbar_width
|
||||
} else {
|
||||
px(0.)
|
||||
},
|
||||
text_bounds.bottom_right().y,
|
||||
),
|
||||
)
|
||||
}),
|
||||
axes.vertical.then(|| {
|
||||
Bounds::from_corners(
|
||||
point(self.scrollbar_left(&text_bounds), text_bounds.origin.y),
|
||||
text_bounds.bottom_right(),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
let settings = EditorSettings::get_global(cx);
|
||||
let scroll_beyond_last_line: f32 = match settings.scroll_beyond_last_line {
|
||||
ScrollBeyondLastLine::OnePage => rows_per_page,
|
||||
ScrollBeyondLastLine::Off => 1.0,
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => 1.0 + settings.vertical_scroll_margin,
|
||||
};
|
||||
let total_rows =
|
||||
(snapshot.max_point().row().as_f32() + scroll_beyond_last_line).max(rows_per_page);
|
||||
let height = bounds.size.height;
|
||||
let px_per_row = height / total_rows;
|
||||
let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
|
||||
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.);
|
||||
let scroll_range_size = scrollbar_range_data.scroll_range.size;
|
||||
let total_text_units = axis_pair(
|
||||
Some(scroll_range_size.width / letter_size.width),
|
||||
Some(scroll_range_size.height / letter_size.height),
|
||||
);
|
||||
|
||||
Some(ScrollbarLayout {
|
||||
hitbox: cx.insert_hitbox(track_bounds, false),
|
||||
visible_row_range,
|
||||
row_height,
|
||||
visible: show_scrollbars,
|
||||
thumb_height,
|
||||
})
|
||||
let thumb_size = axis_pair(
|
||||
total_text_units
|
||||
.horizontal
|
||||
.zip(track_bounds.horizontal)
|
||||
.map(|(total_text_units_x, track_bounds_x)| {
|
||||
let thumb_percent =
|
||||
(text_units_per_page.horizontal / total_text_units_x).min(1.);
|
||||
|
||||
track_bounds_x.size.width * thumb_percent
|
||||
}),
|
||||
total_text_units.vertical.zip(track_bounds.vertical).map(
|
||||
|(total_text_units_y, track_bounds_y)| {
|
||||
let thumb_percent = (text_units_per_page.vertical / total_text_units_y).min(1.);
|
||||
|
||||
track_bounds_y.size.height * thumb_percent
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// NOTE: Space not taken by track bounds divided by text units not on screen
|
||||
let text_unit_size = axis_pair(
|
||||
thumb_size
|
||||
.horizontal
|
||||
.zip(track_bounds.horizontal)
|
||||
.zip(total_text_units.horizontal)
|
||||
.map(|((thumb_size, track_bounds), total_text_units)| {
|
||||
(track_bounds.size.width - thumb_size)
|
||||
/ (total_text_units - text_units_per_page.horizontal).max(0.)
|
||||
}),
|
||||
thumb_size
|
||||
.vertical
|
||||
.zip(track_bounds.vertical)
|
||||
.zip(total_text_units.vertical)
|
||||
.map(|((thumb_size, track_bounds), total_text_units)| {
|
||||
(track_bounds.size.height - thumb_size)
|
||||
/ (total_text_units - text_units_per_page.vertical).max(0.)
|
||||
}),
|
||||
);
|
||||
|
||||
let horizontal_scrollbar = track_bounds
|
||||
.horizontal
|
||||
.zip(visible_range.horizontal)
|
||||
.zip(text_unit_size.horizontal)
|
||||
.zip(thumb_size.horizontal)
|
||||
.map(
|
||||
|(((track_bounds, visible_range), text_unit_size), thumb_size)| ScrollbarLayout {
|
||||
hitbox: cx.insert_hitbox(track_bounds, false),
|
||||
visible_range,
|
||||
text_unit_size,
|
||||
visible: show_scrollbars,
|
||||
thumb_size,
|
||||
axis: Axis::Horizontal,
|
||||
},
|
||||
);
|
||||
|
||||
let vertical_scrollbar = track_bounds
|
||||
.vertical
|
||||
.zip(visible_range.vertical)
|
||||
.zip(text_unit_size.vertical)
|
||||
.zip(thumb_size.vertical)
|
||||
.map(
|
||||
|(((track_bounds, visible_range), text_unit_size), thumb_size)| ScrollbarLayout {
|
||||
hitbox: cx.insert_hitbox(track_bounds, false),
|
||||
visible_range,
|
||||
text_unit_size,
|
||||
visible: show_scrollbars,
|
||||
thumb_size,
|
||||
axis: Axis::Vertical,
|
||||
},
|
||||
);
|
||||
|
||||
axis_pair(horizontal_scrollbar, vertical_scrollbar)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
@ -3419,10 +3533,13 @@ impl EditorElement {
|
|||
+ layout.position_map.em_width / 2.)
|
||||
- scroll_left;
|
||||
|
||||
let show_scrollbars = layout
|
||||
.scrollbar_layout
|
||||
.as_ref()
|
||||
.map_or(false, |scrollbar| scrollbar.visible);
|
||||
let show_scrollbars = {
|
||||
let (scrollbar_x, scrollbar_y) = &layout.scrollbars_layout.as_xy();
|
||||
|
||||
scrollbar_x.as_ref().map_or(false, |sx| sx.visible)
|
||||
|| scrollbar_y.as_ref().map_or(false, |sy| sy.visible)
|
||||
};
|
||||
|
||||
if x < layout.text_hitbox.origin.x
|
||||
|| (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds))
|
||||
{
|
||||
|
@ -3903,137 +4020,306 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
let Some(scrollbar_layout) = layout.scrollbar_layout.as_ref() else {
|
||||
return;
|
||||
};
|
||||
fn paint_scrollbars(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
let (scrollbar_x, scrollbar_y) = layout.scrollbars_layout.as_xy();
|
||||
|
||||
let thumb_bounds = scrollbar_layout.thumb_bounds();
|
||||
if scrollbar_layout.visible {
|
||||
cx.paint_layer(scrollbar_layout.hitbox.bounds, |cx| {
|
||||
cx.paint_quad(quad(
|
||||
scrollbar_layout.hitbox.bounds,
|
||||
Corners::default(),
|
||||
cx.theme().colors().scrollbar_track_background,
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: Pixels::ZERO,
|
||||
bottom: Pixels::ZERO,
|
||||
left: ScrollbarLayout::BORDER_WIDTH,
|
||||
},
|
||||
cx.theme().colors().scrollbar_track_border,
|
||||
));
|
||||
|
||||
let fast_markers =
|
||||
self.collect_fast_scrollbar_markers(layout, scrollbar_layout, cx);
|
||||
// Refresh slow scrollbar markers in the background. Below, we paint whatever markers have already been computed.
|
||||
self.refresh_slow_scrollbar_markers(layout, scrollbar_layout, cx);
|
||||
|
||||
let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
|
||||
for marker in markers.iter().chain(&fast_markers) {
|
||||
let mut marker = marker.clone();
|
||||
marker.bounds.origin += scrollbar_layout.hitbox.origin;
|
||||
cx.paint_quad(marker);
|
||||
}
|
||||
|
||||
cx.paint_quad(quad(
|
||||
thumb_bounds,
|
||||
Corners::default(),
|
||||
cx.theme().colors().scrollbar_thumb_background,
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: Pixels::ZERO,
|
||||
bottom: Pixels::ZERO,
|
||||
left: ScrollbarLayout::BORDER_WIDTH,
|
||||
},
|
||||
cx.theme().colors().scrollbar_thumb_border,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
cx.set_cursor_style(CursorStyle::Arrow, &scrollbar_layout.hitbox);
|
||||
|
||||
let row_height = scrollbar_layout.row_height;
|
||||
let row_range = scrollbar_layout.visible_row_range.clone();
|
||||
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
if let Some(scrollbar_layout) = scrollbar_x {
|
||||
let hitbox = scrollbar_layout.hitbox.clone();
|
||||
let mut mouse_position = cx.mouse_position();
|
||||
move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
return;
|
||||
}
|
||||
let text_unit_size = scrollbar_layout.text_unit_size;
|
||||
let visible_range = scrollbar_layout.visible_range.clone();
|
||||
let thumb_bounds = scrollbar_layout.thumb_bounds();
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
if event.pressed_button == Some(MouseButton::Left)
|
||||
&& editor.scroll_manager.is_dragging_scrollbar()
|
||||
{
|
||||
let y = mouse_position.y;
|
||||
let new_y = event.position.y;
|
||||
if (hitbox.top()..hitbox.bottom()).contains(&y) {
|
||||
let mut position = editor.scroll_position(cx);
|
||||
position.y += (new_y - y) / row_height;
|
||||
if position.y < 0.0 {
|
||||
position.y = 0.0;
|
||||
}
|
||||
editor.set_scroll_position(position, cx);
|
||||
}
|
||||
if scrollbar_layout.visible {
|
||||
cx.paint_layer(hitbox.bounds, |cx| {
|
||||
cx.paint_quad(quad(
|
||||
hitbox.bounds,
|
||||
Corners::default(),
|
||||
cx.theme().colors().scrollbar_track_background,
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: Pixels::ZERO,
|
||||
bottom: Pixels::ZERO,
|
||||
left: Pixels::ZERO,
|
||||
},
|
||||
cx.theme().colors().scrollbar_track_border,
|
||||
));
|
||||
|
||||
cx.stop_propagation();
|
||||
} else {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
||||
if hitbox.is_hovered(cx) {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
}
|
||||
mouse_position = event.position;
|
||||
cx.paint_quad(quad(
|
||||
thumb_bounds,
|
||||
Corners::default(),
|
||||
cx.theme().colors().scrollbar_thumb_background,
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: Pixels::ZERO,
|
||||
bottom: Pixels::ZERO,
|
||||
left: ScrollbarLayout::BORDER_WIDTH,
|
||||
},
|
||||
cx.theme().colors().scrollbar_thumb_border,
|
||||
));
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
|
||||
cx.set_cursor_style(CursorStyle::Arrow, &hitbox);
|
||||
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
move |_: &MouseUpEvent, phase, cx| {
|
||||
|
||||
// there may be a way to avoid this clone
|
||||
let hitbox = hitbox.clone();
|
||||
|
||||
let mut mouse_position = cx.mouse_position();
|
||||
move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
||||
cx.stop_propagation();
|
||||
});
|
||||
if event.pressed_button == Some(MouseButton::Left)
|
||||
&& editor
|
||||
.scroll_manager
|
||||
.is_dragging_scrollbar(Axis::Horizontal)
|
||||
{
|
||||
let x = mouse_position.x;
|
||||
let new_x = event.position.x;
|
||||
if (hitbox.left()..hitbox.right()).contains(&x) {
|
||||
let mut position = editor.scroll_position(cx);
|
||||
|
||||
position.x += (new_x - x) / text_unit_size;
|
||||
if position.x < 0.0 {
|
||||
position.x = 0.0;
|
||||
}
|
||||
editor.set_scroll_position(position, cx);
|
||||
}
|
||||
|
||||
cx.stop_propagation();
|
||||
} else {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(
|
||||
Axis::Horizontal,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
|
||||
if hitbox.is_hovered(cx) {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
}
|
||||
mouse_position = event.position;
|
||||
})
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
if self
|
||||
.editor
|
||||
.read(cx)
|
||||
.scroll_manager
|
||||
.is_dragging_scrollbar(Axis::Horizontal)
|
||||
{
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
move |_: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(
|
||||
Axis::Horizontal,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
cx.stop_propagation();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
|
||||
move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture || !hitbox.is_hovered(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(
|
||||
Axis::Horizontal,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
|
||||
let x = event.position.x;
|
||||
|
||||
if x < thumb_bounds.left() || thumb_bounds.right() < x {
|
||||
let center_row =
|
||||
((x - hitbox.left()) / text_unit_size).round() as u32;
|
||||
let top_row = center_row.saturating_sub(
|
||||
(visible_range.end - visible_range.start) as u32 / 2,
|
||||
);
|
||||
|
||||
let mut position = editor.scroll_position(cx);
|
||||
position.x = top_row as f32;
|
||||
|
||||
editor.set_scroll_position(position, cx);
|
||||
} else {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
|
||||
cx.stop_propagation();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(scrollbar_layout) = scrollbar_y {
|
||||
let hitbox = scrollbar_layout.hitbox.clone();
|
||||
let text_unit_size = scrollbar_layout.text_unit_size;
|
||||
let visible_range = scrollbar_layout.visible_range.clone();
|
||||
let thumb_bounds = scrollbar_layout.thumb_bounds();
|
||||
|
||||
if scrollbar_layout.visible {
|
||||
cx.paint_layer(hitbox.bounds, |cx| {
|
||||
cx.paint_quad(quad(
|
||||
hitbox.bounds,
|
||||
Corners::default(),
|
||||
cx.theme().colors().scrollbar_track_background,
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: Pixels::ZERO,
|
||||
bottom: Pixels::ZERO,
|
||||
left: ScrollbarLayout::BORDER_WIDTH,
|
||||
},
|
||||
cx.theme().colors().scrollbar_track_border,
|
||||
));
|
||||
|
||||
let fast_markers =
|
||||
self.collect_fast_scrollbar_markers(layout, &scrollbar_layout, cx);
|
||||
// Refresh slow scrollbar markers in the background. Below, we paint whatever markers have already been computed.
|
||||
self.refresh_slow_scrollbar_markers(layout, &scrollbar_layout, cx);
|
||||
|
||||
let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
|
||||
for marker in markers.iter().chain(&fast_markers) {
|
||||
let mut marker = marker.clone();
|
||||
marker.bounds.origin += hitbox.origin;
|
||||
cx.paint_quad(marker);
|
||||
}
|
||||
|
||||
cx.paint_quad(quad(
|
||||
thumb_bounds,
|
||||
Corners::default(),
|
||||
cx.theme().colors().scrollbar_thumb_background,
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: Pixels::ZERO,
|
||||
bottom: Pixels::ZERO,
|
||||
left: ScrollbarLayout::BORDER_WIDTH,
|
||||
},
|
||||
cx.theme().colors().scrollbar_thumb_border,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
cx.set_cursor_style(CursorStyle::Arrow, &hitbox);
|
||||
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
let hitbox = scrollbar_layout.hitbox.clone();
|
||||
move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture || !hitbox.is_hovered(cx) {
|
||||
|
||||
let hitbox = hitbox.clone();
|
||||
|
||||
let mut mouse_position = cx.mouse_position();
|
||||
move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
|
||||
|
||||
let y = event.position.y;
|
||||
if y < thumb_bounds.top() || thumb_bounds.bottom() < y {
|
||||
let center_row = ((y - hitbox.top()) / row_height).round() as u32;
|
||||
let top_row = center_row
|
||||
.saturating_sub((row_range.end - row_range.start) as u32 / 2);
|
||||
let mut position = editor.scroll_position(cx);
|
||||
position.y = top_row as f32;
|
||||
editor.set_scroll_position(position, cx);
|
||||
if event.pressed_button == Some(MouseButton::Left)
|
||||
&& editor.scroll_manager.is_dragging_scrollbar(Axis::Vertical)
|
||||
{
|
||||
let y = mouse_position.y;
|
||||
let new_y = event.position.y;
|
||||
if (hitbox.top()..hitbox.bottom()).contains(&y) {
|
||||
let mut position = editor.scroll_position(cx);
|
||||
position.y += (new_y - y) / text_unit_size;
|
||||
if position.y < 0.0 {
|
||||
position.y = 0.0;
|
||||
}
|
||||
editor.set_scroll_position(position, cx);
|
||||
}
|
||||
} else {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(
|
||||
Axis::Vertical,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.stop_propagation();
|
||||
});
|
||||
if hitbox.is_hovered(cx) {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
}
|
||||
mouse_position = event.position;
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
if self
|
||||
.editor
|
||||
.read(cx)
|
||||
.scroll_manager
|
||||
.is_dragging_scrollbar(Axis::Vertical)
|
||||
{
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
move |_: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(
|
||||
Axis::Vertical,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
cx.stop_propagation();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
|
||||
move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture || !hitbox.is_hovered(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(
|
||||
Axis::Vertical,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
|
||||
let y = event.position.y;
|
||||
if y < thumb_bounds.top() || thumb_bounds.bottom() < y {
|
||||
let center_row =
|
||||
((y - hitbox.top()) / text_unit_size).round() as u32;
|
||||
let top_row = center_row.saturating_sub(
|
||||
(visible_range.end - visible_range.start) as u32 / 2,
|
||||
);
|
||||
let mut position = editor.scroll_position(cx);
|
||||
position.y = top_row as f32;
|
||||
editor.set_scroll_position(position, cx);
|
||||
} else {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
|
||||
cx.stop_propagation();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5423,6 +5709,8 @@ impl Element for EditorElement {
|
|||
.unwrap()
|
||||
.width;
|
||||
|
||||
let letter_size = size(em_width, line_height);
|
||||
|
||||
let gutter_dimensions = snapshot.gutter_dimensions(
|
||||
font_id,
|
||||
font_size,
|
||||
|
@ -5433,15 +5721,7 @@ impl Element for EditorElement {
|
|||
);
|
||||
let text_width = bounds.size.width - gutter_dimensions.width;
|
||||
|
||||
let right_margin = if snapshot.mode == EditorMode::Full {
|
||||
EditorElement::SCROLLBAR_WIDTH
|
||||
} else {
|
||||
px(0.)
|
||||
};
|
||||
let overscroll = size(em_width + right_margin, px(0.));
|
||||
|
||||
let editor_width =
|
||||
text_width - gutter_dimensions.margin - overscroll.width - em_width;
|
||||
let editor_width = text_width - gutter_dimensions.margin - em_width;
|
||||
|
||||
snapshot = self.editor.update(cx, |editor, cx| {
|
||||
editor.last_bounds = Some(bounds);
|
||||
|
@ -5492,8 +5772,15 @@ impl Element for EditorElement {
|
|||
let content_origin =
|
||||
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
|
||||
|
||||
let height_in_lines = bounds.size.height / line_height;
|
||||
let scrollbar_bounds =
|
||||
Bounds::from_corners(content_origin, bounds.bottom_right());
|
||||
|
||||
let height_in_lines = scrollbar_bounds.size.height / line_height;
|
||||
|
||||
// NOTE: The max row number in the current file, minus one
|
||||
let max_row = snapshot.max_point().row().as_f32();
|
||||
|
||||
// NOTE: The max scroll position for the top of the window
|
||||
let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
|
||||
(max_row - height_in_lines + 1.).max(0.)
|
||||
} else {
|
||||
|
@ -5508,6 +5795,7 @@ impl Element for EditorElement {
|
|||
}
|
||||
};
|
||||
|
||||
// TODO: Autoscrolling for both axes
|
||||
let mut autoscroll_request = None;
|
||||
let mut autoscroll_containing_element = false;
|
||||
let mut autoscroll_horizontally = false;
|
||||
|
@ -5515,6 +5803,7 @@ impl Element for EditorElement {
|
|||
autoscroll_request = editor.autoscroll_request();
|
||||
autoscroll_containing_element =
|
||||
autoscroll_request.is_some() || editor.has_pending_selection();
|
||||
// TODO: Is this horizontal or vertical?!
|
||||
autoscroll_horizontally =
|
||||
editor.autoscroll_vertically(bounds, line_height, max_scroll_top, cx);
|
||||
snapshot = editor.snapshot(cx);
|
||||
|
@ -5648,8 +5937,18 @@ impl Element for EditorElement {
|
|||
cx,
|
||||
)
|
||||
.width;
|
||||
let mut scroll_width =
|
||||
longest_line_width.max(max_visible_line_width) + overscroll.width;
|
||||
|
||||
let scrollbar_range_data = ScrollbarRangeData::new(
|
||||
scrollbar_bounds,
|
||||
letter_size,
|
||||
&snapshot,
|
||||
longest_line_width,
|
||||
&style,
|
||||
cx,
|
||||
);
|
||||
|
||||
let scroll_range_bounds = scrollbar_range_data.scroll_range;
|
||||
let mut scroll_width = scroll_range_bounds.size.width;
|
||||
|
||||
let blocks = cx.with_element_namespace("blocks", |cx| {
|
||||
self.render_blocks(
|
||||
|
@ -5685,7 +5984,7 @@ impl Element for EditorElement {
|
|||
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
|
||||
|
||||
let scroll_max = point(
|
||||
((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
|
||||
((scroll_width - scrollbar_bounds.size.width) / em_width).max(0.0),
|
||||
max_row.as_f32(),
|
||||
);
|
||||
|
||||
|
@ -5770,7 +6069,7 @@ impl Element for EditorElement {
|
|||
);
|
||||
|
||||
let scroll_max = point(
|
||||
((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
|
||||
((scroll_width - scrollbar_bounds.size.width) / em_width).max(0.0),
|
||||
max_scroll_top,
|
||||
);
|
||||
|
||||
|
@ -5839,11 +6138,10 @@ impl Element for EditorElement {
|
|||
cx,
|
||||
);
|
||||
|
||||
let scrollbar_layout = self.layout_scrollbar(
|
||||
let scrollbars_layout = self.layout_scrollbars(
|
||||
&snapshot,
|
||||
bounds,
|
||||
scrollbar_range_data,
|
||||
scroll_position,
|
||||
height_in_lines,
|
||||
non_visible_cursors,
|
||||
cx,
|
||||
);
|
||||
|
@ -6075,7 +6373,7 @@ impl Element for EditorElement {
|
|||
gutter_dimensions,
|
||||
display_hunks,
|
||||
content_origin,
|
||||
scrollbar_layout,
|
||||
scrollbars_layout,
|
||||
active_rows,
|
||||
highlighted_rows,
|
||||
highlighted_ranges,
|
||||
|
@ -6178,7 +6476,7 @@ impl Element for EditorElement {
|
|||
});
|
||||
}
|
||||
|
||||
self.paint_scrollbar(layout, cx);
|
||||
self.paint_scrollbars(layout, cx);
|
||||
self.paint_inline_completion_popover(layout, cx);
|
||||
self.paint_mouse_context_menu(layout, cx);
|
||||
});
|
||||
|
@ -6197,6 +6495,52 @@ pub(super) fn gutter_bounds(
|
|||
}
|
||||
}
|
||||
|
||||
struct ScrollbarRangeData {
|
||||
scrollbar_bounds: Bounds<Pixels>,
|
||||
scroll_range: Bounds<Pixels>,
|
||||
letter_size: Size<Pixels>,
|
||||
}
|
||||
|
||||
impl ScrollbarRangeData {
|
||||
pub fn new(
|
||||
scrollbar_bounds: Bounds<Pixels>,
|
||||
letter_size: Size<Pixels>,
|
||||
snapshot: &EditorSnapshot,
|
||||
longest_line_width: Pixels,
|
||||
style: &EditorStyle,
|
||||
cx: &WindowContext,
|
||||
) -> ScrollbarRangeData {
|
||||
// TODO: Simplify this function down, it requires a lot of parameters
|
||||
let max_row = snapshot.max_point().row();
|
||||
let text_bounds_size = size(longest_line_width, max_row.0 as f32 * letter_size.height);
|
||||
|
||||
let scrollbar_width = style.scrollbar_width;
|
||||
|
||||
let settings = EditorSettings::get_global(cx);
|
||||
let scroll_beyond_last_line: Pixels = match settings.scroll_beyond_last_line {
|
||||
ScrollBeyondLastLine::OnePage => px(scrollbar_bounds.size.height / letter_size.height),
|
||||
ScrollBeyondLastLine::Off => px(1.),
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => px(1.0 + settings.vertical_scroll_margin),
|
||||
};
|
||||
|
||||
let overscroll = size(
|
||||
scrollbar_width + (letter_size.width / 2.0),
|
||||
letter_size.height * scroll_beyond_last_line,
|
||||
);
|
||||
|
||||
let scroll_range = Bounds {
|
||||
origin: scrollbar_bounds.origin,
|
||||
size: text_bounds_size + overscroll,
|
||||
};
|
||||
|
||||
ScrollbarRangeData {
|
||||
scrollbar_bounds,
|
||||
scroll_range,
|
||||
letter_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for EditorElement {
|
||||
type Element = Self;
|
||||
|
||||
|
@ -6212,7 +6556,7 @@ pub struct EditorLayout {
|
|||
gutter_hitbox: Hitbox,
|
||||
gutter_dimensions: GutterDimensions,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
scrollbar_layout: Option<ScrollbarLayout>,
|
||||
scrollbars_layout: AxisPair<Option<ScrollbarLayout>>,
|
||||
mode: EditorMode,
|
||||
wrap_guides: SmallVec<[(Pixels, bool); 2]>,
|
||||
indent_guides: Option<Vec<IndentGuideLayout>>,
|
||||
|
@ -6256,29 +6600,43 @@ struct ColoredRange<T> {
|
|||
#[derive(Clone)]
|
||||
struct ScrollbarLayout {
|
||||
hitbox: Hitbox,
|
||||
visible_row_range: Range<f32>,
|
||||
visible_range: Range<f32>,
|
||||
visible: bool,
|
||||
row_height: Pixels,
|
||||
thumb_height: Pixels,
|
||||
text_unit_size: Pixels,
|
||||
thumb_size: Pixels,
|
||||
axis: Axis,
|
||||
}
|
||||
|
||||
impl ScrollbarLayout {
|
||||
const BORDER_WIDTH: Pixels = px(1.0);
|
||||
const LINE_MARKER_HEIGHT: Pixels = px(2.0);
|
||||
const MIN_MARKER_HEIGHT: Pixels = px(5.0);
|
||||
const MIN_THUMB_HEIGHT: Pixels = px(20.0);
|
||||
// const MIN_THUMB_HEIGHT: Pixels = px(20.0);
|
||||
|
||||
fn thumb_bounds(&self) -> Bounds<Pixels> {
|
||||
let thumb_top = self.y_for_row(self.visible_row_range.start);
|
||||
let thumb_bottom = thumb_top + self.thumb_height;
|
||||
Bounds::from_corners(
|
||||
point(self.hitbox.left(), thumb_top),
|
||||
point(self.hitbox.right(), thumb_bottom),
|
||||
)
|
||||
match self.axis {
|
||||
Axis::Vertical => {
|
||||
let thumb_top = self.y_for_row(self.visible_range.start);
|
||||
let thumb_bottom = thumb_top + self.thumb_size;
|
||||
Bounds::from_corners(
|
||||
point(self.hitbox.left(), thumb_top),
|
||||
point(self.hitbox.right(), thumb_bottom),
|
||||
)
|
||||
}
|
||||
Axis::Horizontal => {
|
||||
let thumb_left =
|
||||
self.hitbox.left() + self.visible_range.start * self.text_unit_size;
|
||||
let thumb_right = thumb_left + self.thumb_size;
|
||||
Bounds::from_corners(
|
||||
point(thumb_left, self.hitbox.top()),
|
||||
point(thumb_right, self.hitbox.bottom()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn y_for_row(&self, row: f32) -> Pixels {
|
||||
self.hitbox.top() + row * self.row_height
|
||||
self.hitbox.top() + row * self.text_unit_size
|
||||
}
|
||||
|
||||
fn marker_quads_for_ranges(
|
||||
|
@ -6314,13 +6672,16 @@ impl ScrollbarLayout {
|
|||
)
|
||||
};
|
||||
|
||||
let row_to_y = |row: DisplayRow| row.as_f32() * self.row_height;
|
||||
let row_to_y = |row: DisplayRow| row.as_f32() * self.text_unit_size;
|
||||
let mut pixel_ranges = row_ranges
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let start_y = row_to_y(range.start);
|
||||
let end_y = row_to_y(range.end)
|
||||
+ self.row_height.max(height_limit.min).min(height_limit.max);
|
||||
+ self
|
||||
.text_unit_size
|
||||
.max(height_limit.min)
|
||||
.min(height_limit.max);
|
||||
ColoredRange {
|
||||
start: start_y,
|
||||
end: end_y,
|
||||
|
|
|
@ -2,7 +2,7 @@ mod actions;
|
|||
pub(crate) mod autoscroll;
|
||||
pub(crate) mod scroll_amount;
|
||||
|
||||
use crate::editor_settings::ScrollBeyondLastLine;
|
||||
use crate::editor_settings::{ScrollBeyondLastLine, ScrollbarAxes};
|
||||
use crate::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
hover_popover::hide_hover,
|
||||
|
@ -11,7 +11,10 @@ use crate::{
|
|||
InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint,
|
||||
};
|
||||
pub use autoscroll::{Autoscroll, AutoscrollStrategy};
|
||||
use gpui::{point, px, AppContext, Entity, Global, Pixels, Task, ViewContext, WindowContext};
|
||||
use core::fmt::Debug;
|
||||
use gpui::{
|
||||
point, px, Along, AppContext, Axis, Entity, Global, Pixels, Task, ViewContext, WindowContext,
|
||||
};
|
||||
use language::{Bias, Point};
|
||||
pub use scroll_amount::ScrollAmount;
|
||||
use settings::Settings;
|
||||
|
@ -60,10 +63,53 @@ impl ScrollAnchor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Axis {
|
||||
Vertical,
|
||||
Horizontal,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AxisPair<T: Clone> {
|
||||
pub vertical: T,
|
||||
pub horizontal: T,
|
||||
}
|
||||
|
||||
pub fn axis_pair<T: Clone>(horizontal: T, vertical: T) -> AxisPair<T> {
|
||||
AxisPair {
|
||||
vertical,
|
||||
horizontal,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> AxisPair<T> {
|
||||
pub fn as_xy(&self) -> (&T, &T) {
|
||||
(&self.horizontal, &self.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Along for AxisPair<T> {
|
||||
type Unit = T;
|
||||
|
||||
fn along(&self, axis: gpui::Axis) -> Self::Unit {
|
||||
match axis {
|
||||
gpui::Axis::Horizontal => self.horizontal.clone(),
|
||||
gpui::Axis::Vertical => self.vertical.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_along(&self, axis: gpui::Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self {
|
||||
match axis {
|
||||
gpui::Axis::Horizontal => Self {
|
||||
horizontal: f(self.horizontal.clone()),
|
||||
vertical: self.vertical.clone(),
|
||||
},
|
||||
gpui::Axis::Vertical => Self {
|
||||
horizontal: self.horizontal.clone(),
|
||||
vertical: f(self.vertical.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScrollbarAxes> for AxisPair<bool> {
|
||||
fn from(value: ScrollbarAxes) -> Self {
|
||||
axis_pair(value.horizontal, value.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -136,7 +182,7 @@ pub struct ScrollManager {
|
|||
last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
|
||||
show_scrollbars: bool,
|
||||
hide_scrollbar_task: Option<Task<()>>,
|
||||
dragging_scrollbar: bool,
|
||||
dragging_scrollbar: AxisPair<bool>,
|
||||
visible_line_count: Option<f32>,
|
||||
forbid_vertical_scroll: bool,
|
||||
}
|
||||
|
@ -150,7 +196,7 @@ impl ScrollManager {
|
|||
autoscroll_request: None,
|
||||
show_scrollbars: true,
|
||||
hide_scrollbar_task: None,
|
||||
dragging_scrollbar: false,
|
||||
dragging_scrollbar: axis_pair(false, false),
|
||||
last_autoscroll: None,
|
||||
visible_line_count: None,
|
||||
forbid_vertical_scroll: false,
|
||||
|
@ -311,15 +357,18 @@ impl ScrollManager {
|
|||
self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
|
||||
}
|
||||
|
||||
pub fn is_dragging_scrollbar(&self) -> bool {
|
||||
self.dragging_scrollbar
|
||||
pub fn is_dragging_scrollbar(&self, axis: Axis) -> bool {
|
||||
self.dragging_scrollbar.along(axis)
|
||||
}
|
||||
|
||||
pub fn set_is_dragging_scrollbar(&mut self, dragging: bool, cx: &mut ViewContext<Editor>) {
|
||||
if dragging != self.dragging_scrollbar {
|
||||
self.dragging_scrollbar = dragging;
|
||||
cx.notify();
|
||||
}
|
||||
pub fn set_is_dragging_scrollbar(
|
||||
&mut self,
|
||||
axis: Axis,
|
||||
dragging: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.dragging_scrollbar = self.dragging_scrollbar.apply_along(axis, |_| dragging);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
|
||||
|
|
|
@ -5,18 +5,16 @@ use crate::{
|
|||
};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
actions::SelectAll,
|
||||
items::active_match_index,
|
||||
scroll::{Autoscroll, Axis},
|
||||
Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MultiBuffer,
|
||||
MAX_TAB_TITLE_LEN,
|
||||
actions::SelectAll, items::active_match_index, scroll::Autoscroll, Anchor, Editor,
|
||||
EditorElement, EditorEvent, EditorSettings, EditorStyle, MultiBuffer, MAX_TAB_TITLE_LEN,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, div, Action, AnyElement, AnyView, AppContext, Context as _, EntityId, EventEmitter,
|
||||
FocusHandle, FocusableView, Global, Hsla, InteractiveElement, IntoElement, KeyContext, Model,
|
||||
ModelContext, ParentElement, Point, Render, SharedString, Styled, Subscription, Task,
|
||||
TextStyle, UpdateGlobal, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext,
|
||||
actions, div, Action, AnyElement, AnyView, AppContext, Axis, Context as _, EntityId,
|
||||
EventEmitter, FocusHandle, FocusableView, Global, Hsla, InteractiveElement, IntoElement,
|
||||
KeyContext, Model, ModelContext, ParentElement, Point, Render, SharedString, Styled,
|
||||
Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, VisualContext, WeakModel,
|
||||
WeakView, WindowContext,
|
||||
};
|
||||
use language::Buffer;
|
||||
use menu::Confirm;
|
||||
|
|
|
@ -534,7 +534,11 @@ List of `string` values
|
|||
"git_diff": true,
|
||||
"search_results": true,
|
||||
"selected_symbol": true,
|
||||
"diagnostics": true
|
||||
"diagnostics": true,
|
||||
"axes": {
|
||||
"horizontal": true,
|
||||
"vertical": true,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
|
@ -628,6 +632,41 @@ List of `string` values
|
|||
|
||||
`boolean` values
|
||||
|
||||
### Axes
|
||||
|
||||
- Description: Forcefully enable or disable the scrollbar for each axis
|
||||
- Setting: `axes`
|
||||
- Default:
|
||||
|
||||
```json
|
||||
"scrollbar": {
|
||||
"axes": {
|
||||
"horizontal": true,
|
||||
"vertical": true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### Horizontal
|
||||
|
||||
- Description: When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
- Setting: `horizontal`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
#### Vertical
|
||||
|
||||
- Description: When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
- Setting: `vertical`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
## Editor Tab Bar
|
||||
|
||||
- Description: Settings related to the editor's tab bar.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue