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.
|
// Whether to show selected symbol occurrences in the scrollbar.
|
||||||
"selected_symbol": true,
|
"selected_symbol": true,
|
||||||
// Whether to show diagnostic indicators in the scrollbar.
|
// 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.
|
// Enable middle-click paste on Linux.
|
||||||
"middle_click_paste": true,
|
"middle_click_paste": true,
|
||||||
|
@ -304,6 +311,8 @@
|
||||||
"vertical_scroll_margin": 3,
|
"vertical_scroll_margin": 3,
|
||||||
// Whether to scroll when clicking near the edge of the visible text area.
|
// Whether to scroll when clicking near the edge of the visible text area.
|
||||||
"autoscroll_on_clicks": false,
|
"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
|
// Scroll sensitivity multiplier. This multiplier is applied
|
||||||
// to both the horizontal and vertical delta values while scrolling.
|
// to both the horizontal and vertical delta values while scrolling.
|
||||||
"scroll_sensitivity": 1.0,
|
"scroll_sensitivity": 1.0,
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub struct EditorSettings {
|
||||||
pub scroll_beyond_last_line: ScrollBeyondLastLine,
|
pub scroll_beyond_last_line: ScrollBeyondLastLine,
|
||||||
pub vertical_scroll_margin: f32,
|
pub vertical_scroll_margin: f32,
|
||||||
pub autoscroll_on_clicks: bool,
|
pub autoscroll_on_clicks: bool,
|
||||||
|
pub horizontal_scroll_margin: f32,
|
||||||
pub scroll_sensitivity: f32,
|
pub scroll_sensitivity: f32,
|
||||||
pub relative_line_numbers: bool,
|
pub relative_line_numbers: bool,
|
||||||
pub seed_search_query_from_cursor: SeedQuerySetting,
|
pub seed_search_query_from_cursor: SeedQuerySetting,
|
||||||
|
@ -105,6 +106,7 @@ pub struct Scrollbar {
|
||||||
pub search_results: bool,
|
pub search_results: bool,
|
||||||
pub diagnostics: bool,
|
pub diagnostics: bool,
|
||||||
pub cursors: bool,
|
pub cursors: bool,
|
||||||
|
pub axes: ScrollbarAxes,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
|
@ -132,6 +134,21 @@ pub enum ShowScrollbar {
|
||||||
Never,
|
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
|
/// The key to use for adding multiple cursors
|
||||||
///
|
///
|
||||||
/// Default: alt
|
/// Default: alt
|
||||||
|
@ -219,6 +236,10 @@ pub struct EditorSettingsContent {
|
||||||
///
|
///
|
||||||
/// Default: false
|
/// Default: false
|
||||||
pub autoscroll_on_clicks: Option<bool>,
|
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
|
/// Scroll sensitivity multiplier. This multiplier is applied
|
||||||
/// to both the horizontal and vertical delta values while scrolling.
|
/// to both the horizontal and vertical delta values while scrolling.
|
||||||
///
|
///
|
||||||
|
@ -328,6 +349,22 @@ pub struct ScrollbarContent {
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub cursors: Option<bool>,
|
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
|
/// Gutter related settings
|
||||||
|
|
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
hunk_status,
|
hunk_status,
|
||||||
items::BufferSearchHighlights,
|
items::BufferSearchHighlights,
|
||||||
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
||||||
scroll::scroll_amount::ScrollAmount,
|
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
|
||||||
BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
|
BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
|
||||||
DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
|
DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
|
||||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||||
|
@ -31,7 +31,7 @@ use file_icons::FileIcons;
|
||||||
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
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,
|
ContentMask, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler,
|
||||||
Entity, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
Entity, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
||||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
||||||
|
@ -154,7 +154,7 @@ pub struct EditorElement {
|
||||||
type DisplayRowDelta = u32;
|
type DisplayRowDelta = u32;
|
||||||
|
|
||||||
impl EditorElement {
|
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 {
|
pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -714,9 +714,24 @@ impl EditorElement {
|
||||||
scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
|
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);
|
// We need horizontal width of text
|
||||||
let left = text_bounds.origin.x + horizontal_margin;
|
let style = editor.style.clone().unwrap_or_default();
|
||||||
let right = text_bounds.top_right().x - horizontal_margin;
|
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 {
|
if event.position.x < left {
|
||||||
scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
|
scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
|
||||||
}
|
}
|
||||||
|
@ -1161,15 +1176,20 @@ impl EditorElement {
|
||||||
cursor_layouts
|
cursor_layouts
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_scrollbar(
|
fn layout_scrollbars(
|
||||||
&self,
|
&self,
|
||||||
snapshot: &EditorSnapshot,
|
snapshot: &EditorSnapshot,
|
||||||
bounds: Bounds<Pixels>,
|
scrollbar_range_data: ScrollbarRangeData,
|
||||||
scroll_position: gpui::Point<f32>,
|
scroll_position: gpui::Point<f32>,
|
||||||
rows_per_page: f32,
|
|
||||||
non_visible_cursors: bool,
|
non_visible_cursors: bool,
|
||||||
cx: &mut WindowContext,
|
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 scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
|
||||||
let show_scrollbars = match scrollbar_settings.show {
|
let show_scrollbars = match scrollbar_settings.show {
|
||||||
ShowScrollbar::Auto => {
|
ShowScrollbar::Auto => {
|
||||||
|
@ -1197,45 +1217,139 @@ impl EditorElement {
|
||||||
ShowScrollbar::Always => true,
|
ShowScrollbar::Always => true,
|
||||||
ShowScrollbar::Never => false,
|
ShowScrollbar::Never => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let axes: AxisPair<bool> = scrollbar_settings.axes.into();
|
||||||
|
|
||||||
if snapshot.mode != EditorMode::Full {
|
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,
|
// If a drag took place after we started dragging the scrollbar,
|
||||||
// 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.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(
|
let text_bounds = scrollbar_range_data.scrollbar_bounds;
|
||||||
point(self.scrollbar_left(&bounds), bounds.origin.y),
|
|
||||||
point(bounds.bottom_right().x, bounds.bottom_left().y),
|
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_range_size = scrollbar_range_data.scroll_range.size;
|
||||||
let scroll_beyond_last_line: f32 = match settings.scroll_beyond_last_line {
|
let total_text_units = axis_pair(
|
||||||
ScrollBeyondLastLine::OnePage => rows_per_page,
|
Some(scroll_range_size.width / letter_size.width),
|
||||||
ScrollBeyondLastLine::Off => 1.0,
|
Some(scroll_range_size.height / letter_size.height),
|
||||||
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.);
|
|
||||||
|
|
||||||
Some(ScrollbarLayout {
|
let thumb_size = axis_pair(
|
||||||
hitbox: cx.insert_hitbox(track_bounds, false),
|
total_text_units
|
||||||
visible_row_range,
|
.horizontal
|
||||||
row_height,
|
.zip(track_bounds.horizontal)
|
||||||
visible: show_scrollbars,
|
.map(|(total_text_units_x, track_bounds_x)| {
|
||||||
thumb_height,
|
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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
@ -3419,10 +3533,13 @@ impl EditorElement {
|
||||||
+ layout.position_map.em_width / 2.)
|
+ layout.position_map.em_width / 2.)
|
||||||
- scroll_left;
|
- scroll_left;
|
||||||
|
|
||||||
let show_scrollbars = layout
|
let show_scrollbars = {
|
||||||
.scrollbar_layout
|
let (scrollbar_x, scrollbar_y) = &layout.scrollbars_layout.as_xy();
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |scrollbar| scrollbar.visible);
|
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
|
if x < layout.text_hitbox.origin.x
|
||||||
|| (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds))
|
|| (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) {
|
fn paint_scrollbars(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||||
let Some(scrollbar_layout) = layout.scrollbar_layout.as_ref() else {
|
let (scrollbar_x, scrollbar_y) = layout.scrollbars_layout.as_xy();
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let thumb_bounds = scrollbar_layout.thumb_bounds();
|
if let Some(scrollbar_layout) = scrollbar_x {
|
||||||
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();
|
|
||||||
let hitbox = scrollbar_layout.hitbox.clone();
|
let hitbox = scrollbar_layout.hitbox.clone();
|
||||||
let mut mouse_position = cx.mouse_position();
|
let text_unit_size = scrollbar_layout.text_unit_size;
|
||||||
move |event: &MouseMoveEvent, phase, cx| {
|
let visible_range = scrollbar_layout.visible_range.clone();
|
||||||
if phase == DispatchPhase::Capture {
|
let thumb_bounds = scrollbar_layout.thumb_bounds();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
if scrollbar_layout.visible {
|
||||||
if event.pressed_button == Some(MouseButton::Left)
|
cx.paint_layer(hitbox.bounds, |cx| {
|
||||||
&& editor.scroll_manager.is_dragging_scrollbar()
|
cx.paint_quad(quad(
|
||||||
{
|
hitbox.bounds,
|
||||||
let y = mouse_position.y;
|
Corners::default(),
|
||||||
let new_y = event.position.y;
|
cx.theme().colors().scrollbar_track_background,
|
||||||
if (hitbox.top()..hitbox.bottom()).contains(&y) {
|
Edges {
|
||||||
let mut position = editor.scroll_position(cx);
|
top: Pixels::ZERO,
|
||||||
position.y += (new_y - y) / row_height;
|
right: Pixels::ZERO,
|
||||||
if position.y < 0.0 {
|
bottom: Pixels::ZERO,
|
||||||
position.y = 0.0;
|
left: Pixels::ZERO,
|
||||||
}
|
},
|
||||||
editor.set_scroll_position(position, cx);
|
cx.theme().colors().scrollbar_track_border,
|
||||||
}
|
));
|
||||||
|
|
||||||
cx.stop_propagation();
|
cx.paint_quad(quad(
|
||||||
} else {
|
thumb_bounds,
|
||||||
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
Corners::default(),
|
||||||
if hitbox.is_hovered(cx) {
|
cx.theme().colors().scrollbar_thumb_background,
|
||||||
editor.scroll_manager.show_scrollbar(cx);
|
Edges {
|
||||||
}
|
top: Pixels::ZERO,
|
||||||
}
|
right: Pixels::ZERO,
|
||||||
mouse_position = event.position;
|
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({
|
cx.on_mouse_event({
|
||||||
let editor = self.editor.clone();
|
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 {
|
if phase == DispatchPhase::Capture {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
if event.pressed_button == Some(MouseButton::Left)
|
||||||
cx.stop_propagation();
|
&& 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({
|
cx.on_mouse_event({
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let hitbox = scrollbar_layout.hitbox.clone();
|
|
||||||
move |event: &MouseDownEvent, phase, cx| {
|
let hitbox = hitbox.clone();
|
||||||
if phase == DispatchPhase::Capture || !hitbox.is_hovered(cx) {
|
|
||||||
|
let mut mouse_position = cx.mouse_position();
|
||||||
|
move |event: &MouseMoveEvent, phase, cx| {
|
||||||
|
if phase == DispatchPhase::Capture {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
|
if event.pressed_button == Some(MouseButton::Left)
|
||||||
|
&& editor.scroll_manager.is_dragging_scrollbar(Axis::Vertical)
|
||||||
let y = event.position.y;
|
{
|
||||||
if y < thumb_bounds.top() || thumb_bounds.bottom() < y {
|
let y = mouse_position.y;
|
||||||
let center_row = ((y - hitbox.top()) / row_height).round() as u32;
|
let new_y = event.position.y;
|
||||||
let top_row = center_row
|
if (hitbox.top()..hitbox.bottom()).contains(&y) {
|
||||||
.saturating_sub((row_range.end - row_range.start) as u32 / 2);
|
let mut position = editor.scroll_position(cx);
|
||||||
let mut position = editor.scroll_position(cx);
|
position.y += (new_y - y) / text_unit_size;
|
||||||
position.y = top_row as f32;
|
if position.y < 0.0 {
|
||||||
editor.set_scroll_position(position, cx);
|
position.y = 0.0;
|
||||||
|
}
|
||||||
|
editor.set_scroll_position(position, cx);
|
||||||
|
}
|
||||||
} else {
|
} 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()
|
.unwrap()
|
||||||
.width;
|
.width;
|
||||||
|
|
||||||
|
let letter_size = size(em_width, line_height);
|
||||||
|
|
||||||
let gutter_dimensions = snapshot.gutter_dimensions(
|
let gutter_dimensions = snapshot.gutter_dimensions(
|
||||||
font_id,
|
font_id,
|
||||||
font_size,
|
font_size,
|
||||||
|
@ -5433,15 +5721,7 @@ impl Element for EditorElement {
|
||||||
);
|
);
|
||||||
let text_width = bounds.size.width - gutter_dimensions.width;
|
let text_width = bounds.size.width - gutter_dimensions.width;
|
||||||
|
|
||||||
let right_margin = if snapshot.mode == EditorMode::Full {
|
let editor_width = text_width - gutter_dimensions.margin - em_width;
|
||||||
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;
|
|
||||||
|
|
||||||
snapshot = self.editor.update(cx, |editor, cx| {
|
snapshot = self.editor.update(cx, |editor, cx| {
|
||||||
editor.last_bounds = Some(bounds);
|
editor.last_bounds = Some(bounds);
|
||||||
|
@ -5492,8 +5772,15 @@ impl Element for EditorElement {
|
||||||
let content_origin =
|
let content_origin =
|
||||||
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
|
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();
|
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 { .. }) {
|
let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
|
||||||
(max_row - height_in_lines + 1.).max(0.)
|
(max_row - height_in_lines + 1.).max(0.)
|
||||||
} else {
|
} else {
|
||||||
|
@ -5508,6 +5795,7 @@ impl Element for EditorElement {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Autoscrolling for both axes
|
||||||
let mut autoscroll_request = None;
|
let mut autoscroll_request = None;
|
||||||
let mut autoscroll_containing_element = false;
|
let mut autoscroll_containing_element = false;
|
||||||
let mut autoscroll_horizontally = false;
|
let mut autoscroll_horizontally = false;
|
||||||
|
@ -5515,6 +5803,7 @@ impl Element for EditorElement {
|
||||||
autoscroll_request = editor.autoscroll_request();
|
autoscroll_request = editor.autoscroll_request();
|
||||||
autoscroll_containing_element =
|
autoscroll_containing_element =
|
||||||
autoscroll_request.is_some() || editor.has_pending_selection();
|
autoscroll_request.is_some() || editor.has_pending_selection();
|
||||||
|
// TODO: Is this horizontal or vertical?!
|
||||||
autoscroll_horizontally =
|
autoscroll_horizontally =
|
||||||
editor.autoscroll_vertically(bounds, line_height, max_scroll_top, cx);
|
editor.autoscroll_vertically(bounds, line_height, max_scroll_top, cx);
|
||||||
snapshot = editor.snapshot(cx);
|
snapshot = editor.snapshot(cx);
|
||||||
|
@ -5648,8 +5937,18 @@ impl Element for EditorElement {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.width;
|
.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| {
|
let blocks = cx.with_element_namespace("blocks", |cx| {
|
||||||
self.render_blocks(
|
self.render_blocks(
|
||||||
|
@ -5685,7 +5984,7 @@ impl Element for EditorElement {
|
||||||
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
|
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
|
||||||
|
|
||||||
let scroll_max = point(
|
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(),
|
max_row.as_f32(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -5770,7 +6069,7 @@ impl Element for EditorElement {
|
||||||
);
|
);
|
||||||
|
|
||||||
let scroll_max = point(
|
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,
|
max_scroll_top,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -5839,11 +6138,10 @@ impl Element for EditorElement {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
let scrollbar_layout = self.layout_scrollbar(
|
let scrollbars_layout = self.layout_scrollbars(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
bounds,
|
scrollbar_range_data,
|
||||||
scroll_position,
|
scroll_position,
|
||||||
height_in_lines,
|
|
||||||
non_visible_cursors,
|
non_visible_cursors,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -6075,7 +6373,7 @@ impl Element for EditorElement {
|
||||||
gutter_dimensions,
|
gutter_dimensions,
|
||||||
display_hunks,
|
display_hunks,
|
||||||
content_origin,
|
content_origin,
|
||||||
scrollbar_layout,
|
scrollbars_layout,
|
||||||
active_rows,
|
active_rows,
|
||||||
highlighted_rows,
|
highlighted_rows,
|
||||||
highlighted_ranges,
|
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_inline_completion_popover(layout, cx);
|
||||||
self.paint_mouse_context_menu(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 {
|
impl IntoElement for EditorElement {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
|
@ -6212,7 +6556,7 @@ pub struct EditorLayout {
|
||||||
gutter_hitbox: Hitbox,
|
gutter_hitbox: Hitbox,
|
||||||
gutter_dimensions: GutterDimensions,
|
gutter_dimensions: GutterDimensions,
|
||||||
content_origin: gpui::Point<Pixels>,
|
content_origin: gpui::Point<Pixels>,
|
||||||
scrollbar_layout: Option<ScrollbarLayout>,
|
scrollbars_layout: AxisPair<Option<ScrollbarLayout>>,
|
||||||
mode: EditorMode,
|
mode: EditorMode,
|
||||||
wrap_guides: SmallVec<[(Pixels, bool); 2]>,
|
wrap_guides: SmallVec<[(Pixels, bool); 2]>,
|
||||||
indent_guides: Option<Vec<IndentGuideLayout>>,
|
indent_guides: Option<Vec<IndentGuideLayout>>,
|
||||||
|
@ -6256,29 +6600,43 @@ struct ColoredRange<T> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ScrollbarLayout {
|
struct ScrollbarLayout {
|
||||||
hitbox: Hitbox,
|
hitbox: Hitbox,
|
||||||
visible_row_range: Range<f32>,
|
visible_range: Range<f32>,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
row_height: Pixels,
|
text_unit_size: Pixels,
|
||||||
thumb_height: Pixels,
|
thumb_size: Pixels,
|
||||||
|
axis: Axis,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollbarLayout {
|
impl ScrollbarLayout {
|
||||||
const BORDER_WIDTH: Pixels = px(1.0);
|
const BORDER_WIDTH: Pixels = px(1.0);
|
||||||
const LINE_MARKER_HEIGHT: Pixels = px(2.0);
|
const LINE_MARKER_HEIGHT: Pixels = px(2.0);
|
||||||
const MIN_MARKER_HEIGHT: Pixels = px(5.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> {
|
fn thumb_bounds(&self) -> Bounds<Pixels> {
|
||||||
let thumb_top = self.y_for_row(self.visible_row_range.start);
|
match self.axis {
|
||||||
let thumb_bottom = thumb_top + self.thumb_height;
|
Axis::Vertical => {
|
||||||
Bounds::from_corners(
|
let thumb_top = self.y_for_row(self.visible_range.start);
|
||||||
point(self.hitbox.left(), thumb_top),
|
let thumb_bottom = thumb_top + self.thumb_size;
|
||||||
point(self.hitbox.right(), thumb_bottom),
|
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 {
|
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(
|
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
|
let mut pixel_ranges = row_ranges
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|range| {
|
.map(|range| {
|
||||||
let start_y = row_to_y(range.start);
|
let start_y = row_to_y(range.start);
|
||||||
let end_y = row_to_y(range.end)
|
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 {
|
ColoredRange {
|
||||||
start: start_y,
|
start: start_y,
|
||||||
end: end_y,
|
end: end_y,
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod actions;
|
||||||
pub(crate) mod autoscroll;
|
pub(crate) mod autoscroll;
|
||||||
pub(crate) mod scroll_amount;
|
pub(crate) mod scroll_amount;
|
||||||
|
|
||||||
use crate::editor_settings::ScrollBeyondLastLine;
|
use crate::editor_settings::{ScrollBeyondLastLine, ScrollbarAxes};
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||||
hover_popover::hide_hover,
|
hover_popover::hide_hover,
|
||||||
|
@ -11,7 +11,10 @@ use crate::{
|
||||||
InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint,
|
InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint,
|
||||||
};
|
};
|
||||||
pub use autoscroll::{Autoscroll, AutoscrollStrategy};
|
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};
|
use language::{Bias, Point};
|
||||||
pub use scroll_amount::ScrollAmount;
|
pub use scroll_amount::ScrollAmount;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -60,10 +63,53 @@ impl ScrollAnchor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Axis {
|
pub struct AxisPair<T: Clone> {
|
||||||
Vertical,
|
pub vertical: T,
|
||||||
Horizontal,
|
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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -136,7 +182,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: bool,
|
dragging_scrollbar: AxisPair<bool>,
|
||||||
visible_line_count: Option<f32>,
|
visible_line_count: Option<f32>,
|
||||||
forbid_vertical_scroll: bool,
|
forbid_vertical_scroll: bool,
|
||||||
}
|
}
|
||||||
|
@ -150,7 +196,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: false,
|
dragging_scrollbar: axis_pair(false, false),
|
||||||
last_autoscroll: None,
|
last_autoscroll: None,
|
||||||
visible_line_count: None,
|
visible_line_count: None,
|
||||||
forbid_vertical_scroll: false,
|
forbid_vertical_scroll: false,
|
||||||
|
@ -311,15 +357,18 @@ impl ScrollManager {
|
||||||
self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
|
self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_dragging_scrollbar(&self) -> bool {
|
pub fn is_dragging_scrollbar(&self, axis: Axis) -> bool {
|
||||||
self.dragging_scrollbar
|
self.dragging_scrollbar.along(axis)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_is_dragging_scrollbar(&mut self, dragging: bool, cx: &mut ViewContext<Editor>) {
|
pub fn set_is_dragging_scrollbar(
|
||||||
if dragging != self.dragging_scrollbar {
|
&mut self,
|
||||||
self.dragging_scrollbar = dragging;
|
axis: Axis,
|
||||||
cx.notify();
|
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 {
|
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
|
||||||
|
|
|
@ -5,18 +5,16 @@ use crate::{
|
||||||
};
|
};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
actions::SelectAll,
|
actions::SelectAll, items::active_match_index, scroll::Autoscroll, Anchor, Editor,
|
||||||
items::active_match_index,
|
EditorElement, EditorEvent, EditorSettings, EditorStyle, MultiBuffer, MAX_TAB_TITLE_LEN,
|
||||||
scroll::{Autoscroll, Axis},
|
|
||||||
Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MultiBuffer,
|
|
||||||
MAX_TAB_TITLE_LEN,
|
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, Action, AnyElement, AnyView, AppContext, Context as _, EntityId, EventEmitter,
|
actions, div, Action, AnyElement, AnyView, AppContext, Axis, Context as _, EntityId,
|
||||||
FocusHandle, FocusableView, Global, Hsla, InteractiveElement, IntoElement, KeyContext, Model,
|
EventEmitter, FocusHandle, FocusableView, Global, Hsla, InteractiveElement, IntoElement,
|
||||||
ModelContext, ParentElement, Point, Render, SharedString, Styled, Subscription, Task,
|
KeyContext, Model, ModelContext, ParentElement, Point, Render, SharedString, Styled,
|
||||||
TextStyle, UpdateGlobal, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext,
|
Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, VisualContext, WeakModel,
|
||||||
|
WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use menu::Confirm;
|
use menu::Confirm;
|
||||||
|
|
|
@ -534,7 +534,11 @@ List of `string` values
|
||||||
"git_diff": true,
|
"git_diff": true,
|
||||||
"search_results": true,
|
"search_results": true,
|
||||||
"selected_symbol": true,
|
"selected_symbol": true,
|
||||||
"diagnostics": true
|
"diagnostics": true,
|
||||||
|
"axes": {
|
||||||
|
"horizontal": true,
|
||||||
|
"vertical": true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -628,6 +632,41 @@ List of `string` values
|
||||||
|
|
||||||
`boolean` 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
|
## Editor Tab Bar
|
||||||
|
|
||||||
- Description: Settings related to the editor's tab bar.
|
- Description: Settings related to the editor's tab bar.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue