From 606d683f29585ea8ab1760d98666693a4824373b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 3 Mar 2023 12:26:29 -0800 Subject: [PATCH] Add interactable fold markers Change fold handlers to be driven by the fold map Switch to a mouse region based implementation for click regions Co-authored-by: Max --- crates/editor/src/display_map.rs | 5 ++ crates/editor/src/display_map/fold_map.rs | 25 ++++++- crates/editor/src/editor.rs | 6 +- crates/editor/src/element.rs | 82 ++++++++++++++++------- crates/gpui/src/app.rs | 8 +-- crates/gpui/src/presenter.rs | 24 ++++++- crates/theme/src/theme.rs | 16 +++-- styles/src/styleTree/editor.ts | 16 +++++ 8 files changed, 140 insertions(+), 42 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 38d5242321..500f3626e3 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -8,6 +8,7 @@ use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; use fold_map::FoldMap; use gpui::{ + color::Color, fonts::{FontId, HighlightStyle}, Entity, ModelContext, ModelHandle, }; @@ -218,6 +219,10 @@ impl DisplayMap { .update(cx, |map, cx| map.set_font(font_id, font_size, cx)) } + pub fn set_fold_ellipses_color(&mut self, color: Color) -> bool { + self.fold_map.set_ellipses_color(color) + } + pub fn set_wrap_width(&self, width: Option, cx: &mut ModelContext) -> bool { self.wrap_map .update(cx, |map, cx| map.set_wrap_width(width, cx)) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 44de95fe32..6323461987 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -4,7 +4,7 @@ use crate::{ ToOffset, }; use collections::BTreeMap; -use gpui::fonts::HighlightStyle; +use gpui::{color::Color, fonts::HighlightStyle}; use language::{Chunk, Edit, Point, TextSummary}; use parking_lot::Mutex; use std::{ @@ -133,6 +133,7 @@ impl<'a> FoldMapWriter<'a> { folds: self.0.folds.clone(), buffer_snapshot: buffer, version: self.0.version.load(SeqCst), + ellipses_color: self.0.ellipses_color, }; (snapshot, edits) } @@ -182,6 +183,7 @@ impl<'a> FoldMapWriter<'a> { folds: self.0.folds.clone(), buffer_snapshot: buffer, version: self.0.version.load(SeqCst), + ellipses_color: self.0.ellipses_color, }; (snapshot, edits) } @@ -192,6 +194,7 @@ pub struct FoldMap { transforms: Mutex>, folds: SumTree, version: AtomicUsize, + ellipses_color: Option, } impl FoldMap { @@ -209,6 +212,7 @@ impl FoldMap { }, &(), )), + ellipses_color: None, version: Default::default(), }; @@ -217,6 +221,7 @@ impl FoldMap { folds: this.folds.clone(), buffer_snapshot: this.buffer.lock().clone(), version: this.version.load(SeqCst), + ellipses_color: None, }; (this, snapshot) } @@ -233,6 +238,7 @@ impl FoldMap { folds: self.folds.clone(), buffer_snapshot: self.buffer.lock().clone(), version: self.version.load(SeqCst), + ellipses_color: self.ellipses_color, }; (snapshot, edits) } @@ -246,6 +252,15 @@ impl FoldMap { (FoldMapWriter(self), snapshot, edits) } + pub fn set_ellipses_color(&mut self, color: Color) -> bool { + if self.ellipses_color != Some(color) { + self.ellipses_color = Some(color); + true + } else { + false + } + } + fn check_invariants(&self) { if cfg!(test) { assert_eq!( @@ -477,6 +492,7 @@ pub struct FoldSnapshot { folds: SumTree, buffer_snapshot: MultiBufferSnapshot, pub version: usize, + pub ellipses_color: Option, } impl FoldSnapshot { @@ -739,6 +755,7 @@ impl FoldSnapshot { max_output_offset: range.end.0, highlight_endpoints: highlight_endpoints.into_iter().peekable(), active_highlights: Default::default(), + ellipses_color: self.ellipses_color, } } @@ -1029,6 +1046,7 @@ pub struct FoldChunks<'a> { max_output_offset: usize, highlight_endpoints: Peekable>, active_highlights: BTreeMap, HighlightStyle>, + ellipses_color: Option, } impl<'a> Iterator for FoldChunks<'a> { @@ -1058,7 +1076,10 @@ impl<'a> Iterator for FoldChunks<'a> { return Some(Chunk { text: output_text, syntax_highlight_id: None, - highlight_style: None, + highlight_style: self.ellipses_color.map(|color| HighlightStyle { + color: Some(color), + ..Default::default() + }), diagnostic_severity: None, is_unnecessary: false, }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8a3acef996..3e7a14d2ae 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -39,11 +39,10 @@ use gpui::{ impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::CursorStyle, - scene::MouseClick, serde_json::json, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity, - EventContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, - View, ViewContext, ViewHandle, WeakViewHandle, + ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View, + ViewContext, ViewHandle, WeakViewHandle, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HideHover, HoverState}; @@ -6448,6 +6447,7 @@ impl View for Editor { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let style = self.style(cx); let font_changed = self.display_map.update(cx, |map, cx| { + map.set_fold_ellipses_color(style.folds.ellipses.text_color); map.set_font(style.text.font_id, style.text.font_size, cx) }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fac90f2507..f69902e53d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -50,6 +50,8 @@ use std::{ }; use workspace::item::Item; +enum FoldMarkers {} + struct SelectionLayout { head: DisplayPoint, cursor_shape: CursorShape, @@ -115,7 +117,6 @@ impl EditorElement { fn attach_mouse_handlers( view: &WeakViewHandle, position_map: &Arc, - fold_ranges: Arc<[Range]>, has_popovers: bool, visible_bounds: RectF, text_bounds: RectF, @@ -212,23 +213,6 @@ impl EditorElement { cx.propagate_event() } } - }) - .on_click(MouseButton::Left, { - let position_map = position_map.clone(); - move |e, cx| { - let point = - position_to_display_point(e.position, text_bounds, &position_map); - if let Some(point) = point { - for range in fold_ranges.iter() { - // Range -> RangeInclusive - if range.contains(&point) || range.end == point { - cx.dispatch_action(UnfoldAt { - display_row: point.row(), - }) - } - } - } - } }), ); @@ -724,9 +708,24 @@ impl EditorElement { }, }); - for range in layout.fold_ranges.iter() { + 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, + ); + for bound in range_to_bounds( - range, + &range, content_origin, scroll_left, scroll_top, @@ -738,6 +737,16 @@ impl EditorElement { bounds: bound, style: CursorStyle::PointingHand, }); + + let display_row = range.start.row(); + cx.scene.push_mouse_region( + MouseRegion::new::(self.view.id(), *id as usize, bound) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(UnfoldAt { display_row }) + }) + .with_notify_on_hover(true) + .with_notify_on_click(true), + ) } } @@ -757,9 +766,10 @@ impl EditorElement { } let mut cursors = SmallVec::<[Cursor; 32]>::new(); + let corner_radius = 0.15 * layout.position_map.line_height; + for (replica_id, selections) in &layout.selections { let selection_style = style.replica_selection_style(*replica_id); - let corner_radius = 0.15 * layout.position_map.line_height; for selection in selections { self.paint_highlighted_range( @@ -1695,8 +1705,12 @@ impl Element for EditorElement { snapshot .folds_in_range(start_anchor..end_anchor) .map(|anchor| { - anchor.start.to_display_point(&snapshot.display_snapshot) - ..anchor.end.to_display_point(&snapshot.display_snapshot) + let start = anchor.start.to_point(&snapshot.buffer_snapshot); + ( + start.row, + start.to_display_point(&snapshot.display_snapshot) + ..anchor.end.to_display_point(&snapshot), + ) }), ); @@ -1768,6 +1782,21 @@ impl Element for EditorElement { .unwrap_or_default() }); + let fold_ranges: Vec<(BufferRow, Range, Color)> = fold_ranges + .into_iter() + .map(|(id, fold)| { + let color = self + .style + .folds + .ellipses + .background + .style_for(&mut cx.mouse_state::(id as usize), false) + .color; + + (id, fold, color) + }) + .collect(); + let line_number_layouts = self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx); @@ -1955,7 +1984,7 @@ impl Element for EditorElement { active_rows, highlighted_rows, highlighted_ranges, - fold_ranges: fold_ranges.into(), + fold_ranges, line_number_layouts, display_hunks, blocks, @@ -1987,7 +2016,6 @@ impl Element for EditorElement { Self::attach_mouse_handlers( &self.view, &layout.position_map, - layout.fold_ranges.clone(), // No need to clone the vec layout.hover_popovers.is_some(), visible_bounds, text_bounds, @@ -2071,6 +2099,8 @@ impl Element for EditorElement { } } +type BufferRow = u32; + pub struct LayoutState { position_map: Arc, gutter_size: Vector2F, @@ -2085,7 +2115,7 @@ pub struct LayoutState { display_hunks: Vec, blocks: Vec, highlighted_ranges: Vec<(Range, Color)>, - fold_ranges: Arc<[Range]>, + fold_ranges: Vec<(BufferRow, Range, Color)>, selections: Vec<(ReplicaId, Vec)>, scrollbar_row_range: Range, show_scrollbars: bool, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 35c91f1ff2..31563010b7 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4159,10 +4159,10 @@ pub struct RenderContext<'a, T: View> { #[derive(Debug, Clone, Default)] pub struct MouseState { - hovered: bool, - clicked: Option, - accessed_hovered: bool, - accessed_clicked: bool, + pub(crate) hovered: bool, + pub(crate) clicked: Option, + pub(crate) accessed_hovered: bool, + pub(crate) accessed_clicked: bool, } impl MouseState { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index c0785e11f3..5ffed579cf 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -12,9 +12,9 @@ use crate::{ text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, Appearance, AssetCache, ElementBox, Entity, FontSystem, ModelHandle, MouseButton, MouseMovedEvent, - MouseRegion, MouseRegionId, ParentId, ReadModel, ReadView, RenderContext, RenderParams, - SceneBuilder, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle, - WeakViewHandle, + MouseRegion, MouseRegionId, MouseState, ParentId, ReadModel, ReadView, RenderContext, + RenderParams, SceneBuilder, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, + WeakModelHandle, WeakViewHandle, }; use anyhow::bail; use collections::{HashMap, HashSet}; @@ -603,6 +603,24 @@ pub struct LayoutContext<'a> { } impl<'a> LayoutContext<'a> { + pub fn mouse_state(&self, region_id: usize) -> MouseState { + let view_id = self.view_stack.last().unwrap(); + + let region_id = MouseRegionId::new::(*view_id, region_id); + MouseState { + hovered: self.hovered_region_ids.contains(®ion_id), + clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| { + if ids.contains(®ion_id) { + Some(*button) + } else { + None + } + }), + accessed_hovered: false, + accessed_clicked: false, + } + } + fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F { let print_error = |view_id| { format!( diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 06122b9dda..5e8b5dcbcd 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -632,16 +632,22 @@ pub struct FieldEditor { pub selection: SelectionStyle, } +#[derive(Clone, Deserialize, Default)] +pub struct InteractiveColor { + pub color: Color, +} + #[derive(Clone, Deserialize, Default)] pub struct CodeActions { #[serde(default)] - pub indicator: Interactive, + pub indicator: Interactive, pub vertical_scale: f32, } #[derive(Clone, Deserialize, Default)] pub struct Folds { - pub indicator: Interactive, + pub indicator: Interactive, + pub ellipses: FoldEllipses, pub fold_background: Color, pub icon_width: f32, pub folded_icon: String, @@ -649,8 +655,10 @@ pub struct Folds { } #[derive(Clone, Deserialize, Default)] -pub struct Indicator { - pub color: Color, +pub struct FoldEllipses { + pub text_color: Color, + pub background: Interactive, + pub corner_radius_factor: f32, } #[derive(Clone, Deserialize, Default)] diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index c8334d1cc0..799363a349 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -76,6 +76,22 @@ export default function editor(colorScheme: ColorScheme) { color: foreground(layer, "on"), }, }, + ellipses: { + textColor: colorScheme.ramps.neutral(0.71).hex(), + cornerRadiusFactor: 0.15, + background: { + // Copied from hover_popover highlight + color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(), + + hover: { + color: colorScheme.ramps.neutral(0.5).alpha(0.5).hex(), + }, + + clicked: { + color: colorScheme.ramps.neutral(0.5).alpha(0.7).hex(), + }, + } + }, foldBackground: foreground(layer, "variant"), }, diff: {