From 178442a4a858a93663f5870192385a75b1a6fe98 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 01:09:02 -0800 Subject: [PATCH 1/5] Add support for rendering cursors as a block and underscore --- crates/editor/src/editor.rs | 12 +++++++++- crates/editor/src/element.rs | 45 ++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 91d02b1aa0..711fee890b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -450,6 +450,7 @@ pub struct Editor { document_highlights_task: Option>, pending_rename: Option, searchable: bool, + cursor_shape: CursorShape, } pub struct EditorSnapshot { @@ -930,6 +931,7 @@ impl Editor { document_highlights_task: Default::default(), pending_rename: Default::default(), searchable: true, + cursor_shape: Default::default(), }; this.end_selection(cx); this @@ -1021,6 +1023,14 @@ impl Editor { cx.notify(); } + pub fn set_cursor_shape( + &mut self, + cursor_shape: CursorShape + ) { + self.cursor_shape = cursor_shape; + // TODO: Do we need to notify? + } + pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor) @@ -5569,7 +5579,7 @@ impl View for Editor { self.display_map.update(cx, |map, cx| { map.set_font(style.text.font_id, style.text.font_size, cx) }); - EditorElement::new(self.handle.clone(), style.clone()).boxed() + EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed() } fn ui_name() -> &'static str { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index dcf716e0bb..85e34108d7 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -32,11 +32,12 @@ use std::{ pub struct EditorElement { view: WeakViewHandle, style: EditorStyle, + cursor_shape: CursorShape, } impl EditorElement { - pub fn new(view: WeakViewHandle, style: EditorStyle) -> Self { - Self { view, style } + pub fn new(view: WeakViewHandle, style: EditorStyle, cursor_shape: CursorShape) -> Self { + Self { view, style, cursor_shape } } fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor { @@ -362,13 +363,24 @@ impl EditorElement { if (start_row..end_row).contains(&cursor_position.row()) { let cursor_row_layout = &layout.line_layouts[(cursor_position.row() - start_row) as usize]; - let x = cursor_row_layout.x_for_index(cursor_position.column() as usize) - - scroll_left; + let cursor_column = cursor_position.column() as usize; + let cursor_character_x = cursor_row_layout.x_for_index(cursor_column); + let mut character_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; + // TODO: Is there a better option here for the character size + // at the end of the line? + // Default to 1/3 the line height + if character_width == 0.0 { + character_width = layout.line_height / 3.0; + } + + let x = cursor_character_x - scroll_left; let y = cursor_position.row() as f32 * layout.line_height - scroll_top; cursors.push(Cursor { color: style.cursor, + character_width, origin: content_origin + vec2f(x, y), line_height: layout.line_height, + shape: self.cursor_shape, }); } } @@ -1212,16 +1224,39 @@ impl PaintState { } } +#[derive(Copy, Clone)] +pub enum CursorShape { + Bar, + Block, + Underscore +} + +impl Default for CursorShape { + fn default() -> Self { + CursorShape::Bar + } +} + struct Cursor { origin: Vector2F, + character_width: f32, line_height: f32, color: Color, + shape: CursorShape } impl Cursor { fn paint(&self, cx: &mut PaintContext) { + let bounds = match self.shape { + CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)), + CursorShape::Block => RectF::new(self.origin, vec2f(self.character_width, self.line_height)), + CursorShape::Underscore => RectF::new( + self.origin + Vector2F::new(0.0, self.line_height - 2.0), + vec2f(self.character_width, 2.0)), + }; + cx.scene.push_quad(Quad { - bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)), + bounds, background: Some(self.color), border: Border::new(0., Color::black()), corner_radius: 0., From 0d42c851959214015e736396b9bf491d0725cae6 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 01:09:25 -0800 Subject: [PATCH 2/5] fix formatting --- crates/editor/src/editor.rs | 5 +---- crates/editor/src/element.rs | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 711fee890b..32a81f4f69 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1023,10 +1023,7 @@ impl Editor { cx.notify(); } - pub fn set_cursor_shape( - &mut self, - cursor_shape: CursorShape - ) { + pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape) { self.cursor_shape = cursor_shape; // TODO: Do we need to notify? } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 85e34108d7..822d3ab085 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -36,8 +36,16 @@ pub struct EditorElement { } impl EditorElement { - pub fn new(view: WeakViewHandle, style: EditorStyle, cursor_shape: CursorShape) -> Self { - Self { view, style, cursor_shape } + pub fn new( + view: WeakViewHandle, + style: EditorStyle, + cursor_shape: CursorShape, + ) -> Self { + Self { + view, + style, + cursor_shape, + } } fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor { @@ -365,7 +373,8 @@ impl EditorElement { &layout.line_layouts[(cursor_position.row() - start_row) as usize]; let cursor_column = cursor_position.column() as usize; let cursor_character_x = cursor_row_layout.x_for_index(cursor_column); - let mut character_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; + let mut character_width = + cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; // TODO: Is there a better option here for the character size // at the end of the line? // Default to 1/3 the line height @@ -1228,7 +1237,7 @@ impl PaintState { pub enum CursorShape { Bar, Block, - Underscore + Underscore, } impl Default for CursorShape { @@ -1242,17 +1251,20 @@ struct Cursor { character_width: f32, line_height: f32, color: Color, - shape: CursorShape + shape: CursorShape, } impl Cursor { fn paint(&self, cx: &mut PaintContext) { let bounds = match self.shape { CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)), - CursorShape::Block => RectF::new(self.origin, vec2f(self.character_width, self.line_height)), + CursorShape::Block => { + RectF::new(self.origin, vec2f(self.character_width, self.line_height)) + } CursorShape::Underscore => RectF::new( self.origin + Vector2F::new(0.0, self.line_height - 2.0), - vec2f(self.character_width, 2.0)), + vec2f(self.character_width, 2.0), + ), }; cx.scene.push_quad(Quad { From eddb089f2744f282c2e490c711fbf0fbf0532fc0 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 13:16:31 -0800 Subject: [PATCH 3/5] render character under block cursor --- crates/editor/src/editor.rs | 4 +-- crates/editor/src/element.rs | 61 +++++++++++++++++++++++++--------- crates/gpui/src/text_layout.rs | 20 +++++++++-- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 32a81f4f69..31a0387cce 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1023,9 +1023,9 @@ impl Editor { cx.notify(); } - pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape) { + pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext) { self.cursor_shape = cursor_shape; - // TODO: Do we need to notify? + cx.notify(); } pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 822d3ab085..caea726700 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -16,7 +16,7 @@ use gpui::{ PathBuilder, }, json::{self, ToJson}, - text_layout::{self, RunStyle, TextLayoutCache}, + text_layout::{self, Line, RunStyle, TextLayoutCache}, AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; @@ -347,7 +347,7 @@ impl EditorElement { let mut cursors = SmallVec::<[Cursor; 32]>::new(); for (replica_id, selections) in &layout.selections { - let style = style.replica_selection_style(*replica_id); + let selection_style = style.replica_selection_style(*replica_id); let corner_radius = 0.15 * layout.line_height; for selection in selections { @@ -355,7 +355,7 @@ impl EditorElement { selection.start..selection.end, start_row, end_row, - style.selection, + selection_style.selection, corner_radius, corner_radius * 2., layout, @@ -372,24 +372,49 @@ impl EditorElement { let cursor_row_layout = &layout.line_layouts[(cursor_position.row() - start_row) as usize]; let cursor_column = cursor_position.column() as usize; + let cursor_character_x = cursor_row_layout.x_for_index(cursor_column); - let mut character_width = + let mut block_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; - // TODO: Is there a better option here for the character size - // at the end of the line? - // Default to 1/3 the line height - if character_width == 0.0 { - character_width = layout.line_height / 3.0; + if block_width == 0.0 { + block_width = layout.em_width; } + let block_text = + if matches!(self.cursor_shape, CursorShape::Block) { + layout.snapshot.chars_at(cursor_position).next().and_then( + |character| { + let font_id = + cursor_row_layout.font_for_index(cursor_column)?; + let text = character.to_string(); + + Some(cx.text_layout_cache.layout_str( + &text, + cursor_row_layout.font_size(), + &[( + text.len(), + RunStyle { + font_id, + color: style.background, + underline: None, + }, + )], + )) + }, + ) + } else { + None + }; + let x = cursor_character_x - scroll_left; let y = cursor_position.row() as f32 * layout.line_height - scroll_top; cursors.push(Cursor { - color: style.cursor, - character_width, + color: selection_style.cursor, + block_width, origin: content_origin + vec2f(x, y), line_height: layout.line_height, shape: self.cursor_shape, + block_text, }); } } @@ -1182,6 +1207,7 @@ fn layout_line( while !line.is_char_boundary(len) { len -= 1; } + line.truncate(len); } @@ -1242,16 +1268,17 @@ pub enum CursorShape { impl Default for CursorShape { fn default() -> Self { - CursorShape::Bar + CursorShape::Block } } struct Cursor { origin: Vector2F, - character_width: f32, + block_width: f32, line_height: f32, color: Color, shape: CursorShape, + block_text: Option, } impl Cursor { @@ -1259,11 +1286,11 @@ impl Cursor { let bounds = match self.shape { CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)), CursorShape::Block => { - RectF::new(self.origin, vec2f(self.character_width, self.line_height)) + RectF::new(self.origin, vec2f(self.block_width, self.line_height)) } CursorShape::Underscore => RectF::new( self.origin + Vector2F::new(0.0, self.line_height - 2.0), - vec2f(self.character_width, 2.0), + vec2f(self.block_width, 2.0), ), }; @@ -1273,6 +1300,10 @@ impl Cursor { border: Border::new(0., Color::black()), corner_radius: 0., }); + + if let Some(block_text) = &self.block_text { + block_text.paint(self.origin, bounds, self.line_height, cx); + } } } diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index 6e371437bf..25f1cb82c0 100644 --- a/crates/gpui/src/text_layout.rs +++ b/crates/gpui/src/text_layout.rs @@ -186,7 +186,7 @@ pub struct Run { pub glyphs: Vec, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Glyph { pub id: GlyphId, pub position: Vector2F, @@ -210,10 +210,14 @@ impl Line { self.layout.width } + pub fn font_size(&self) -> f32 { + self.layout.font_size + } + pub fn x_for_index(&self, index: usize) -> f32 { for run in &self.layout.runs { for glyph in &run.glyphs { - if glyph.index == index { + if glyph.index >= index { return glyph.position.x(); } } @@ -221,6 +225,18 @@ impl Line { self.layout.width } + pub fn font_for_index(&self, index: usize) -> Option { + for run in &self.layout.runs { + for glyph in &run.glyphs { + if glyph.index >= index { + return Some(run.font_id); + } + } + } + + None + } + pub fn index_for_x(&self, x: f32) -> Option { if x >= self.layout.width { None From 5b35c68d2e678beeeccd3969520540fe4feeca3e Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 13:20:45 -0800 Subject: [PATCH 4/5] Fix failing gpui test from missing cursor shape --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index caea726700..3238cf840f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1467,7 +1467,7 @@ mod tests { let (window_id, editor) = cx.add_window(Default::default(), |cx| { Editor::new(EditorMode::Full, buffer, None, settings.1, None, cx) }); - let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx)); + let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx), CursorShape::Bar); let layouts = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); From 5502c00d9a77bf4e0394a9e451bdfd43474eb5b2 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 13:28:13 -0800 Subject: [PATCH 5/5] swap default cursor shape back to bar --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 3238cf840f..b15487b54c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1268,7 +1268,7 @@ pub enum CursorShape { impl Default for CursorShape { fn default() -> Self { - CursorShape::Block + CursorShape::Bar } }