Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/src/elements/text.rs                 |  17 
crates/gpui3/src/platform.rs                      |   7 
crates/gpui3/src/platform/mac/text_system.rs      |   5 
crates/gpui3/src/text_system.rs                   |  45 -
crates/gpui3/src/text_system/line.rs              | 289 +++------------
crates/gpui3/src/text_system/line_layout.rs       | 295 +++++++++++++++++
crates/gpui3/src/text_system/line_wrapper.rs      | 214 ++++--------
crates/gpui3/src/text_system/text_layout_cache.rs | 153 --------
crates/gpui3/src/window.rs                        |   2 
9 files changed, 457 insertions(+), 570 deletions(-)

Detailed changes

crates/gpui3/src/elements/text.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    size, AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, SharedString, Size,
-    ViewContext,
+    AnyElement, BorrowWindow, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels,
+    SharedString, Size, ViewContext,
 };
 use parking_lot::Mutex;
 use smallvec::SmallVec;
@@ -90,7 +90,7 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
                 };
 
                 let size = Size {
-                    width: lines.iter().map(|line| line.width()).max().unwrap(),
+                    width: lines.iter().map(|line| line.layout.width).max().unwrap(),
                     height: line_height * lines.len(),
                 };
 
@@ -119,18 +119,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
         let line_height = element_state.line_height;
         let mut line_origin = bounds.origin;
         for line in &element_state.lines {
-            let line_bounds = Bounds {
-                origin: line_origin,
-                size: size(line.width(), line_height),
-            };
-            line.paint(line_bounds, line_bounds, line_height, cx)
-                .log_err();
-            line_origin.y += line_height;
+            line.paint(line_origin, line_height, cx).log_err();
+            line_origin.y += line.size(line_height).height;
         }
     }
 }
 
 pub struct TextElementState {
-    lines: SmallVec<[Arc<Line>; 1]>,
+    lines: SmallVec<[Line; 1]>,
     line_height: Pixels,
 }

crates/gpui3/src/platform.rs 🔗

