From 41ddd1cc975e06d8a300902010195a6697e2be9b Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Tue, 11 Mar 2025 13:13:11 +0000 Subject: [PATCH] editor: Fix text selection not visible on text background (#26454) Closes #25014 Previously, we painted in the order: highlights -> text background -> text -> etc. This caused text selection to be invisible when the text had a background. This PR changes the painting order to: text background -> highlights -> text -> etc. Before: https://github.com/user-attachments/assets/5d9647c4-3ab2-4960-b6b9-e399882a0c50 After: https://github.com/user-attachments/assets/c699f5b9-4077-45f8-85e5-86c89130eb71 Release Notes: - Fixed an issue where text selection was not visible on top of a text background in the editor. --- crates/editor/src/element.rs | 42 ++++++ crates/gpui/src/text_system/line.rs | 215 ++++++++++++++++++++-------- 2 files changed, 200 insertions(+), 57 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5ce9cdb2bf5eb46ca9018e18afa43856e96dab6e..411c48ed29a196afa29a0471ecc875e6598f033a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4653,6 +4653,7 @@ impl EditorElement { }; window.set_cursor_style(cursor_style, &layout.position_map.text_hitbox); + self.paint_lines_background(layout, window, cx); let invisible_display_ranges = self.paint_highlights(layout, window); self.paint_lines(&invisible_display_ranges, layout, window, cx); self.paint_redactions(layout, window); @@ -4743,6 +4744,18 @@ impl EditorElement { } } + fn paint_lines_background( + &mut self, + layout: &mut EditorLayout, + window: &mut Window, + cx: &mut App, + ) { + for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() { + let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32); + line_with_invisibles.draw_background(layout, row, layout.content_origin, window, cx); + } + } + fn paint_redactions(&mut self, layout: &EditorLayout, window: &mut Window) { if layout.redacted_ranges.is_empty() { return; @@ -6237,6 +6250,35 @@ impl LineWithInvisibles { ); } + fn draw_background( + &self, + layout: &EditorLayout, + row: DisplayRow, + content_origin: gpui::Point, + window: &mut Window, + cx: &mut App, + ) { + let line_height = layout.position_map.line_height; + let line_y = line_height + * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height); + + let mut fragment_origin = + content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y); + + for fragment in &self.fragments { + match fragment { + LineFragment::Text(line) => { + line.paint_background(fragment_origin, line_height, window, cx) + .log_err(); + fragment_origin.x += line.width; + } + LineFragment::Element { size, .. } => { + fragment_origin.x += size.width; + } + } + } + } + fn draw_invisibles( &self, selection_ranges: &[Range], diff --git a/crates/gpui/src/text_system/line.rs b/crates/gpui/src/text_system/line.rs index 679cc1f1babe0eb89d4d51f6464c796742b1aa32..af6c0b7354f8fa6015715308ca3ab0acacf2ea48 100644 --- a/crates/gpui/src/text_system/line.rs +++ b/crates/gpui/src/text_system/line.rs @@ -81,6 +81,29 @@ impl ShapedLine { Ok(()) } + + /// Paint the background of the line to the window. + pub fn paint_background( + &self, + origin: Point, + line_height: Pixels, + window: &mut Window, + cx: &mut App, + ) -> Result<()> { + paint_line_background( + origin, + &self.layout, + line_height, + TextAlign::default(), + None, + &self.decoration_runs, + &[], + window, + cx, + )?; + + Ok(()) + } } /// A line of text that has been shaped, decorated, and wrapped by the text layout system. @@ -159,7 +182,6 @@ fn paint_line( let mut color = black(); let mut current_underline: Option<(Point, UnderlineStyle)> = None; let mut current_strikethrough: Option<(Point, StrikethroughStyle)> = None; - let mut current_background: Option<(Point, Hsla)> = None; let text_system = cx.text_system().clone(); let mut glyph_origin = point( aligned_origin_x( @@ -182,21 +204,6 @@ fn paint_line( if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) { wraps.next(); - if let Some((background_origin, background_color)) = current_background.as_mut() - { - if glyph_origin.x == background_origin.x { - background_origin.x -= max_glyph_size.width.half() - } - window.paint_quad(fill( - Bounds { - origin: *background_origin, - size: size(glyph_origin.x - background_origin.x, line_height), - }, - *background_color, - )); - background_origin.x = origin.x; - background_origin.y += line_height; - } if let Some((underline_origin, underline_style)) = current_underline.as_mut() { if glyph_origin.x == underline_origin.x { underline_origin.x -= max_glyph_size.width.half(); @@ -236,7 +243,6 @@ fn paint_line( } prev_glyph_position = glyph.position; - let mut finished_background: Option<(Point, Hsla)> = None; let mut finished_underline: Option<(Point, UnderlineStyle)> = None; let mut finished_strikethrough: Option<(Point, StrikethroughStyle)> = None; if glyph.index >= run_end { @@ -252,18 +258,6 @@ fn paint_line( } if let Some(style_run) = style_run { - if let Some((_, background_color)) = &mut current_background { - if style_run.background_color.as_ref() != Some(background_color) { - finished_background = current_background.take(); - } - } - if let Some(run_background) = style_run.background_color { - current_background.get_or_insert(( - point(glyph_origin.x, glyph_origin.y), - run_background, - )); - } - if let Some((_, underline_style)) = &mut current_underline { if style_run.underline.as_ref() != Some(underline_style) { finished_underline = current_underline.take(); @@ -305,26 +299,11 @@ fn paint_line( color = style_run.color; } else { run_end = layout.len; - finished_background = current_background.take(); finished_underline = current_underline.take(); finished_strikethrough = current_strikethrough.take(); } } - if let Some((mut background_origin, background_color)) = finished_background { - let mut width = glyph_origin.x - background_origin.x; - if background_origin.x == glyph_origin.x { - background_origin.x -= max_glyph_size.width.half(); - }; - window.paint_quad(fill( - Bounds { - origin: background_origin, - size: size(width, line_height), - }, - background_color, - )); - } - if let Some((mut underline_origin, underline_style)) = finished_underline { if underline_origin.x == glyph_origin.x { underline_origin.x -= max_glyph_size.width.half(); @@ -383,19 +362,6 @@ fn paint_line( last_line_end_x -= glyph.position.x; } - if let Some((mut background_origin, background_color)) = current_background.take() { - if last_line_end_x == background_origin.x { - background_origin.x -= max_glyph_size.width.half() - }; - window.paint_quad(fill( - Bounds { - origin: background_origin, - size: size(last_line_end_x - background_origin.x, line_height), - }, - background_color, - )); - } - if let Some((mut underline_start, underline_style)) = current_underline.take() { if last_line_end_x == underline_start.x { underline_start.x -= max_glyph_size.width.half() @@ -422,6 +388,141 @@ fn paint_line( }) } +fn paint_line_background( + origin: Point, + layout: &LineLayout, + line_height: Pixels, + align: TextAlign, + align_width: Option, + decoration_runs: &[DecorationRun], + wrap_boundaries: &[WrapBoundary], + window: &mut Window, + cx: &mut App, +) -> Result<()> { + let line_bounds = Bounds::new( + origin, + size( + layout.width, + line_height * (wrap_boundaries.len() as f32 + 1.), + ), + ); + window.paint_layer(line_bounds, |window| { + let mut decoration_runs = decoration_runs.iter(); + let mut wraps = wrap_boundaries.iter().peekable(); + let mut run_end = 0; + let mut current_background: Option<(Point, Hsla)> = None; + let text_system = cx.text_system().clone(); + let mut glyph_origin = point( + aligned_origin_x( + origin, + align_width.unwrap_or(layout.width), + px(0.0), + &align, + layout, + wraps.peek(), + ), + origin.y, + ); + let mut prev_glyph_position = Point::default(); + let mut max_glyph_size = size(px(0.), px(0.)); + for (run_ix, run) in layout.runs.iter().enumerate() { + max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size; + + for (glyph_ix, glyph) in run.glyphs.iter().enumerate() { + glyph_origin.x += glyph.position.x - prev_glyph_position.x; + + if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) { + wraps.next(); + if let Some((background_origin, background_color)) = current_background.as_mut() + { + if glyph_origin.x == background_origin.x { + background_origin.x -= max_glyph_size.width.half() + } + window.paint_quad(fill( + Bounds { + origin: *background_origin, + size: size(glyph_origin.x - background_origin.x, line_height), + }, + *background_color, + )); + background_origin.x = origin.x; + background_origin.y += line_height; + } + } + prev_glyph_position = glyph.position; + + let mut finished_background: Option<(Point, Hsla)> = None; + if glyph.index >= run_end { + let mut style_run = decoration_runs.next(); + + // ignore style runs that apply to a partial glyph + while let Some(run) = style_run { + if glyph.index < run_end + (run.len as usize) { + break; + } + run_end += run.len as usize; + style_run = decoration_runs.next(); + } + + if let Some(style_run) = style_run { + if let Some((_, background_color)) = &mut current_background { + if style_run.background_color.as_ref() != Some(background_color) { + finished_background = current_background.take(); + } + } + if let Some(run_background) = style_run.background_color { + current_background.get_or_insert(( + point(glyph_origin.x, glyph_origin.y), + run_background, + )); + } + run_end += style_run.len as usize; + } else { + run_end = layout.len; + finished_background = current_background.take(); + } + } + + if let Some((mut background_origin, background_color)) = finished_background { + let mut width = glyph_origin.x - background_origin.x; + if background_origin.x == glyph_origin.x { + background_origin.x -= max_glyph_size.width.half(); + }; + window.paint_quad(fill( + Bounds { + origin: background_origin, + size: size(width, line_height), + }, + background_color, + )); + } + } + } + + let mut last_line_end_x = origin.x + layout.width; + if let Some(boundary) = wrap_boundaries.last() { + let run = &layout.runs[boundary.run_ix]; + let glyph = &run.glyphs[boundary.glyph_ix]; + last_line_end_x -= glyph.position.x; + } + + if let Some((mut background_origin, background_color)) = current_background.take() { + if last_line_end_x == background_origin.x { + background_origin.x -= max_glyph_size.width.half() + }; + window.paint_quad(fill( + Bounds { + origin: background_origin, + size: size(last_line_end_x - background_origin.x, line_height), + }, + background_color, + )); + } + + Ok(()) + }) +} + fn aligned_origin_x( origin: Point, align_width: Pixels,