diff --git a/crates/gpui/src/text_system/line.rs b/crates/gpui/src/text_system/line.rs index cb66359206..15101e6a2f 100644 --- a/crates/gpui/src/text_system/line.rs +++ b/crates/gpui/src/text_system/line.rs @@ -1,7 +1,7 @@ use crate::{ App, Bounds, DevicePixels, Half, Hsla, LineLayout, Pixels, Point, RenderGlyphParams, Result, - ShapedGlyph, ShapedRun, SharedString, StrikethroughStyle, TextAlign, UnderlineStyle, Window, - WrapBoundary, WrappedLineLayout, black, fill, point, px, size, + SharedString, StrikethroughStyle, TextAlign, UnderlineStyle, Window, WrapBoundary, + WrappedLineLayout, black, fill, point, px, size, }; use derive_more::{Deref, DerefMut}; use smallvec::SmallVec; @@ -139,38 +139,7 @@ impl ShapedLine { /// split into two with adjusted lengths. /// - `font_size`, `ascent`, and `descent` are copied to both halves. pub fn split_at(&self, byte_index: usize) -> (ShapedLine, ShapedLine) { - let x_offset = self.layout.x_for_index(byte_index); - - // Partition glyph runs. A single run may contribute glyphs to both halves. - let mut left_runs = Vec::new(); - let mut right_runs = Vec::new(); - - for run in &self.layout.runs { - let split_pos = run.glyphs.partition_point(|g| g.index < byte_index); - - if split_pos > 0 { - left_runs.push(ShapedRun { - font_id: run.font_id, - glyphs: run.glyphs[..split_pos].to_vec(), - }); - } - - if split_pos < run.glyphs.len() { - let right_glyphs = run.glyphs[split_pos..] - .iter() - .map(|g| ShapedGlyph { - id: g.id, - position: point(g.position.x - x_offset, g.position.y), - index: g.index - byte_index, - is_emoji: g.is_emoji, - }) - .collect(); - right_runs.push(ShapedRun { - font_id: run.font_id, - glyphs: right_glyphs, - }); - } - } + let (left_layout, right_layout) = self.layout.split_at(byte_index); // Partition decoration runs. A run straddling the boundary is split into two. let mut left_decorations = SmallVec::new(); @@ -219,31 +188,14 @@ impl ShapedLine { SharedString::new(&self.text[byte_index..]) }; - let left_width = x_offset; - let right_width = self.layout.width - left_width; - let left = ShapedLine { - layout: Arc::new(LineLayout { - font_size: self.layout.font_size, - width: left_width, - ascent: self.layout.ascent, - descent: self.layout.descent, - runs: left_runs, - len: byte_index, - }), + layout: Arc::new(left_layout), text: left_text, decoration_runs: left_decorations, }; let right = ShapedLine { - layout: Arc::new(LineLayout { - font_size: self.layout.font_size, - width: right_width, - ascent: self.layout.ascent, - descent: self.layout.descent, - runs: right_runs, - len: self.layout.len - byte_index, - }), + layout: Arc::new(right_layout), text: right_text, decoration_runs: right_decorations, }; @@ -252,6 +204,64 @@ impl ShapedLine { } } +impl LineLayout { + /// Paint this layout to the window, using the given decoration runs to color + /// glyphs and draw underlines and strikethroughs. + /// + /// This is a lower-level alternative to [`ShapedLine::paint`] for callers that + /// hold a bare layout and track decorations themselves. + pub fn paint( + &self, + origin: Point, + line_height: Pixels, + align: TextAlign, + align_width: Option, + decoration_runs: &[DecorationRun], + window: &mut Window, + cx: &mut App, + ) -> Result<()> { + paint_line( + origin, + self, + line_height, + align, + align_width, + decoration_runs, + &[], + window, + cx, + ) + } + + /// Paint the background of this layout to the window, using the given + /// decoration runs to determine background colors. + /// + /// This is a lower-level alternative to [`ShapedLine::paint_background`] for + /// callers that hold a bare layout and track decorations themselves. + pub fn paint_background( + &self, + origin: Point, + line_height: Pixels, + align: TextAlign, + align_width: Option, + decoration_runs: &[DecorationRun], + window: &mut Window, + cx: &mut App, + ) -> Result<()> { + paint_line_background( + origin, + self, + line_height, + align, + align_width, + decoration_runs, + &[], + window, + cx, + ) + } +} + /// A line of text that has been shaped, decorated, and wrapped by the text layout system. #[derive(Default, Debug, Deref, DerefMut)] pub struct WrappedLine { @@ -752,7 +762,7 @@ fn aligned_origin_x( #[cfg(test)] mod tests { use super::*; - use crate::{FontId, GlyphId}; + use crate::{FontId, GlyphId, ShapedGlyph, ShapedRun}; /// Helper: build a ShapedLine from glyph descriptors without the platform text system. /// Each glyph is described as (byte_index, x_position). diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 5e85cefb8c..e7362f71e5 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -125,6 +125,68 @@ impl LineLayout { None } + /// Split this layout at a byte index, returning `(prefix, suffix)`. + /// + /// - `prefix` contains glyphs for bytes `[0, byte_index)` with original positions. + /// Its width equals the x-advance up to the split point. + /// - `suffix` contains glyphs for bytes `[byte_index, len)` with positions + /// shifted left so the first glyph starts at x=0, and byte indices rebased to 0. + /// - `font_size`, `ascent`, and `descent` are copied to both halves. + pub fn split_at(&self, byte_index: usize) -> (LineLayout, LineLayout) { + let x_offset = self.x_for_index(byte_index); + + // Partition glyph runs. A single run may contribute glyphs to both halves. + let mut left_runs = Vec::new(); + let mut right_runs = Vec::new(); + + for run in &self.runs { + let split_pos = run.glyphs.partition_point(|g| g.index < byte_index); + + if split_pos > 0 { + left_runs.push(ShapedRun { + font_id: run.font_id, + glyphs: run.glyphs[..split_pos].to_vec(), + }); + } + + if split_pos < run.glyphs.len() { + let right_glyphs = run.glyphs[split_pos..] + .iter() + .map(|g| ShapedGlyph { + id: g.id, + position: point(g.position.x - x_offset, g.position.y), + index: g.index - byte_index, + is_emoji: g.is_emoji, + }) + .collect(); + right_runs.push(ShapedRun { + font_id: run.font_id, + glyphs: right_glyphs, + }); + } + } + + let left = LineLayout { + font_size: self.font_size, + width: x_offset, + ascent: self.ascent, + descent: self.descent, + runs: left_runs, + len: byte_index, + }; + + let right = LineLayout { + font_size: self.font_size, + width: self.width - x_offset, + ascent: self.ascent, + descent: self.descent, + runs: right_runs, + len: self.len - byte_index, + }; + + (left, right) + } + fn compute_wrap_boundaries( &self, text: &str,