@@ -171,12 +171,7 @@ pub trait PlatformTextSystem: Send + Sync {
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
     fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
     fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
-    fn layout_line(
-        &self,
-        text: &SharedString,
-        font_size: Pixels,
-        runs: &[(usize, FontId)],
-    ) -> LineLayout;
+    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
     fn wrap_line(
         &self,
         text: &str,

crates/gpui3/src/platform/mac/text_system.rs 🔗

@@ -151,7 +151,7 @@ impl PlatformTextSystem for MacTextSystem {
 
     fn layout_line(
         &self,
-        text: &SharedString,
+        text: &str,
         font_size: Pixels,
         font_runs: &[(usize, FontId)],
     ) -> LineLayout {
@@ -339,7 +339,7 @@ impl MacTextSystemState {
 
     fn layout_line(
         &mut self,
-        text: &SharedString,
+        text: &str,
         font_size: Pixels,
         font_runs: &[(usize, FontId)],
     ) -> LineLayout {
@@ -416,7 +416,6 @@ impl MacTextSystemState {
 
         let typographic_bounds = line.get_typographic_bounds();
         LineLayout {
-            text: text.clone(),
             width: typographic_bounds.width.into(),
             ascent: typographic_bounds.ascent.into(),
             descent: typographic_bounds.descent.into(),

crates/gpui3/src/text_system.rs 🔗

@@ -1,14 +1,14 @@
 mod font_features;
 mod line;
+mod line_layout;
 mod line_wrapper;
-mod text_layout_cache;
 
 use anyhow::anyhow;
 pub use font_features::*;
 pub use line::*;
+pub use line_layout::*;
 use line_wrapper::*;
 use smallvec::SmallVec;
-pub use text_layout_cache::*;
 
 use crate::{
     px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
@@ -35,7 +35,7 @@ pub struct FontFamilyId(pub usize);
 pub const SUBPIXEL_VARIANTS: u8 = 4;
 
 pub struct TextSystem {
-    text_layout_cache: Arc<TextLayoutCache>,
+    line_layout_cache: Arc<LineLayoutCache>,
     platform_text_system: Arc<dyn PlatformTextSystem>,
     font_ids_by_font: RwLock<HashMap<Font, FontId>>,
     font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
@@ -46,7 +46,7 @@ pub struct TextSystem {
 impl TextSystem {
     pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
         TextSystem {
-            text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
+            line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())),
             platform_text_system,
             font_metrics: RwLock::new(HashMap::default()),
             font_ids_by_font: RwLock::new(HashMap::default()),
@@ -151,7 +151,7 @@ impl TextSystem {
         font_size: Pixels,
         runs: &[TextRun],
         wrap_width: Option<Pixels>,
-    ) -> Result<SmallVec<[Arc<Line>; 1]>> {
+    ) -> Result<SmallVec<[Line; 1]>> {
         let mut runs = runs.iter().cloned().peekable();
         let mut font_runs: Vec<(usize, FontId)> =
             self.font_runs_pool.lock().pop().unwrap_or_default();
@@ -204,9 +204,12 @@ impl TextSystem {
             }
 
             let layout = self
-                .text_layout_cache
-                .layout_line(&line_text, font_size, &font_runs);
-            lines.push(Arc::new(Line::new(layout, decoration_runs)));
+                .line_layout_cache
+                .layout_line(&line_text, font_size, &font_runs, wrap_width);
+            lines.push(Line {
+                layout,
+                decorations: decoration_runs,
+            });
 
             line_start = line_end + 1; // Skip `\n` character.
             font_runs.clear();
@@ -218,7 +221,7 @@ impl TextSystem {
     }
 
     pub fn end_frame(&self) {
-        self.text_layout_cache.end_frame()
+        self.line_layout_cache.end_frame()
     }
 
     pub fn line_wrapper(
@@ -390,30 +393,6 @@ impl From<u32> for GlyphId {
     }
 }
 
-#[derive(Default, Debug)]
-pub struct LineLayout {
-    pub text: SharedString,
-    pub font_size: Pixels,
-    pub width: Pixels,
-    pub ascent: Pixels,
-    pub descent: Pixels,
-    pub runs: Vec<ShapedRun>,
-}
-
-#[derive(Debug)]
-pub struct ShapedRun {
-    pub font_id: FontId,
-    pub glyphs: SmallVec<[ShapedGlyph; 8]>,
-}
-
-#[derive(Clone, Debug)]
-pub struct ShapedGlyph {
-    pub id: GlyphId,
-    pub position: Point<Pixels>,
-    pub index: usize,
-    pub is_emoji: bool,
-}
-
 #[derive(Clone, Debug, PartialEq)]
 pub struct RenderGlyphParams {
     pub(crate) font_id: FontId,

crates/gpui3/src/text_system/line.rs 🔗

@@ -1,17 +1,10 @@
 use crate::{
-    black, point, px, Bounds, FontId, Hsla, LineLayout, Pixels, Point, ShapedBoundary, ShapedRun,
-    UnderlineStyle, WindowContext,
+    black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
+    UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
 };
-use anyhow::Result;
 use smallvec::SmallVec;
 use std::sync::Arc;
 
-#[derive(Default, Debug, Clone)]
-pub struct Line {
-    layout: Arc<LineLayout>,
-    decoration_runs: SmallVec<[DecorationRun; 32]>,
-}
-
 #[derive(Debug, Clone)]
 pub struct DecorationRun {
     pub len: u32,
@@ -19,100 +12,62 @@ pub struct DecorationRun {
     pub underline: Option<UnderlineStyle>,
 }
 
-impl Line {
-    pub fn new(layout: Arc<LineLayout>, decoration_runs: SmallVec<[DecorationRun; 32]>) -> Self {
-        Self {
-            layout,
-            decoration_runs,
-        }
-    }
-
-    pub fn runs(&self) -> &[ShapedRun] {
-        &self.layout.runs
-    }
-
-    pub fn width(&self) -> Pixels {
-        self.layout.width
-    }
-
-    pub fn font_size(&self) -> Pixels {
-        self.layout.font_size
-    }
-
-    pub fn x_for_index(&self, index: usize) -> Pixels {
-        for run in &self.layout.runs {
-            for glyph in &run.glyphs {
-                if glyph.index >= index {
-                    return glyph.position.x;
-                }
-            }
-        }
-        self.layout.width
-    }
-
-    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
-        for run in &self.layout.runs {
-            for glyph in &run.glyphs {
-                if glyph.index >= index {
-                    return Some(run.font_id);
-                }
-            }
-        }
-
-        None
-    }
-
-    pub fn len(&self) -> usize {
-        self.layout.text.len()
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.layout.text.is_empty()
-    }
+#[derive(Clone, Default, Debug)]
+pub struct Line {
+    pub(crate) layout: Arc<WrappedLineLayout>,
+    pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
+}
 
-    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
-        if x >= self.layout.width {
-            None
-        } else {
-            for run in self.layout.runs.iter().rev() {
-                for glyph in run.glyphs.iter().rev() {
-                    if glyph.position.x <= x {
-                        return Some(glyph.index);
-                    }
-                }
-            }
-            Some(0)
-        }
+impl Line {
+    pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
+        size(
+            self.layout.width,
+            line_height * (self.layout.wrap_boundaries.len() + 1),
+        )
     }
 
     pub fn paint(
         &self,
-        bounds: Bounds<Pixels>,
-        visible_bounds: Bounds<Pixels>, // todo!("use clipping")
+        origin: Point<Pixels>,
         line_height: Pixels,
         cx: &mut WindowContext,
     ) -> Result<()> {
-        let origin = bounds.origin;
-        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
-        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
+        let padding_top =
+            (line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
+        let baseline_offset = point(px(0.), padding_top + self.layout.layout.ascent);
 
-        let mut style_runs = self.decoration_runs.iter();
+        let mut style_runs = self.decorations.iter();
+        let mut wraps = self.layout.wrap_boundaries.iter().peekable();
         let mut run_end = 0;
         let mut color = black();
         let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
         let text_system = cx.text_system().clone();
 
-        for run in &self.layout.runs {
-            let max_glyph_width = text_system
-                .bounding_box(run.font_id, self.layout.font_size)?
-                .size
-                .width;
+        let mut glyph_origin = origin;
+        let mut prev_glyph_position = Point::default();
+        for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
+            let max_glyph_size = text_system
+                .bounding_box(run.font_id, self.layout.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((underline_origin, underline_style)) = current_underline.take() {
+                        cx.paint_underline(
+                            underline_origin,
+                            glyph_origin.x - underline_origin.x,
+                            &underline_style,
+                        )?;
+                    }
 
-            for glyph in &run.glyphs {
-                let glyph_origin = origin + baseline_offset + glyph.position;
-                if glyph_origin.x > visible_bounds.upper_right().x {
-                    break;
+                    glyph_origin.x = origin.x;
+                    glyph_origin.y += line_height;
                 }
+                prev_glyph_position = glyph.position;
+                let glyph_origin = glyph_origin + baseline_offset;
 
                 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
                 if glyph.index >= run_end {
@@ -126,7 +81,9 @@ impl Line {
                             current_underline.get_or_insert((
                                 point(
                                     glyph_origin.x,
-                                    origin.y + baseline_offset.y + (self.layout.descent * 0.618),
+                                    origin.y
+                                        + baseline_offset.y
+                                        + (self.layout.layout.descent * 0.618),
                                 ),
                                 UnderlineStyle {
                                     color: Some(run_underline.color.unwrap_or(style_run.color)),
@@ -144,10 +101,6 @@ impl Line {
                     }
                 }
 
-                if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
-                    continue;
-                }
-
                 if let Some((underline_origin, underline_style)) = finished_underline {
                     cx.paint_underline(
                         underline_origin,
@@ -156,144 +109,38 @@ impl Line {
                     )?;
                 }
 
-                if glyph.is_emoji {
-                    cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
-                } else {
-                    cx.paint_glyph(
-                        glyph_origin,
-                        run.font_id,
-                        glyph.id,
-                        self.layout.font_size,
-                        color,
-                    )?;
-                }
-            }
-        }
-
-        if let Some((underline_start, underline_style)) = current_underline.take() {
-            let line_end_x = origin.x + self.layout.width;
-            cx.paint_underline(
-                underline_start,
-                line_end_x - underline_start.x,
-                &underline_style,
-            )?;
-        }
-
-        Ok(())
-    }
-
-    pub fn paint_wrapped(
-        &self,
-        origin: Point<Pixels>,
-        _visible_bounds: Bounds<Pixels>, // todo!("use clipping")
-        line_height: Pixels,
-        boundaries: &[ShapedBoundary],
-        cx: &mut WindowContext,
-    ) -> Result<()> {
-        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
-        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
-
-        let mut boundaries = boundaries.into_iter().peekable();
-        let mut color_runs = self.decoration_runs.iter();
-        let mut style_run_end = 0;
-        let mut _color = black(); // todo!
-        let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
-
-        let mut glyph_origin = origin;
-        let mut prev_position = px(0.);
-        for (run_ix, run) in self.layout.runs.iter().enumerate() {
-            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
-                glyph_origin.x += glyph.position.x - prev_position;
+                let max_glyph_bounds = Bounds {
+                    origin: glyph_origin,
+                    size: max_glyph_size,
+                };
 
-                if boundaries
-                    .peek()
-                    .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
-                {
-                    boundaries.next();
-                    if let Some((underline_origin, underline_style)) = current_underline.take() {
-                        cx.paint_underline(
-                            underline_origin,
-                            glyph_origin.x - underline_origin.x,
-                            &underline_style,
+                let content_mask = cx.content_mask();
+                if max_glyph_bounds.intersects(&content_mask.bounds) {
+                    if glyph.is_emoji {
+                        cx.paint_emoji(
+                            glyph_origin,
+                            run.font_id,
+                            glyph.id,
+                            self.layout.layout.font_size,
                         )?;
-                    }
-
-                    glyph_origin = point(origin.x, glyph_origin.y + line_height);
-                }
-                prev_position = glyph.position.x;
-
-                let mut finished_underline = None;
-                if glyph.index >= style_run_end {
-                    if let Some(style_run) = color_runs.next() {
-                        style_run_end += style_run.len as usize;
-                        _color = style_run.color;
-                        if let Some((_, underline_style)) = &mut current_underline {
-                            if style_run.underline.as_ref() != Some(underline_style) {
-                                finished_underline = current_underline.take();
-                            }
-                        }
-                        if let Some(underline_style) = style_run.underline.as_ref() {
-                            current_underline.get_or_insert((
-                                glyph_origin
-                                    + point(
-                                        px(0.),
-                                        baseline_offset.y + (self.layout.descent * 0.618),
-                                    ),
-                                UnderlineStyle {
-                                    color: Some(underline_style.color.unwrap_or(style_run.color)),
-                                    thickness: underline_style.thickness,
-                                    wavy: underline_style.wavy,
-                                },
-                            ));
-                        }
                     } else {
-                        style_run_end = self.layout.text.len();
-                        _color = black();
-                        finished_underline = current_underline.take();
+                        cx.paint_glyph(
+                            glyph_origin,
+                            run.font_id,
+                            glyph.id,
+                            self.layout.layout.font_size,
+                            color,
+                        )?;
                     }
                 }
-
-                if let Some((underline_origin, underline_style)) = finished_underline {
-                    cx.paint_underline(
-                        underline_origin,
-                        glyph_origin.x - underline_origin.x,
-                        &underline_style,
-                    )?;
-                }
-
-                let text_system = cx.text_system();
-                let _glyph_bounds = Bounds {
-                    origin: glyph_origin,
-                    size: text_system
-                        .bounding_box(run.font_id, self.layout.font_size)?
-                        .size,
-                };
-                // if glyph_bounds.intersects(visible_bounds) {
-                //     if glyph.is_emoji {
-                //         cx.scene().push_image_glyph(scene::ImageGlyph {
-                //             font_id: run.font_id,
-                //             font_size: self.layout.font_size,
-                //             id: glyph.id,
-                //             origin: glyph_bounds.origin() + baseline_offset,
-                //         });
-                //     } else {
-                //         cx.scene().push_glyph(scene::Glyph {
-                //             font_id: run.font_id,
-                //             font_size: self.layout.font_size,
-                //             id: glyph.id,
-                //             origin: glyph_bounds.origin() + baseline_offset,
-                //             color,
-                //         });
-                //     }
-                // }
             }
         }
 
-        if let Some((underline_origin, underline_style)) = current_underline.take() {
-            let line_end_x = glyph_origin.x + self.layout.width - prev_position;
+        if let Some((underline_start, underline_style)) = current_underline.take() {
+            let line_end_x = origin.x + self.layout.layout.width;
             cx.paint_underline(
-                underline_origin,
-                line_end_x - underline_origin.x,
+                underline_start,
+                line_end_x - underline_start.x,
                 &underline_style,
             )?;
         }

crates/gpui3/src/text_system/line_layout.rs 🔗

@@ -0,0 +1,295 @@
+use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString};
+use derive_more::{Deref, DerefMut};
+use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
+use smallvec::SmallVec;
+use std::{
+    borrow::Borrow,
+    collections::HashMap,
+    hash::{Hash, Hasher},
+    sync::Arc,
+};
+
+#[derive(Default, Debug)]
+pub struct LineLayout {
+    pub font_size: Pixels,
+    pub width: Pixels,
+    pub ascent: Pixels,
+    pub descent: Pixels,
+    pub runs: Vec<ShapedRun>,
+}
+
+#[derive(Debug)]
+pub struct ShapedRun {
+    pub font_id: FontId,
+    pub glyphs: SmallVec<[ShapedGlyph; 8]>,
+}
+
+#[derive(Clone, Debug)]
+pub struct ShapedGlyph {
+    pub id: GlyphId,
+    pub position: Point<Pixels>,
+    pub index: usize,
+    pub is_emoji: bool,
+}
+
+impl LineLayout {
+    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
+        if x >= self.width {
+            None
+        } else {
+            for run in self.runs.iter().rev() {
+                for glyph in run.glyphs.iter().rev() {
+                    if glyph.position.x <= x {
+                        return Some(glyph.index);
+                    }
+                }
+            }
+            Some(0)
+        }
+    }
+
+    pub fn x_for_index(&self, index: usize) -> Pixels {
+        for run in &self.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return glyph.position.x;
+                }
+            }
+        }
+        self.width
+    }
+
+    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
+        for run in &self.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return Some(run.font_id);
+                }
+            }
+        }
+
+        None
+    }
+
+    fn compute_wrap_boundaries(
+        &self,
+        text: &str,
+        wrap_width: Pixels,
+    ) -> SmallVec<[WrapBoundary; 1]> {
+        let mut boundaries = SmallVec::new();
+
+        let mut first_non_whitespace_ix = None;
+        let mut last_candidate_ix = None;
+        let mut last_candidate_x = px(0.);
+        let mut last_boundary = WrapBoundary {
+            run_ix: 0,
+            glyph_ix: 0,
+        };
+        let mut last_boundary_x = px(0.);
+        let mut prev_ch = '\0';
+        let mut glyphs = self
+            .runs
+            .iter()
+            .enumerate()
+            .flat_map(move |(run_ix, run)| {
+                run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
+                    let character = text[glyph.index..].chars().next().unwrap();
+                    (
+                        WrapBoundary { run_ix, glyph_ix },
+                        character,
+                        glyph.position.x,
+                    )
+                })
+            })
+            .peekable();
+
+        while let Some((boundary, ch, x)) = glyphs.next() {
+            if ch == '\n' {
+                continue;
+            }
+
+            if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
+                last_candidate_ix = Some(boundary);
+                last_candidate_x = x;
+            }
+
+            if ch != ' ' && first_non_whitespace_ix.is_none() {
+                first_non_whitespace_ix = Some(boundary);
+            }
+
+            let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
+            let width = next_x - last_boundary_x;
+            if width > wrap_width && boundary > last_boundary {
+                if let Some(last_candidate_ix) = last_candidate_ix.take() {
+                    last_boundary = last_candidate_ix;
+                    last_boundary_x = last_candidate_x;
+                } else {
+                    last_boundary = boundary;
+                    last_boundary_x = x;
+                }
+
+                boundaries.push(last_boundary);
+            }
+            prev_ch = ch;
+        }
+
+        boundaries
+    }
+}
+
+#[derive(Deref, DerefMut, Default, Debug)]
+pub struct WrappedLineLayout {
+    #[deref]
+    #[deref_mut]
+    pub layout: LineLayout,
+    pub text: SharedString,
+    pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct WrapBoundary {
+    pub run_ix: usize,
+    pub glyph_ix: usize,
+}
+
+pub(crate) struct LineLayoutCache {
+    prev_frame: Mutex<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
+    curr_frame: RwLock<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
+    platform_text_system: Arc<dyn PlatformTextSystem>,
+}
+
+impl LineLayoutCache {
+    pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
+        Self {
+            prev_frame: Mutex::new(HashMap::new()),
+            curr_frame: RwLock::new(HashMap::new()),
+            platform_text_system,
+        }
+    }
+
+    pub fn end_frame(&self) {
+        let mut prev_frame = self.prev_frame.lock();
+        let mut curr_frame = self.curr_frame.write();
+        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
+        curr_frame.clear();
+    }
+
+    pub fn layout_line(
+        &self,
+        text: &SharedString,
+        font_size: Pixels,
+        runs: &[(usize, FontId)],
+        wrap_width: Option<Pixels>,
+    ) -> Arc<WrappedLineLayout> {
+        let key = &CacheKeyRef {
+            text,
+            font_size,
+            runs,
+        } as &dyn AsCacheKeyRef;
+        let curr_frame = self.curr_frame.upgradable_read();
+        if let Some(layout) = curr_frame.get(key) {
+            return layout.clone();
+        }
+
+        let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
+        if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
+            curr_frame.insert(key, layout.clone());
+            layout
+        } else {
+            let layout = self.platform_text_system.layout_line(text, font_size, runs);
+            let wrap_boundaries = wrap_width
+                .map(|wrap_width| layout.compute_wrap_boundaries(text.as_ref(), wrap_width))
+                .unwrap_or_default();
+            let wrapped_line = Arc::new(WrappedLineLayout {
+                layout,
+                text: text.clone(),
+                wrap_boundaries,
+            });
+
+            let key = CacheKey {
+                text: text.clone(),
+                font_size,
+                runs: SmallVec::from(runs),
+            };
+            curr_frame.insert(key, wrapped_line.clone());
+            wrapped_line
+        }
+    }
+}
+
+trait AsCacheKeyRef {
+    fn as_cache_key_ref(&self) -> CacheKeyRef;
+}
+
+#[derive(Eq)]
+struct CacheKey {
+    text: SharedString,
+    font_size: Pixels,
+    runs: SmallVec<[(usize, FontId); 1]>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+struct CacheKeyRef<'a> {
+    text: &'a str,
+    font_size: Pixels,
+    runs: &'a [(usize, FontId)],
+}
+
+impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
+    fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
+        self.as_cache_key_ref() == other.as_cache_key_ref()
+    }
+}
+
+impl<'a> Eq for (dyn AsCacheKeyRef + 'a) {}
+
+impl<'a> Hash for (dyn AsCacheKeyRef + 'a) {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.as_cache_key_ref().hash(state)
+    }
+}
+
+impl AsCacheKeyRef for CacheKey {
+    fn as_cache_key_ref(&self) -> CacheKeyRef {
+        CacheKeyRef {
+            text: &self.text,
+            font_size: self.font_size,
+            runs: self.runs.as_slice(),
+        }
+    }
+}
+
+impl PartialEq for CacheKey {
+    fn eq(&self, other: &Self) -> bool {
+        self.as_cache_key_ref().eq(&other.as_cache_key_ref())
+    }
+}
+
+impl Hash for CacheKey {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.as_cache_key_ref().hash(state);
+    }
+}
+
+impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for CacheKey {
+    fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
+        self as &dyn AsCacheKeyRef
+    }
+}
+
+impl<'a> AsCacheKeyRef for CacheKeyRef<'a> {
+    fn as_cache_key_ref(&self) -> CacheKeyRef {
+        *self
+    }
+}
+
+impl<'a> Hash for CacheKeyRef<'a> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.text.hash(state);
+        self.font_size.hash(state);
+        for (len, font_id) in self.runs {
+            len.hash(state);
+            font_id.hash(state);
+        }
+    }
+}

crates/gpui3/src/text_system/line_wrapper.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary, SharedString};
+use crate::{px, FontId, Pixels, PlatformTextSystem};
 use collections::HashMap;
 use std::{iter, sync::Arc};
 
@@ -46,7 +46,7 @@ impl LineWrapper {
                     continue;
                 }
 
-                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
+                if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
                     last_candidate_ix = ix;
                     last_candidate_width = width;
                 }
