Handle clicking folded ranges

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2023-11-15 18:19:26 +01:00
parent 3ff8c78b58
commit 17b8e4a684
4 changed files with 374 additions and 375 deletions

View file

@ -18,11 +18,12 @@ use crate::{
use anyhow::Result;
use collections::{BTreeMap, HashMap};
use gpui::{
point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, BorrowWindow,
Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
ElementInputHandler, Entity, EntityId, Hsla, Line, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, ParentComponent, Pixels, ScrollWheelEvent, Size, Style, Styled, TextRun,
TextStyle, View, ViewContext, WindowContext,
div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
BorrowWindow, Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element,
ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveComponent, Line,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels,
ScrollWheelEvent, Size, StatefulInteractiveComponent, Style, Styled, TextRun, TextStyle, View,
ViewContext, WindowContext,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@ -615,21 +616,22 @@ impl EditorElement {
fn paint_text(
&mut self,
bounds: Bounds<Pixels>,
text_bounds: Bounds<Pixels>,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
) {
let scroll_position = layout.position_map.snapshot.scroll_position();
let start_row = layout.visible_display_row_range.start;
let scroll_top = scroll_position.y * layout.position_map.line_height;
let max_glyph_width = layout.position_map.em_width;
let scroll_left = scroll_position.x * max_glyph_width;
let content_origin = bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
let line_end_overshoot = 0.15 * layout.position_map.line_height;
let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
cx.with_content_mask(
Some(ContentMask {
bounds: text_bounds,
}),
|cx| {
// todo!("cursor region")
// cx.scene().push_cursor_region(CursorRegion {
// bounds,
@ -640,54 +642,77 @@ impl EditorElement {
// },
// });
// todo!("fold ranges")
// let fold_corner_radius =
// self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
// for (id, range, color) in layout.fold_ranges.iter() {
// self.paint_highlighted_range(
// range.clone(),
// *color,
// fold_corner_radius,
// fold_corner_radius * 2.,
// layout,
// content_origin,
// scroll_top,
// scroll_left,
// bounds,
// cx,
// );
let fold_corner_radius = 0.15 * layout.position_map.line_height;
cx.with_element_id(Some("folds"), |cx| {
let snapshot = &layout.position_map.snapshot;
for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) {
let fold_range = fold.range.clone();
let display_range = fold.range.start.to_display_point(&snapshot)
..fold.range.end.to_display_point(&snapshot);
debug_assert_eq!(display_range.start.row(), display_range.end.row());
let row = display_range.start.row();
// for bound in range_to_bounds(
// &range,
// content_origin,
// scroll_left,
// scroll_top,
// &layout.visible_display_row_range,
// line_end_overshoot,
// &layout.position_map,
// ) {
// cx.scene().push_cursor_region(CursorRegion {
// bounds: bound,
// style: CursorStyle::PointingHand,
// });
let line_layout = &layout.position_map.line_layouts
[(row - layout.visible_display_row_range.start) as usize]
.line;
let start_x = content_origin.x
+ line_layout.x_for_index(display_range.start.column() as usize)
- layout.position_map.scroll_position.x;
let start_y = content_origin.y
+ row as f32 * layout.position_map.line_height
- layout.position_map.scroll_position.y;
let end_x = content_origin.x
+ line_layout.x_for_index(display_range.end.column() as usize)
- layout.position_map.scroll_position.x;
// let display_row = range.start.row();
let fold_bounds = Bounds {
origin: point(start_x, start_y),
size: size(end_x - start_x, layout.position_map.line_height),
};
// let buffer_row = DisplayPoint::new(display_row, 0)
// .to_point(&layout.position_map.snapshot.display_snapshot)
// .row;
let fold_background = cx.with_z_index(1, |cx| {
div()
.id(fold.id)
.size_full()
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.on_click(move |editor: &mut Editor, _, cx| {
editor.unfold_ranges(
[fold_range.start..fold_range.end],
true,
false,
cx,
);
cx.stop_propagation();
})
.draw(
fold_bounds.origin,
fold_bounds.size,
editor,
cx,
|fold_element_state, cx| {
if fold_element_state.is_active() {
gpui::blue()
} else if fold_bounds.contains_point(&cx.mouse_position()) {
gpui::black()
} else {
gpui::red()
}
},
)
});
// let view_id = cx.view_id();
// cx.scene().push_mouse_region(
// MouseRegion::new::<FoldMarkers>(view_id, *id as usize, bound)
// .on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| {
// editor.unfold_at(&UnfoldAt { buffer_row }, cx)
// })
// .with_notify_on_hover(true)
// .with_notify_on_click(true),
// )
// }
// }
self.paint_highlighted_range(
display_range.clone(),
fold_background,
fold_corner_radius,
fold_corner_radius * 2.,
layout,
content_origin,
text_bounds,
cx,
);
}
});
for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range(
@ -697,9 +722,7 @@ impl EditorElement {
line_end_overshoot,
layout,
content_origin,
scroll_top,
scroll_left,
bounds,
text_bounds,
cx,
);
}
@ -717,9 +740,7 @@ impl EditorElement {
corner_radius * 2.,
layout,
content_origin,
scroll_top,
scroll_left,
bounds,
text_bounds,
cx,
);
@ -738,13 +759,16 @@ impl EditorElement {
.line;
let cursor_column = cursor_position.column() as usize;
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
let mut block_width = cursor_row_layout.x_for_index(cursor_column + 1)
let cursor_character_x =
cursor_row_layout.x_for_index(cursor_column);
let mut block_width = cursor_row_layout
.x_for_index(cursor_column + 1)
- cursor_character_x;
if block_width == Pixels::ZERO {
block_width = layout.position_map.em_width;
}
let block_text = if let CursorShape::Block = selection.cursor_shape {
let block_text = if let CursorShape::Block = selection.cursor_shape
{
layout
.position_map
.snapshot
@ -771,13 +795,16 @@ impl EditorElement {
None
};
let x = cursor_character_x - scroll_left;
let y = cursor_position.row() as f32 * layout.position_map.line_height
- scroll_top;
let x = cursor_character_x - layout.position_map.scroll_position.x;
let y = cursor_position.row() as f32
* layout.position_map.line_height
- layout.position_map.scroll_position.y;
if selection.is_newest {
editor.pixel_position_of_newest_cursor = Some(point(
bounds.origin.x + x + block_width / 2.,
bounds.origin.y + y + layout.position_map.line_height / 2.,
text_bounds.origin.x + x + block_width / 2.,
text_bounds.origin.y
+ y
+ layout.position_map.line_height / 2.,
));
}
cursors.push(Cursor {
@ -793,14 +820,14 @@ impl EditorElement {
}
}
for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
for (ix, line_with_invisibles) in
layout.position_map.line_layouts.iter().enumerate()
{
let row = start_row + ix as u32;
line_with_invisibles.draw(
layout,
row,
scroll_top,
content_origin,
scroll_left,
whitespace_setting,
&invisible_display_ranges,
cx,
@ -819,7 +846,8 @@ impl EditorElement {
let available_space = size(
AvailableSpace::MinContent,
AvailableSpace::Definite(
(12. * line_height).min((bounds.size.height - line_height) / 2.),
(12. * line_height)
.min((text_bounds.size.height - line_height) / 2.),
),
);
let context_menu_size = context_menu.measure(available_space, editor, cx);
@ -827,9 +855,10 @@ impl EditorElement {
let cursor_row_layout = &layout.position_map.line_layouts
[(position.row() - start_row) as usize]
.line;
let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
let y =
(position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
let x = cursor_row_layout.x_for_index(position.column() as usize)
- layout.position_map.scroll_position.x;
let y = (position.row() + 1) as f32 * layout.position_map.line_height
- layout.position_map.scroll_position.y;
let mut list_origin = content_origin + point(x, y);
let list_width = context_menu_size.width;
let list_height = context_menu_size.height;
@ -837,10 +866,11 @@ impl EditorElement {
// Snap the right edge of the list to the right edge of the window if
// its horizontal bounds overflow.
if list_origin.x + list_width > cx.viewport_size().width {
list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
list_origin.x =
(cx.viewport_size().width - list_width).max(Pixels::ZERO);
}
if list_origin.y + list_height > bounds.lower_right().y {
if list_origin.y + list_height > text_bounds.lower_right().y {
list_origin.y -= layout.position_map.line_height - list_height;
}
@ -919,7 +949,8 @@ impl EditorElement {
// cx.scene().pop_stacking_context();
// }
})
},
)
}
fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
@ -1133,8 +1164,6 @@ impl EditorElement {
line_end_overshoot: Pixels,
layout: &LayoutState,
content_origin: gpui::Point<Pixels>,
scroll_top: Pixels,
scroll_left: Pixels,
bounds: Bounds<Pixels>,
cx: &mut ViewContext<Editor>,
) {
@ -1153,7 +1182,7 @@ impl EditorElement {
corner_radius,
start_y: content_origin.y
+ row_range.start as f32 * layout.position_map.line_height
- scroll_top,
- layout.position_map.scroll_position.y,
lines: row_range
.into_iter()
.map(|row| {
@ -1163,17 +1192,17 @@ impl EditorElement {
start_x: if row == range.start.row() {
content_origin.x
+ line_layout.x_for_index(range.start.column() as usize)
- scroll_left
- layout.position_map.scroll_position.x
} else {
content_origin.x - scroll_left
content_origin.x - layout.position_map.scroll_position.x
},
end_x: if row == range.end.row() {
content_origin.x
+ line_layout.x_for_index(range.end.column() as usize)
- scroll_left
- layout.position_map.scroll_position.x
} else {
content_origin.x + line_layout.width + line_end_overshoot
- scroll_left
- layout.position_map.scroll_position.x
},
}
})
@ -1567,7 +1596,6 @@ impl EditorElement {
let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
let mut active_rows = BTreeMap::new();
let mut fold_ranges = Vec::new();
let is_singleton = editor.is_singleton(cx);
let highlighted_rows = editor.highlighted_rows();
@ -1577,19 +1605,6 @@ impl EditorElement {
cx.theme().colors(),
);
fold_ranges.extend(
snapshot
.folds_in_range(start_anchor..end_anchor)
.map(|anchor| {
let start = anchor.range.start.to_point(&snapshot.buffer_snapshot);
(
start.row,
start.to_display_point(&snapshot.display_snapshot)
..anchor.range.end.to_display_point(&snapshot),
)
}),
);
let mut newest_selection_head = None;
if editor.show_local_selections {
@ -1698,15 +1713,6 @@ impl EditorElement {
ShowScrollbar::Never => false,
};
let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = fold_ranges
.into_iter()
.map(|(id, fold)| {
// todo!("change color based on mouse state")
let color = gpui::red();
(id, fold, color)
})
.collect();
let head_for_relative = newest_selection_head.unwrap_or_else(|| {
let newest = editor.selections.newest::<Point>(cx);
SelectionLayout::new(
@ -1908,6 +1914,10 @@ impl EditorElement {
mode: editor_mode,
position_map: Arc::new(PositionMap {
size: bounds.size,
scroll_position: point(
scroll_position.x * em_width,
scroll_position.y * line_height,
),
scroll_max,
line_layouts,
line_height,
@ -1915,6 +1925,7 @@ impl EditorElement {
em_advance,
snapshot,
}),
visible_anchor_range: start_anchor..end_anchor,
visible_display_row_range: start_row..end_row,
wrap_guides,
gutter_size,
@ -1928,7 +1939,6 @@ impl EditorElement {
active_rows,
highlighted_rows,
highlighted_ranges,
fold_ranges,
line_number_layouts,
display_hunks,
blocks,
@ -2116,11 +2126,13 @@ impl EditorElement {
bounds: Bounds<Pixels>,
gutter_bounds: Bounds<Pixels>,
text_bounds: Bounds<Pixels>,
position_map: &Arc<PositionMap>,
layout: &LayoutState,
cx: &mut ViewContext<Editor>,
) {
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
cx.on_mouse_event({
let position_map = position_map.clone();
let position_map = layout.position_map.clone();
move |editor, event: &ScrollWheelEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
@ -2132,7 +2144,7 @@ impl EditorElement {
}
});
cx.on_mouse_event({
let position_map = position_map.clone();
let position_map = layout.position_map.clone();
move |editor, event: &MouseDownEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
@ -2144,7 +2156,7 @@ impl EditorElement {
}
});
cx.on_mouse_event({
let position_map = position_map.clone();
let position_map = layout.position_map.clone();
move |editor, event: &MouseUpEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
@ -2157,7 +2169,7 @@ impl EditorElement {
});
// todo!()
// on_down(MouseButton::Right, {
// let position_map = position_map.clone();
// let position_map = layout.position_map.clone();
// move |event, editor, cx| {
// if !Self::mouse_right_down(
// editor,
@ -2171,7 +2183,7 @@ impl EditorElement {
// }
// });
cx.on_mouse_event({
let position_map = position_map.clone();
let position_map = layout.position_map.clone();
move |editor, event: &MouseMoveEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
@ -2301,18 +2313,16 @@ impl LineWithInvisibles {
&self,
layout: &LayoutState,
row: u32,
scroll_top: Pixels,
content_origin: gpui::Point<Pixels>,
scroll_left: Pixels,
whitespace_setting: ShowWhitespaceSetting,
selection_ranges: &[Range<DisplayPoint>],
cx: &mut ViewContext<Editor>,
) {
let line_height = layout.position_map.line_height;
let line_y = line_height * row as f32 - scroll_top;
let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
self.line.paint(
content_origin + gpui::point(-scroll_left, line_y),
content_origin + gpui::point(-layout.position_map.scroll_position.x, line_y),
line_height,
cx,
);
@ -2321,7 +2331,6 @@ impl LineWithInvisibles {
&selection_ranges,
layout,
content_origin,
scroll_left,
line_y,
row,
line_height,
@ -2335,7 +2344,6 @@ impl LineWithInvisibles {
selection_ranges: &[Range<DisplayPoint>],
layout: &LayoutState,
content_origin: gpui::Point<Pixels>,
scroll_left: Pixels,
line_y: Pixels,
row: u32,
line_height: Pixels,
@ -2357,8 +2365,11 @@ impl LineWithInvisibles {
let x_offset = self.line.x_for_index(token_offset);
let invisible_offset =
(layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
let origin =
content_origin + gpui::point(-scroll_left + x_offset + invisible_offset, line_y);
let origin = content_origin
+ gpui::point(
x_offset + invisible_offset - layout.position_map.scroll_position.x,
line_y,
);
if let Some(allowed_regions) = allowed_invisibles_regions {
let invisible_point = DisplayPoint::new(row, token_offset as u32);
@ -2440,13 +2451,6 @@ impl Element<Editor> for EditorElement {
// We call with_z_index to establish a new stacking context.
cx.with_z_index(0, |cx| {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
self.paint_mouse_listeners(
bounds,
gutter_bounds,
text_bounds,
&layout.position_map,
cx,
);
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
if layout.gutter_size.width > Pixels::ZERO {
self.paint_gutter(gutter_bounds, &mut layout, editor, cx);
@ -2457,6 +2461,8 @@ impl Element<Editor> for EditorElement {
self.paint_blocks(bounds, &mut layout, editor, cx);
}
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
let input_handler = ElementInputHandler::new(bounds, cx);
cx.handle_input(&editor.focus_handle, input_handler);
});
@ -3080,6 +3086,7 @@ pub struct LayoutState {
text_size: gpui::Size<Pixels>,
mode: EditorMode,
wrap_guides: SmallVec<[(Pixels, bool); 2]>,
visible_anchor_range: Range<Anchor>,
visible_display_row_range: Range<u32>,
active_rows: BTreeMap<u32, bool>,
highlighted_rows: Option<Range<u32>>,
@ -3087,7 +3094,6 @@ pub struct LayoutState {
display_hunks: Vec<DisplayDiffHunk>,
blocks: Vec<BlockLayout>,
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)>,
selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
scrollbar_row_range: Range<f32>,
show_scrollbars: bool,
@ -3109,6 +3115,7 @@ struct CodeActionsIndicator {
struct PositionMap {
size: Size<Pixels>,
line_height: Pixels,
scroll_position: gpui::Point<Pixels>,
scroll_max: gpui::Point<f32>,
em_width: Pixels,
em_advance: Pixels,
@ -3445,58 +3452,6 @@ impl HighlightedRange {
}
}
// fn range_to_bounds(
// range: &Range<DisplayPoint>,
// content_origin: gpui::Point<Pixels>,
// scroll_left: f32,
// scroll_top: f32,
// visible_row_range: &Range<u32>,
// line_end_overshoot: f32,
// position_map: &PositionMap,
// ) -> impl Iterator<Item = Bounds<Pixels>> {
// let mut bounds: SmallVec<[Bounds<Pixels>; 1]> = SmallVec::new();
// if range.start == range.end {
// return bounds.into_iter();
// }
// let start_row = visible_row_range.start;
// let end_row = visible_row_range.end;
// let row_range = if range.end.column() == 0 {
// cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
// } else {
// cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
// };
// let first_y =
// content_origin.y + row_range.start as f32 * position_map.line_height - scroll_top;
// for (idx, row) in row_range.enumerate() {
// let line_layout = &position_map.line_layouts[(row - start_row) as usize].line;
// let start_x = if row == range.start.row() {
// content_origin.x + line_layout.x_for_index(range.start.column() as usize)
// - scroll_left
// } else {
// content_origin.x - scroll_left
// };
// let end_x = if row == range.end.row() {
// content_origin.x + line_layout.x_for_index(range.end.column() as usize) - scroll_left
// } else {
// content_origin.x + line_layout.width() + line_end_overshoot - scroll_left
// };
// bounds.push(Bounds::<Pixels>::from_points(
// point(start_x, first_y + position_map.line_height * idx as f32),
// point(end_x, first_y + position_map.line_height * (idx + 1) as f32),
// ))
// }
// bounds.into_iter()
// }
pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
(delta.pow(1.5) / 100.0).into()
}

View file

@ -3,7 +3,7 @@ use crate::{
};
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
use std::{any::Any, mem};
use std::{any::Any, fmt::Debug, mem};
pub trait Element<V: 'static> {
type ElementState: 'static;
@ -33,6 +33,42 @@ pub trait Element<V: 'static> {
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
);
fn draw<T, R>(
self,
origin: Point<Pixels>,
available_space: Size<T>,
view_state: &mut V,
cx: &mut ViewContext<V>,
f: impl FnOnce(&Self::ElementState, &mut ViewContext<V>) -> R,
) -> R
where
Self: Sized,
T: Clone + Default + Debug + Into<AvailableSpace>,
{
let mut element = RenderedElement {
element: self,
phase: ElementRenderPhase::Start,
};
element.draw(origin, available_space.map(Into::into), view_state, cx);
if let ElementRenderPhase::Painted { frame_state } = &element.phase {
if let Some(frame_state) = frame_state.as_ref() {
f(&frame_state, cx)
} else {
let element_id = element
.element
.element_id()
.expect("we either have some frame_state or some element_id");
cx.with_element_state(element_id, |element_state, cx| {
let element_state = element_state.unwrap();
let result = f(&element_state, cx);
(result, element_state)
})
}
} else {
unreachable!()
}
}
}
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
@ -99,7 +135,9 @@ enum ElementRenderPhase<V> {
available_space: Size<AvailableSpace>,
frame_state: Option<V>,
},
Painted,
Painted {
frame_state: Option<V>,
},
}
/// Internal struct that wraps an element to store Layout and ElementState after the element is rendered.
@ -157,7 +195,7 @@ where
ElementRenderPhase::Start => panic!("must call initialize before layout"),
ElementRenderPhase::LayoutRequested { .. }
| ElementRenderPhase::LayoutComputed { .. }
| ElementRenderPhase::Painted => {
| ElementRenderPhase::Painted { .. } => {
panic!("element rendered twice")
}
};
@ -192,7 +230,7 @@ where
self.element
.paint(bounds, view_state, frame_state.as_mut().unwrap(), cx);
}
ElementRenderPhase::Painted
ElementRenderPhase::Painted { frame_state }
}
_ => panic!("must call layout before paint"),

View file

@ -711,6 +711,12 @@ pub struct DivState {
interactive_state: InteractiveElementState,
}
impl DivState {
pub fn is_active(&self) -> bool {
self.interactive_state.pending_mouse_down.lock().is_some()
}
}
pub struct Interactivity<V> {
pub element_id: Option<ElementId>,
pub key_context: KeyContext,

View file

@ -216,7 +216,7 @@ pub struct Window {
// #[derive(Default)]
pub(crate) struct Frame {
element_states: HashMap<GlobalElementId, AnyBox>,
pub(crate) element_states: HashMap<GlobalElementId, AnyBox>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
pub(crate) dispatch_tree: DispatchTree,
pub(crate) focus_listeners: Vec<AnyFocusListener>,