From 695a24d8a7a47e7456e8c559d05b0b9dbb6e7d19 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 17 Oct 2023 08:26:12 +0200 Subject: [PATCH] Checkpoint --- 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 | 291 ++++------------- crates/gpui3/src/text_system/line_layout.rs | 295 ++++++++++++++++++ crates/gpui3/src/text_system/line_wrapper.rs | 214 +++++-------- .../src/text_system/text_layout_cache.rs | 153 --------- crates/gpui3/src/window.rs | 2 +- 9 files changed, 458 insertions(+), 571 deletions(-) create mode 100644 crates/gpui3/src/text_system/line_layout.rs delete mode 100644 crates/gpui3/src/text_system/text_layout_cache.rs diff --git a/crates/gpui3/src/elements/text.rs b/crates/gpui3/src/elements/text.rs index 356b4c59d6..9b9a80ea04 100644 --- a/crates/gpui3/src/elements/text.rs +++ b/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 Element for Text { }; 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 Element for Text { 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; 1]>, + lines: SmallVec<[Line; 1]>, line_height: Pixels, } diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 87f88a9702..7caf192404 100644 --- a/crates/gpui3/src/platform.rs +++ b/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; fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result>; fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size, Vec)>; - 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, diff --git a/crates/gpui3/src/platform/mac/text_system.rs b/crates/gpui3/src/platform/mac/text_system.rs index 0dba74cc78..13c6e815fa 100644 --- a/crates/gpui3/src/platform/mac/text_system.rs +++ b/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(), diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs index 331a5702c4..f3a12f0767 100644 --- a/crates/gpui3/src/text_system.rs +++ b/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, + line_layout_cache: Arc, platform_text_system: Arc, font_ids_by_font: RwLock>, font_metrics: RwLock>, @@ -46,7 +46,7 @@ pub struct TextSystem { impl TextSystem { pub fn new(platform_text_system: Arc) -> 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, - ) -> Result; 1]>> { + ) -> Result> { 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 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, -} - -#[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, - pub index: usize, - pub is_emoji: bool, -} - #[derive(Clone, Debug, PartialEq)] pub struct RenderGlyphParams { pub(crate) font_id: FontId, diff --git a/crates/gpui3/src/text_system/line.rs b/crates/gpui3/src/text_system/line.rs index 0c7f05b2a1..deb87e9796 100644 --- a/crates/gpui3/src/text_system/line.rs +++ b/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, - decoration_runs: SmallVec<[DecorationRun; 32]>, -} - #[derive(Debug, Clone)] pub struct DecorationRun { pub len: u32, @@ -19,100 +12,62 @@ pub struct DecorationRun { pub underline: Option, } +#[derive(Clone, Default, Debug)] +pub struct Line { + pub(crate) layout: Arc, + pub(crate) decorations: SmallVec<[DecorationRun; 32]>, +} + impl Line { - pub fn new(layout: Arc, 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 { - 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() - } - - pub fn index_for_x(&self, x: Pixels) -> Option { - 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) - } + pub fn size(&self, line_height: Pixels) -> Size { + size( + self.layout.width, + line_height * (self.layout.wrap_boundaries.len() + 1), + ) } pub fn paint( &self, - bounds: Bounds, - visible_bounds: Bounds, // todo!("use clipping") + origin: Point, 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, 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 in &run.glyphs { - let glyph_origin = origin + baseline_offset + glyph.position; - if glyph_origin.x > visible_bounds.upper_right().x { - break; + 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, + )?; + } + + 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, 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,22 +109,35 @@ 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, - )?; + let max_glyph_bounds = Bounds { + origin: glyph_origin, + size: max_glyph_size, + }; + + 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, + )?; + } else { + cx.paint_glyph( + glyph_origin, + run.font_id, + glyph.id, + self.layout.layout.font_size, + color, + )?; + } } } } if let Some((underline_start, underline_style)) = current_underline.take() { - let line_end_x = origin.x + self.layout.width; + let line_end_x = origin.x + self.layout.layout.width; cx.paint_underline( underline_start, line_end_x - underline_start.x, @@ -181,123 +147,4 @@ impl Line { Ok(()) } - - pub fn paint_wrapped( - &self, - origin: Point, - _visible_bounds: Bounds, // 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, 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; - - 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, - )?; - } - - 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(); - } - } - - 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; - cx.paint_underline( - underline_origin, - line_end_x - underline_origin.x, - &underline_style, - )?; - } - - Ok(()) - } } diff --git a/crates/gpui3/src/text_system/line_layout.rs b/crates/gpui3/src/text_system/line_layout.rs new file mode 100644 index 0000000000..675a4a8760 --- /dev/null +++ b/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, +} + +#[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, + pub index: usize, + pub is_emoji: bool, +} + +impl LineLayout { + pub fn index_for_x(&self, x: Pixels) -> Option { + 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 { + 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>>, + curr_frame: RwLock>>, + platform_text_system: Arc, +} + +impl LineLayoutCache { + pub fn new(platform_text_system: Arc) -> 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, + ) -> Arc { + 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(&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(&self, state: &mut H) { + self.as_cache_key_ref().hash(state); + } +} + +impl<'a> Borrow 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(&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); + } + } +} diff --git a/crates/gpui3/src/text_system/line_wrapper.rs b/crates/gpui3/src/text_system/line_wrapper.rs index ad6623f8bc..aa2af39a81 100644 --- a/crates/gpui3/src/text_system/line_wrapper.rs +++ b/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 + '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::>(), - &[ - 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::>(), + // &[ + // ShapedBoundary { + // run_ix: 1, + // glyph_ix: 3 + // }, + // ShapedBoundary { + // run_ix: 2, + // glyph_ix: 3 + // }, + // ShapedBoundary { + // run_ix: 4, + // glyph_ix: 2 + // } + // ], + // ); + // }); + // } } diff --git a/crates/gpui3/src/text_system/text_layout_cache.rs b/crates/gpui3/src/text_system/text_layout_cache.rs deleted file mode 100644 index fe3d46e36c..0000000000 --- a/crates/gpui3/src/text_system/text_layout_cache.rs +++ /dev/null @@ -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>>, - curr_frame: RwLock>>, - platform_text_system: Arc, -} - -impl TextLayoutCache { - pub fn new(platform_text_system: Arc) -> 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 { - 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(&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(&self, state: &mut H) { - self.key().hash(state); - } -} - -impl<'a> Borrow 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(&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 - } -} diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 2861664b51..5d28a9be9b 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -45,7 +45,7 @@ type MouseEventHandler = pub struct Window { handle: AnyWindowHandle, platform_window: MainThreadOnly>, - pub(crate) display_id: DisplayId, // todo!("make private again?") + display_id: DisplayId, sprite_atlas: Arc, rem_size: Pixels, content_size: Size,