@@ -87,79 +87,6 @@ impl LineWrapper {
         })
     }
 
-    pub fn wrap_shaped_line<'a>(
-        &'a mut self,
-        str: &'a SharedString,
-        line: &'a Line,
-        wrap_width: Pixels,
-    ) -> impl Iterator<Item = ShapedBoundary> + 'a {
-        let mut first_non_whitespace_ix = None;
-        let mut last_candidate_ix = None;
-        let mut last_candidate_x = px(0.);
-        let mut last_wrap_ix = ShapedBoundary {
-            run_ix: 0,
-            glyph_ix: 0,
-        };
-        let mut last_wrap_x = px(0.);
-        let mut prev_c = '\0';
-        let mut glyphs = line
-            .runs()
-            .iter()
-            .enumerate()
-            .flat_map(move |(run_ix, run)| {
-                run.glyphs()
-                    .iter()
-                    .enumerate()
-                    .map(move |(glyph_ix, glyph)| {
-                        let character = str[glyph.index..].chars().next().unwrap();
-                        (
-                            ShapedBoundary { run_ix, glyph_ix },
-                            character,
-                            glyph.position.x,
-                        )
-                    })
-            })
-            .peekable();
-
-        iter::from_fn(move || {
-            while let Some((ix, c, x)) = glyphs.next() {
-                if c == '\n' {
-                    continue;
-                }
-
-                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
-                    last_candidate_ix = Some(ix);
-                    last_candidate_x = x;
-                }
-
-                if c != ' ' && first_non_whitespace_ix.is_none() {
-                    first_non_whitespace_ix = Some(ix);
-                }
-
-                let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
-                let width = next_x - last_wrap_x;
-                if width > wrap_width && ix > last_wrap_ix {
-                    if let Some(last_candidate_ix) = last_candidate_ix.take() {
-                        last_wrap_ix = last_candidate_ix;
-                        last_wrap_x = last_candidate_x;
-                    } else {
-                        last_wrap_ix = ix;
-                        last_wrap_x = x;
-                    }
-
-                    return Some(last_wrap_ix);
-                }
-                prev_c = c;
-            }
-
-            None
-        })
-    }
-
-    fn is_boundary(&self, prev: char, next: char) -> bool {
-        (prev == ' ') && (next != ' ')
-    }
-
     #[inline(always)]
     fn width_for_char(&mut self, c: char) -> Pixels {
         if (c as u32) < 128 {
@@ -182,8 +109,10 @@ impl LineWrapper {
     }
 
     fn compute_width_for_char(&self, c: char) -> Pixels {
+        let mut buffer = [0; 4];
+        let buffer = c.encode_utf8(&mut buffer);
         self.platform_text_system
-            .layout_line(&c.to_string().into(), self.font_size, &[(1, self.font_id)])
+            .layout_line(buffer, self.font_size, &[(1, self.font_id)])
             .width
     }
 }
@@ -203,7 +132,7 @@ impl Boundary {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{font, App, TextRun};
+    use crate::{font, App};
 
     #[test]
     fn test_wrap_line() {
@@ -268,74 +197,75 @@ mod tests {
         });
     }
 
+    // todo!("move this to a test on TextSystem::layout_text")
     // todo! repeat this test
-    #[test]
-    fn test_wrap_shaped_line() {
-        App::test().run(|cx| {
-            let text_system = cx.text_system().clone();
+    // #[test]
+    // fn test_wrap_shaped_line() {
+    //     App::test().run(|cx| {
+    //         let text_system = cx.text_system().clone();
 
-            let normal = TextRun {
-                len: 0,
-                font: font("Helvetica"),
-                color: Default::default(),
-                underline: Default::default(),
-            };
-            let bold = TextRun {
-                len: 0,
-                font: font("Helvetica").bold(),
-                color: Default::default(),
-                underline: Default::default(),
-            };
+    //         let normal = TextRun {
+    //             len: 0,
+    //             font: font("Helvetica"),
+    //             color: Default::default(),
+    //             underline: Default::default(),
+    //         };
+    //         let bold = TextRun {
+    //             len: 0,
+    //             font: font("Helvetica").bold(),
+    //             color: Default::default(),
+    //             underline: Default::default(),
+    //         };
 
-            impl TextRun {
-                fn with_len(&self, len: usize) -> Self {
-                    let mut this = self.clone();
-                    this.len = len;
-                    this
-                }
-            }
+    //         impl TextRun {
+    //             fn with_len(&self, len: usize) -> Self {
+    //                 let mut this = self.clone();
+    //                 this.len = len;
+    //                 this
+    //             }
+    //         }
 
-            let text = "aa bbb cccc ddddd eeee".into();
-            let lines = text_system
-                .layout_text(
-                    &text,
-                    px(16.),
-                    &[
-                        normal.with_len(4),
-                        bold.with_len(5),
-                        normal.with_len(6),
-                        bold.with_len(1),
-                        normal.with_len(7),
-                    ],
-                    None,
-                )
-                .unwrap();
-            let line = &lines[0];
+    //         let text = "aa bbb cccc ddddd eeee".into();
+    //         let lines = text_system
+    //             .layout_text(
+    //                 &text,
+    //                 px(16.),
+    //                 &[
+    //                     normal.with_len(4),
+    //                     bold.with_len(5),
+    //                     normal.with_len(6),
+    //                     bold.with_len(1),
+    //                     normal.with_len(7),
+    //                 ],
+    //                 None,
+    //             )
+    //             .unwrap();
+    //         let line = &lines[0];
 
-            let mut wrapper = LineWrapper::new(
-                text_system.font_id(&normal.font).unwrap(),
-                px(16.),
-                text_system.platform_text_system.clone(),
-            );
-            assert_eq!(
-                wrapper
-                    .wrap_shaped_line(&text, &line, px(72.))
-                    .collect::<Vec<_>>(),
-                &[
-                    ShapedBoundary {
-                        run_ix: 1,
-                        glyph_ix: 3
-                    },
-                    ShapedBoundary {
-                        run_ix: 2,
-                        glyph_ix: 3
-                    },
-                    ShapedBoundary {
-                        run_ix: 4,
-                        glyph_ix: 2
-                    }
-                ],
-            );
-        });
-    }
+    //         let mut wrapper = LineWrapper::new(
+    //             text_system.font_id(&normal.font).unwrap(),
+    //             px(16.),
+    //             text_system.platform_text_system.clone(),
+    //         );
+    //         assert_eq!(
+    //             wrapper
+    //                 .wrap_shaped_line(&text, &line, px(72.))
+    //                 .collect::<Vec<_>>(),
+    //             &[
+    //                 ShapedBoundary {
+    //                     run_ix: 1,
+    //                     glyph_ix: 3
+    //                 },
+    //                 ShapedBoundary {
+    //                     run_ix: 2,
+    //                     glyph_ix: 3
+    //                 },
+    //                 ShapedBoundary {
+    //                     run_ix: 4,
+    //                     glyph_ix: 2
+    //                 }
+    //             ],
+    //         );
+    //     });
+    // }
 }

