@@ -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.);
-
- Some(ScrollbarLayout {
- hitbox: cx.insert_hitbox(track_bounds, false),
- visible_row_range,
- row_height,
- visible: show_scrollbars,
- thumb_height,
- })
+ 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),
+ );
+
+ 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,
- ));
+ if let Some(scrollbar_layout) = scrollbar_x {
+ 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: Pixels::ZERO,
+ },
+ 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);
+ 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,
+ ));
+ })
+ }
- 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.set_cursor_style(CursorStyle::Arrow, &hitbox);
- 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.on_mouse_event({
+ let editor = self.editor.clone();
- cx.set_cursor_style(CursorStyle::Arrow, &scrollbar_layout.hitbox);
+ // there may be a way to avoid this clone
+ let hitbox = hitbox.clone();
- let row_height = scrollbar_layout.row_height;
- let row_range = scrollbar_layout.visible_row_range.clone();
+ let mut mouse_position = cx.mouse_position();
+ move |event: &MouseMoveEvent, phase, cx| {
+ if phase == DispatchPhase::Capture {
+ return;
+ }
- cx.on_mouse_event({
- let editor = self.editor.clone();
- let hitbox = scrollbar_layout.hitbox.clone();
- let mut mouse_position = cx.mouse_position();
- move |event: &MouseMoveEvent, phase, cx| {
- if phase == DispatchPhase::Capture {
- return;
- }
+ editor.update(cx, |editor, cx| {
+ 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);
+ }
- 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;
+ 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);
}
- editor.set_scroll_position(position, cx);
}
+ mouse_position = event.position;
+ })
+ }
+ });
- cx.stop_propagation();
- } else {
- editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
- if hitbox.is_hovered(cx) {
- editor.scroll_manager.show_scrollbar(cx);
+ 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();
+ });
}
- mouse_position = event.position;
- })
+ });
+ } 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);
- if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
cx.on_mouse_event({
let editor = self.editor.clone();
- move |_: &MouseUpEvent, phase, 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(false, cx);
- cx.stop_propagation();
- });
+ 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.set_is_dragging_scrollbar(
+ Axis::Vertical,
+ false,
+ cx,
+ );
+
+ if hitbox.is_hovered(cx) {
+ editor.scroll_manager.show_scrollbar(cx);
+ }
+ }
+ mouse_position = event.position;
+ })
}
});
- } else {
- 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) {
- return;
+
+ 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();
- 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);
- } else {
- editor.scroll_manager.show_scrollbar(cx);
+ move |event: &MouseDownEvent, phase, cx| {
+ if phase == DispatchPhase::Capture || !hitbox.is_hovered(cx) {
+ return;
}
- cx.stop_propagation();
- });
- }
- });
+ 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;