crates/gpui3/src/text_system/text_layout_cache.rs 🔗

@@ -1,153 +0,0 @@
-use crate::{FontId, LineLayout, Pixels, PlatformTextSystem, ShapedGlyph, ShapedRun, SharedString};
-use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
-use smallvec::SmallVec;
-use std::{
-    borrow::Borrow,
-    collections::HashMap,
-    hash::{Hash, Hasher},
-    sync::Arc,
-};
-
-pub(crate) struct TextLayoutCache {
-    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
-    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
-    platform_text_system: Arc<dyn PlatformTextSystem>,
-}
-
-impl TextLayoutCache {
-    pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
-        Self {
-            prev_frame: Mutex::new(HashMap::new()),
-            curr_frame: RwLock::new(HashMap::new()),
-            platform_text_system,
-        }
-    }
-
-    pub fn end_frame(&self) {
-        let mut prev_frame = self.prev_frame.lock();
-        let mut curr_frame = self.curr_frame.write();
-        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
-        curr_frame.clear();
-    }
-
-    pub fn layout_line(
-        &self,
-        text: &SharedString,
-        font_size: Pixels,
-        runs: &[(usize, FontId)],
-    ) -> Arc<LineLayout> {
-        let key = &CacheKeyRef {
-            text,
-            font_size,
-            runs,
-        } as &dyn CacheKey;
-        let curr_frame = self.curr_frame.upgradable_read();
-        if let Some(layout) = curr_frame.get(key) {
-            return layout.clone();
-        }
-
-        let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
-        if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
-            curr_frame.insert(key, layout.clone());
-            layout
-        } else {
-            let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
-            let key = CacheKeyValue {
-                text: text.clone(),
-                font_size,
-                runs: SmallVec::from(runs),
-            };
-            curr_frame.insert(key, layout.clone());
-            layout
-        }
-    }
-}
-
-trait CacheKey {
-    fn key(&self) -> CacheKeyRef;
-}
-
-impl<'a> PartialEq for (dyn CacheKey + 'a) {
-    fn eq(&self, other: &dyn CacheKey) -> bool {
-        self.key() == other.key()
-    }
-}
-
-impl<'a> Eq for (dyn CacheKey + 'a) {}
-
-impl<'a> Hash for (dyn CacheKey + 'a) {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.key().hash(state)
-    }
-}
-
-#[derive(Eq)]
-struct CacheKeyValue {
-    text: SharedString,
-    font_size: Pixels,
-    runs: SmallVec<[(usize, FontId); 1]>,
-}
-
-impl CacheKey for CacheKeyValue {
-    fn key(&self) -> CacheKeyRef {
-        CacheKeyRef {
-            text: &self.text,
-            font_size: self.font_size,
-            runs: self.runs.as_slice(),
-        }
-    }
-}
-
-impl PartialEq for CacheKeyValue {
-    fn eq(&self, other: &Self) -> bool {
-        self.key().eq(&other.key())
-    }
-}
-
-impl Hash for CacheKeyValue {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.key().hash(state);
-    }
-}
-
-impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
-    fn borrow(&self) -> &(dyn CacheKey + 'a) {
-        self as &dyn CacheKey
-    }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-struct CacheKeyRef<'a> {
-    text: &'a str,
-    font_size: Pixels,
-    runs: &'a [(usize, FontId)],
-}
-
-impl<'a> CacheKey for CacheKeyRef<'a> {
-    fn key(&self) -> CacheKeyRef {
-        *self
-    }
-}
-
-impl<'a> Hash for CacheKeyRef<'a> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.text.hash(state);
-        self.font_size.hash(state);
-        for (len, font_id) in self.runs {
-            len.hash(state);
-            font_id.hash(state);
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ShapedBoundary {
-    pub run_ix: usize,
-    pub glyph_ix: usize,
-}
-
-impl ShapedRun {
-    pub fn glyphs(&self) -> &[ShapedGlyph] {
-        &self.glyphs
-    }
-}

crates/gpui3/src/window.rs 🔗

@@ -45,7 +45,7 @@ type MouseEventHandler =
 pub struct Window {
     handle: AnyWindowHandle,
     platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
-    pub(crate) display_id: DisplayId, // todo!("make private again?")
+    display_id: DisplayId,
     sprite_atlas: Arc<dyn PlatformAtlas>,
     rem_size: Pixels,
     content_size: Size<Pixels>,