From 847376cd8fb6e86f56adfa14e6106d0dff442b70 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 13 Oct 2023 14:38:13 -0600 Subject: [PATCH 01/31] WIP --- crates/gpui3/src/elements/text.rs | 5 +- crates/gpui3/src/platform.rs | 6 +-- crates/gpui3/src/platform/mac/text_system.rs | 12 ++--- crates/gpui3/src/text_system.rs | 54 +++++++++++++++---- crates/gpui3/src/text_system/line.rs | 6 +-- crates/gpui3/src/text_system/line_wrapper.rs | 3 +- .../src/text_system/text_layout_cache.rs | 8 +-- 7 files changed, 66 insertions(+), 28 deletions(-) diff --git a/crates/gpui3/src/elements/text.rs b/crates/gpui3/src/elements/text.rs index 6423e44ae0..2c5738ad96 100644 --- a/crates/gpui3/src/elements/text.rs +++ b/crates/gpui3/src/elements/text.rs @@ -74,12 +74,13 @@ impl Element for Text { let rem_size = cx.rem_size(); let layout_id = cx.request_measured_layout(Default::default(), rem_size, { let element_state = element_state.clone(); - move |_, _| { + move |known_dimensions, _| { let Some(line_layout) = text_system - .layout_line( + .layout_text( text.as_ref(), font_size, &[(text.len(), text_style.to_run())], + known_dimensions.width, // Wrap if we know the width. ) .log_err() else { diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 6d9cf3ea75..7caf192404 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -6,8 +6,8 @@ mod test; use crate::{ AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, - GlobalPixels, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, - Result, Scene, ShapedLine, SharedString, Size, + GlobalPixels, GlyphId, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, + RenderSvgParams, Result, Scene, SharedString, Size, }; use anyhow::anyhow; use async_task::Runnable; @@ -171,7 +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: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine; + 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 0320f91386..659be155f0 100644 --- a/crates/gpui3/src/platform/mac/text_system.rs +++ b/crates/gpui3/src/platform/mac/text_system.rs @@ -1,7 +1,7 @@ use crate::{ point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle, - FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph, - ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, + FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, + ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, }; use anyhow::anyhow; use cocoa::appkit::{CGFloat, CGPoint}; @@ -154,7 +154,7 @@ impl PlatformTextSystem for MacTextSystem { text: &str, font_size: Pixels, font_runs: &[(usize, FontId)], - ) -> ShapedLine { + ) -> LineLayout { self.0.write().layout_line(text, font_size, font_runs) } @@ -342,7 +342,7 @@ impl MacTextSystemState { text: &str, font_size: Pixels, font_runs: &[(usize, FontId)], - ) -> ShapedLine { + ) -> LineLayout { // Construct the attributed string, converting UTF8 ranges to UTF16 ranges. let mut string = CFMutableAttributedString::new(); { @@ -394,7 +394,7 @@ impl MacTextSystemState { let font_id = self.id_for_native_font(font); let mut ix_converter = StringIndexConverter::new(text); - let mut glyphs = Vec::new(); + let mut glyphs = SmallVec::new(); for ((glyph_id, position), glyph_utf16_ix) in run .glyphs() .iter() @@ -415,7 +415,7 @@ impl MacTextSystemState { } let typographic_bounds = line.get_typographic_bounds(); - ShapedLine { + LineLayout { 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 4ac98556b4..514d12cd83 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -7,6 +7,7 @@ use anyhow::anyhow; pub use font_features::*; pub use line::*; use line_wrapper::*; +use smallvec::SmallVec; pub use text_layout_cache::*; use crate::{ @@ -143,13 +144,15 @@ impl TextSystem { } } - pub fn layout_line( + pub fn layout_text( &self, text: &str, font_size: Pixels, runs: &[(usize, RunStyle)], - ) -> Result { - let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default(); + wrap_width: Option, + ) -> Result> { + let mut font_runs: Vec<(usize, FontId)> = + self.font_runs_pool.lock().pop().unwrap_or_default(); let mut last_font: Option<&Font> = None; for (len, style) in runs { @@ -163,14 +166,47 @@ impl TextSystem { font_runs.push((*len, self.font_id(&style.font)?)); } - let layout = self - .text_layout_cache - .layout_line(text, font_size, &font_runs); + let mut layouts = SmallVec::new(); + let mut start = 0; + let mut run_start = 0; + for line in text.lines() { + let end = start + line.len(); + let mut run_end = run_start; + let mut line_length = 0; + for (len, _) in font_runs[run_start..].iter() { + line_length += len; + if *len >= line_length { + break; + } + run_end += 1; + } + // If a run lands in the middle of a line, create an additional run for the remaining characters. + if line_length < end - start { + // Create a run for the part that fits this line. + let partial_run = font_runs[run_end]; + partial_run.0 = line_length; + layouts.push(self.text_layout_cache.layout_line( + &text[start..start + line_length], + font_size, + &font_runs[run_start..=run_end], + )); + // Update the original run to only include the part that does not fit this line. + font_runs[run_end].0 -= line_length; + } else { + layouts.push(self.text_layout_cache.layout_line( + &text[start..end], + font_size, + &font_runs[run_start..run_end], + )); + run_start = run_end; + } + start = end + 1; + } font_runs.clear(); self.font_runs_pool.lock().push(font_runs); - Ok(Line::new(layout.clone(), runs)) + Ok(layouts) } pub fn end_frame(&self) { @@ -346,7 +382,7 @@ impl From for GlyphId { } #[derive(Default, Debug)] -pub struct ShapedLine { +pub struct LineLayout { pub font_size: Pixels, pub width: Pixels, pub ascent: Pixels, @@ -358,7 +394,7 @@ pub struct ShapedLine { #[derive(Debug)] pub struct ShapedRun { pub font_id: FontId, - pub glyphs: Vec, + pub glyphs: SmallVec<[ShapedGlyph; 8]>, } #[derive(Clone, Debug)] diff --git a/crates/gpui3/src/text_system/line.rs b/crates/gpui3/src/text_system/line.rs index c63c18825b..5064046c4f 100644 --- a/crates/gpui3/src/text_system/line.rs +++ b/crates/gpui3/src/text_system/line.rs @@ -1,5 +1,5 @@ use crate::{ - black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine, + black, point, px, Bounds, FontId, Hsla, LineLayout, Pixels, Point, RunStyle, ShapedBoundary, ShapedRun, UnderlineStyle, WindowContext, }; use anyhow::Result; @@ -8,7 +8,7 @@ use std::sync::Arc; #[derive(Default, Debug, Clone)] pub struct Line { - layout: Arc, + layout: Arc, style_runs: SmallVec<[StyleRun; 32]>, } @@ -20,7 +20,7 @@ struct StyleRun { } impl Line { - pub fn new(layout: Arc, runs: &[(usize, RunStyle)]) -> Self { + pub fn new(layout: Arc, runs: &[(usize, RunStyle)]) -> Self { let mut style_runs = SmallVec::new(); for (len, style) in runs { style_runs.push(StyleRun { diff --git a/crates/gpui3/src/text_system/line_wrapper.rs b/crates/gpui3/src/text_system/line_wrapper.rs index 87c75cc9ac..4c0214ce80 100644 --- a/crates/gpui3/src/text_system/line_wrapper.rs +++ b/crates/gpui3/src/text_system/line_wrapper.rs @@ -287,7 +287,7 @@ mod tests { let text = "aa bbb cccc ddddd eeee"; let line = text_system - .layout_line( + .layout_text( text, px(16.), &[ @@ -297,6 +297,7 @@ mod tests { (1, bold.clone()), (7, normal.clone()), ], + None, ) .unwrap(); diff --git a/crates/gpui3/src/text_system/text_layout_cache.rs b/crates/gpui3/src/text_system/text_layout_cache.rs index e9768aaf21..1c3432aea2 100644 --- a/crates/gpui3/src/text_system/text_layout_cache.rs +++ b/crates/gpui3/src/text_system/text_layout_cache.rs @@ -1,4 +1,4 @@ -use crate::{FontId, Pixels, PlatformTextSystem, ShapedGlyph, ShapedLine, ShapedRun}; +use crate::{FontId, LineLayout, Pixels, PlatformTextSystem, ShapedGlyph, ShapedRun}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; use std::{ @@ -9,8 +9,8 @@ use std::{ }; pub(crate) struct TextLayoutCache { - prev_frame: Mutex>>, - curr_frame: RwLock>>, + prev_frame: Mutex>>, + curr_frame: RwLock>>, platform_text_system: Arc, } @@ -35,7 +35,7 @@ impl TextLayoutCache { text: &'a str, font_size: Pixels, runs: &[(usize, FontId)], - ) -> Arc { + ) -> Arc { let key = &CacheKeyRef { text, font_size, From 938dd8b9cab7803718372f4646440325349de9be Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 16 Oct 2023 20:16:35 +0200 Subject: [PATCH 02/31] Checkpoint --- crates/gpui/src/fonts.rs | 5 +- crates/gpui3/src/elements/text.rs | 43 ++++---- crates/gpui3/src/geometry.rs | 8 ++ crates/gpui3/src/text_system.rs | 102 ++++++++++-------- crates/gpui3/src/text_system/line.rs | 81 +++++++------- crates/gpui3/src/text_system/line_wrapper.rs | 3 +- .../src/text_system/text_layout_cache.rs | 4 +- 7 files changed, 130 insertions(+), 116 deletions(-) diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index 19ff468088..f360ef933f 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -155,8 +155,9 @@ impl Refineable for TextStyleRefinement { } } - fn refined(self, refinement: Self::Refinement) -> Self { - todo!() + fn refined(mut self, refinement: Self::Refinement) -> Self { + self.refine(&refinement); + self } } diff --git a/crates/gpui3/src/elements/text.rs b/crates/gpui3/src/elements/text.rs index 2c5738ad96..b511bfc977 100644 --- a/crates/gpui3/src/elements/text.rs +++ b/crates/gpui3/src/elements/text.rs @@ -1,7 +1,8 @@ use crate::{ - AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, Size, ViewContext, + size, AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, Size, ViewContext, }; use parking_lot::Mutex; +use smallvec::SmallVec; use std::{marker::PhantomData, sync::Arc}; use util::{arc_cow::ArcCow, ResultExt}; @@ -75,7 +76,7 @@ impl Element for Text { let layout_id = cx.request_measured_layout(Default::default(), rem_size, { let element_state = element_state.clone(); move |known_dimensions, _| { - let Some(line_layout) = text_system + let Some(lines) = text_system .layout_text( text.as_ref(), font_size, @@ -88,14 +89,13 @@ impl Element for Text { }; let size = Size { - width: line_layout.width(), - height: line_height, + width: lines.iter().map(|line| line.width()).max().unwrap(), + height: line_height * lines.len(), }; - element_state.lock().replace(TextElementState { - line: Arc::new(line_layout), - line_height, - }); + element_state + .lock() + .replace(TextElementState { lines, line_height }); size } @@ -111,22 +111,25 @@ impl Element for Text { element_state: &mut Self::ElementState, cx: &mut ViewContext, ) { - let line; - let line_height; - { - let element_state = element_state.lock(); - let element_state = element_state - .as_ref() - .expect("measurement has not been performed"); - line = element_state.line.clone(); - line_height = element_state.line_height; + let element_state = element_state.lock(); + let element_state = element_state + .as_ref() + .expect("measurement has not been performed"); + 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(bounds, bounds, line_height, cx).log_err(); } } pub struct TextElementState { - line: Arc, + lines: SmallVec<[Arc; 1]>, line_height: Pixels, } diff --git a/crates/gpui3/src/geometry.rs b/crates/gpui3/src/geometry.rs index 33d6809044..e7933fcbfa 100644 --- a/crates/gpui3/src/geometry.rs +++ b/crates/gpui3/src/geometry.rs @@ -656,6 +656,14 @@ impl Mul for Pixels { } } +impl Mul for Pixels { + type Output = Pixels; + + fn mul(self, other: usize) -> Pixels { + Pixels(self.0 * other as f32) + } +} + impl Mul for f32 { type Output = Pixels; diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs index 514d12cd83..a50cbffc6c 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -18,6 +18,7 @@ use collections::HashMap; use core::fmt; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use std::{ + cmp, fmt::{Debug, Display, Formatter}, hash::{Hash, Hasher}, ops::{Deref, DerefMut}, @@ -150,63 +151,72 @@ impl TextSystem { font_size: Pixels, runs: &[(usize, RunStyle)], wrap_width: Option, - ) -> Result> { + ) -> Result; 1]>> { + let mut runs = runs + .iter() + .map(|(run_len, style)| (*run_len, style)) + .peekable(); let mut font_runs: Vec<(usize, FontId)> = self.font_runs_pool.lock().pop().unwrap_or_default(); - let mut last_font: Option<&Font> = None; - for (len, style) in runs { - if let Some(last_font) = last_font.as_ref() { - if **last_font == style.font { - font_runs.last_mut().unwrap().0 += len; - continue; - } - } - last_font = Some(&style.font); - font_runs.push((*len, self.font_id(&style.font)?)); - } + let mut lines = SmallVec::new(); + let mut line_start = 0; + for line in text.split('\n') { + let line_end = line_start + line.len(); - let mut layouts = SmallVec::new(); - let mut start = 0; - let mut run_start = 0; - for line in text.lines() { - let end = start + line.len(); - let mut run_end = run_start; - let mut line_length = 0; - for (len, _) in font_runs[run_start..].iter() { - line_length += len; - if *len >= line_length { + let mut last_font: Option<&Font> = None; + let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new(); + let mut run_start = line_start; + while run_start < line_end { + let Some((run_len, run_style)) = runs.peek_mut() else { break; + }; + + let run_len_within_line = cmp::min(line_end, run_start + *run_len) - run_start; + + if last_font == Some(&run_style.font) { + font_runs.last_mut().unwrap().0 += run_len_within_line; + } else { + last_font = Some(&run_style.font); + font_runs.push(( + run_len_within_line, + self.platform_text_system.font_id(&run_style.font)?, + )); } - run_end += 1; + + if decoration_runs.last().map_or(false, |last_run| { + last_run.color == run_style.color && last_run.underline == run_style.underline + }) { + decoration_runs.last_mut().unwrap().len += run_len_within_line as u32; + } else { + decoration_runs.push(DecorationRun { + len: run_len_within_line as u32, + color: run_style.color, + underline: run_style.underline.clone(), + }); + } + + if run_len_within_line == *run_len { + runs.next(); + } else { + // Preserve the remainder of the run for the next line + *run_len -= run_len_within_line; + } + run_start += run_len_within_line; } - // If a run lands in the middle of a line, create an additional run for the remaining characters. - if line_length < end - start { - // Create a run for the part that fits this line. - let partial_run = font_runs[run_end]; - partial_run.0 = line_length; - layouts.push(self.text_layout_cache.layout_line( - &text[start..start + line_length], - font_size, - &font_runs[run_start..=run_end], - )); - // Update the original run to only include the part that does not fit this line. - font_runs[run_end].0 -= line_length; - } else { - layouts.push(self.text_layout_cache.layout_line( - &text[start..end], - font_size, - &font_runs[run_start..run_end], - )); - run_start = run_end; - } - start = end + 1; + + let layout = self + .text_layout_cache + .layout_line(line, font_size, &font_runs); + lines.push(Arc::new(Line::new(layout, decoration_runs))); + + line_start = line_end + 1; // Skip `\n` character. + font_runs.clear(); } - font_runs.clear(); self.font_runs_pool.lock().push(font_runs); - Ok(layouts) + Ok(lines) } pub fn end_frame(&self) { diff --git a/crates/gpui3/src/text_system/line.rs b/crates/gpui3/src/text_system/line.rs index 5064046c4f..e5ccc38c12 100644 --- a/crates/gpui3/src/text_system/line.rs +++ b/crates/gpui3/src/text_system/line.rs @@ -1,6 +1,6 @@ use crate::{ - black, point, px, Bounds, FontId, Hsla, LineLayout, Pixels, Point, RunStyle, ShapedBoundary, - ShapedRun, UnderlineStyle, WindowContext, + black, point, px, Bounds, FontId, Hsla, LineLayout, Pixels, Point, ShapedBoundary, ShapedRun, + UnderlineStyle, WindowContext, }; use anyhow::Result; use smallvec::SmallVec; @@ -9,27 +9,22 @@ use std::sync::Arc; #[derive(Default, Debug, Clone)] pub struct Line { layout: Arc, - style_runs: SmallVec<[StyleRun; 32]>, + decoration_runs: SmallVec<[DecorationRun; 32]>, } #[derive(Debug, Clone)] -struct StyleRun { - len: u32, - color: Hsla, - underline: UnderlineStyle, +pub struct DecorationRun { + pub len: u32, + pub color: Hsla, + pub underline: Option, } impl Line { - pub fn new(layout: Arc, runs: &[(usize, RunStyle)]) -> Self { - let mut style_runs = SmallVec::new(); - for (len, style) in runs { - style_runs.push(StyleRun { - len: *len as u32, - color: style.color, - underline: style.underline.clone().unwrap_or_default(), - }); + pub fn new(layout: Arc, decoration_runs: SmallVec<[DecorationRun; 32]>) -> Self { + Self { + layout, + decoration_runs, } - Self { layout, style_runs } } pub fn runs(&self) -> &[ShapedRun] { @@ -101,10 +96,10 @@ impl Line { 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 style_runs = self.style_runs.iter(); + let mut style_runs = self.decoration_runs.iter(); let mut run_end = 0; let mut color = black(); - let mut underline = None; + let mut current_underline: Option<(Point, UnderlineStyle)> = None; let text_system = cx.text_system().clone(); for run in &self.layout.runs { @@ -122,23 +117,21 @@ impl Line { let mut finished_underline: Option<(Point, UnderlineStyle)> = None; if glyph.index >= run_end { if let Some(style_run) = style_runs.next() { - if let Some((_, underline_style)) = &mut underline { - if style_run.underline != *underline_style { - finished_underline = underline.take(); + if let Some((_, underline_style)) = &mut current_underline { + if style_run.underline.as_ref() != Some(underline_style) { + finished_underline = current_underline.take(); } } - if style_run.underline.thickness > px(0.) { - underline.get_or_insert(( + if let Some(run_underline) = style_run.underline.as_ref() { + current_underline.get_or_insert(( point( glyph_origin.x, origin.y + baseline_offset.y + (self.layout.descent * 0.618), ), UnderlineStyle { - color: Some( - style_run.underline.color.unwrap_or(style_run.color), - ), - thickness: style_run.underline.thickness, - wavy: style_run.underline.wavy, + color: Some(run_underline.color.unwrap_or(style_run.color)), + thickness: run_underline.thickness, + wavy: run_underline.wavy, }, )); } @@ -147,7 +140,7 @@ impl Line { color = style_run.color; } else { run_end = self.layout.len; - finished_underline = underline.take(); + finished_underline = current_underline.take(); } } @@ -177,7 +170,7 @@ impl Line { } } - if let Some((underline_start, underline_style)) = underline.take() { + if let Some((underline_start, underline_style)) = current_underline.take() { let line_end_x = origin.x + self.layout.width; cx.paint_underline( underline_start, @@ -201,10 +194,10 @@ impl Line { let baseline_offset = point(px(0.), padding_top + self.layout.ascent); let mut boundaries = boundaries.into_iter().peekable(); - let mut color_runs = self.style_runs.iter(); + let mut color_runs = self.decoration_runs.iter(); let mut style_run_end = 0; let mut _color = black(); // todo! - let mut underline: Option<(Point, UnderlineStyle)> = None; + let mut current_underline: Option<(Point, UnderlineStyle)> = None; let mut glyph_origin = origin; let mut prev_position = px(0.); @@ -217,7 +210,7 @@ impl Line { .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix) { boundaries.next(); - if let Some((underline_origin, underline_style)) = underline.take() { + if let Some((underline_origin, underline_style)) = current_underline.take() { cx.paint_underline( underline_origin, glyph_origin.x - underline_origin.x, @@ -234,31 +227,29 @@ impl Line { 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 underline { - if style_run.underline != *underline_style { - finished_underline = underline.take(); + if let Some((_, underline_style)) = &mut current_underline { + if style_run.underline.as_ref() != Some(underline_style) { + finished_underline = current_underline.take(); } } - if style_run.underline.thickness > px(0.) { - underline.get_or_insert(( + 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( - style_run.underline.color.unwrap_or(style_run.color), - ), - thickness: style_run.underline.thickness, - wavy: style_run.underline.wavy, + color: Some(underline_style.color.unwrap_or(style_run.color)), + thickness: underline_style.thickness, + wavy: underline_style.wavy, }, )); } } else { style_run_end = self.layout.len; _color = black(); - finished_underline = underline.take(); + finished_underline = current_underline.take(); } } @@ -298,7 +289,7 @@ impl Line { } } - if let Some((underline_origin, underline_style)) = underline.take() { + 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, diff --git a/crates/gpui3/src/text_system/line_wrapper.rs b/crates/gpui3/src/text_system/line_wrapper.rs index 4c0214ce80..fe9f10c6ba 100644 --- a/crates/gpui3/src/text_system/line_wrapper.rs +++ b/crates/gpui3/src/text_system/line_wrapper.rs @@ -286,7 +286,7 @@ mod tests { }; let text = "aa bbb cccc ddddd eeee"; - let line = text_system + let lines = text_system .layout_text( text, px(16.), @@ -300,6 +300,7 @@ mod tests { None, ) .unwrap(); + let line = &lines[0]; let mut wrapper = LineWrapper::new( text_system.font_id(&normal.font).unwrap(), diff --git a/crates/gpui3/src/text_system/text_layout_cache.rs b/crates/gpui3/src/text_system/text_layout_cache.rs index 1c3432aea2..47e6fa3b47 100644 --- a/crates/gpui3/src/text_system/text_layout_cache.rs +++ b/crates/gpui3/src/text_system/text_layout_cache.rs @@ -15,11 +15,11 @@ pub(crate) struct TextLayoutCache { } impl TextLayoutCache { - pub fn new(fonts: Arc) -> Self { + pub fn new(platform_text_system: Arc) -> Self { Self { prev_frame: Mutex::new(HashMap::new()), curr_frame: RwLock::new(HashMap::new()), - platform_text_system: fonts, + platform_text_system, } } From 3127c78bc793951d38bd231f964b23f193e993d2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 16 Oct 2023 19:14:22 +0200 Subject: [PATCH 03/31] Fix compile error --- .../src/derive_refineable.rs | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/crates/refineable/derive_refineable/src/derive_refineable.rs b/crates/refineable/derive_refineable/src/derive_refineable.rs index 1462b48078..ed233e1b0d 100644 --- a/crates/refineable/derive_refineable/src/derive_refineable.rs +++ b/crates/refineable/derive_refineable/src/derive_refineable.rs @@ -157,7 +157,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { }) .collect(); - let refinement_refined_assignments: Vec = fields + let refinement_refined_assigments: Vec = fields .iter() .map(|field| { let name = &field.ident; @@ -169,14 +169,37 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { } } else { quote! { - if refinement.#name.is_some() { - self.#name = refinement.#name; + if let Some(value) = refinement.#name { + self.#name = Some(value); } } } }) .collect(); + let from_refinement_assigments: Vec = fields + .iter() + .map(|field| { + let name = &field.ident; + let is_refineable = is_refineable_field(field); + let is_optional = is_optional_field(field); + + if is_refineable { + quote! { + #name: value.#name.into(), + } + } else if is_optional { + quote! { + #name: value.#name.map(|v| v.into()), + } + } else { + quote! { + #name: value.#name.map(|v| v.into()).unwrap_or_default(), + } + } + }) + .collect(); + let debug_impl = if impl_debug_on_refinement { let refinement_field_debugs: Vec = fields .iter() @@ -243,11 +266,21 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { } fn refined(mut self, refinement: Self::Refinement) -> Self { - #( #refinement_refined_assignments )* + #( #refinement_refined_assigments )* self } } + impl #impl_generics From<#refinement_ident #ty_generics> for #ident #ty_generics + #where_clause + { + fn from(value: #refinement_ident #ty_generics) -> Self { + Self { + #( #from_refinement_assigments )* + } + } + } + impl #impl_generics ::core::default::Default for #refinement_ident #ty_generics #where_clause { @@ -273,7 +306,6 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { #debug_impl }; - gen.into() } From 0df1eb71cb7665bac174c0fdb2764d6690af8bf9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 17 Oct 2023 05:08:56 +0200 Subject: [PATCH 04/31] Use cloned --- crates/gpui3/src/text_system.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs index a50cbffc6c..e6395c4022 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -152,10 +152,7 @@ impl TextSystem { runs: &[(usize, RunStyle)], wrap_width: Option, ) -> Result; 1]>> { - let mut runs = runs - .iter() - .map(|(run_len, style)| (*run_len, style)) - .peekable(); + let mut runs = runs.iter().cloned().peekable(); let mut font_runs: Vec<(usize, FontId)> = self.font_runs_pool.lock().pop().unwrap_or_default(); @@ -164,7 +161,7 @@ impl TextSystem { for line in text.split('\n') { let line_end = line_start + line.len(); - let mut last_font: Option<&Font> = None; + let mut last_font: Option = None; let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new(); let mut run_start = line_start; while run_start < line_end { @@ -174,10 +171,10 @@ impl TextSystem { let run_len_within_line = cmp::min(line_end, run_start + *run_len) - run_start; - if last_font == Some(&run_style.font) { + if last_font == Some(run_style.font.clone()) { font_runs.last_mut().unwrap().0 += run_len_within_line; } else { - last_font = Some(&run_style.font); + last_font = Some(run_style.font.clone()); font_runs.push(( run_len_within_line, self.platform_text_system.font_id(&run_style.font)?, From bf49f55c95980e992530d0cd0c4779a187b666c5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 17 Oct 2023 05:15:00 +0200 Subject: [PATCH 05/31] Include length in run to not use tuples --- crates/gpui3/src/elements/text.rs | 2 +- crates/gpui3/src/style.rs | 9 ++++--- crates/gpui3/src/text_system.rs | 25 ++++++++++--------- crates/gpui3/src/text_system/line_wrapper.rs | 26 ++++++++++++++------ 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/crates/gpui3/src/elements/text.rs b/crates/gpui3/src/elements/text.rs index b511bfc977..2e1254c4ab 100644 --- a/crates/gpui3/src/elements/text.rs +++ b/crates/gpui3/src/elements/text.rs @@ -80,7 +80,7 @@ impl Element for Text { .layout_text( text.as_ref(), font_size, - &[(text.len(), text_style.to_run())], + &[text_style.to_run(text.len())], known_dimensions.width, // Wrap if we know the width. ) .log_err() diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index 24e0dfaa17..17cd2dc43f 100644 --- a/crates/gpui3/src/style.rs +++ b/crates/gpui3/src/style.rs @@ -1,8 +1,8 @@ use crate::{ phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, - FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString, - Size, SizeRefinement, ViewContext, WindowContext, + FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, SharedString, Size, + SizeRefinement, TextRun, ViewContext, WindowContext, }; use refineable::{Cascade, Refineable}; use smallvec::SmallVec; @@ -161,8 +161,9 @@ impl TextStyle { Ok(self) } - pub fn to_run(&self) -> RunStyle { - RunStyle { + pub fn to_run(&self, len: usize) -> TextRun { + TextRun { + len, font: Font { family: self.font_family.clone(), features: Default::default(), diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs index e6395c4022..be6cdef129 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -149,7 +149,7 @@ impl TextSystem { &self, text: &str, font_size: Pixels, - runs: &[(usize, RunStyle)], + runs: &[TextRun], wrap_width: Option, ) -> Result; 1]>> { let mut runs = runs.iter().cloned().peekable(); @@ -165,39 +165,39 @@ impl TextSystem { let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new(); let mut run_start = line_start; while run_start < line_end { - let Some((run_len, run_style)) = runs.peek_mut() else { + let Some(run) = runs.peek_mut() else { break; }; - let run_len_within_line = cmp::min(line_end, run_start + *run_len) - run_start; + let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start; - if last_font == Some(run_style.font.clone()) { + if last_font == Some(run.font.clone()) { font_runs.last_mut().unwrap().0 += run_len_within_line; } else { - last_font = Some(run_style.font.clone()); + last_font = Some(run.font.clone()); font_runs.push(( run_len_within_line, - self.platform_text_system.font_id(&run_style.font)?, + self.platform_text_system.font_id(&run.font)?, )); } if decoration_runs.last().map_or(false, |last_run| { - last_run.color == run_style.color && last_run.underline == run_style.underline + last_run.color == run.color && last_run.underline == run.underline }) { decoration_runs.last_mut().unwrap().len += run_len_within_line as u32; } else { decoration_runs.push(DecorationRun { len: run_len_within_line as u32, - color: run_style.color, - underline: run_style.underline.clone(), + color: run.color, + underline: run.underline.clone(), }); } - if run_len_within_line == *run_len { + if run_len_within_line == run.len { runs.next(); } else { // Preserve the remainder of the run for the next line - *run_len -= run_len_within_line; + run.len -= run_len_within_line; } run_start += run_len_within_line; } @@ -360,7 +360,8 @@ impl Display for FontStyle { } #[derive(Clone, Debug, PartialEq, Eq)] -pub struct RunStyle { +pub struct TextRun { + pub len: usize, pub font: Font, pub color: Hsla, pub underline: Option, diff --git a/crates/gpui3/src/text_system/line_wrapper.rs b/crates/gpui3/src/text_system/line_wrapper.rs index fe9f10c6ba..2236a0144e 100644 --- a/crates/gpui3/src/text_system/line_wrapper.rs +++ b/crates/gpui3/src/text_system/line_wrapper.rs @@ -203,7 +203,7 @@ impl Boundary { #[cfg(test)] mod tests { use super::*; - use crate::{font, App, RunStyle}; + use crate::{font, App, TextRun}; #[test] fn test_wrap_line() { @@ -274,28 +274,38 @@ mod tests { App::test().run(|cx| { let text_system = cx.text_system().clone(); - let normal = RunStyle { + let normal = TextRun { + len: 0, font: font("Helvetica"), color: Default::default(), underline: Default::default(), }; - let bold = RunStyle { + 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 + } + } + let text = "aa bbb cccc ddddd eeee"; let lines = text_system .layout_text( text, px(16.), &[ - (4, normal.clone()), - (5, bold.clone()), - (6, normal.clone()), - (1, bold.clone()), - (7, normal.clone()), + normal.with_len(4), + bold.with_len(5), + normal.with_len(6), + bold.with_len(1), + normal.with_len(7), ], None, ) From 247214253205e806f8851650cc225de617677532 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 17 Oct 2023 06:08:53 +0200 Subject: [PATCH 06/31] Checkpoint --- crates/gpui3/src/elements/text.rs | 15 ++++++++------- crates/gpui3/src/gpui3.rs | 3 ++- crates/gpui3/src/platform.rs | 7 ++++++- crates/gpui3/src/platform/mac/text_system.rs | 6 +++--- crates/gpui3/src/text_system.rs | 11 ++++++----- crates/gpui3/src/text_system/line.rs | 8 ++++---- crates/gpui3/src/text_system/line_wrapper.rs | 12 ++++++------ crates/gpui3/src/text_system/text_layout_cache.rs | 14 +++++++------- 8 files changed, 42 insertions(+), 34 deletions(-) diff --git a/crates/gpui3/src/elements/text.rs b/crates/gpui3/src/elements/text.rs index 2e1254c4ab..356b4c59d6 100644 --- a/crates/gpui3/src/elements/text.rs +++ b/crates/gpui3/src/elements/text.rs @@ -1,12 +1,13 @@ use crate::{ - size, AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, Size, ViewContext, + size, AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, SharedString, Size, + ViewContext, }; use parking_lot::Mutex; use smallvec::SmallVec; use std::{marker::PhantomData, sync::Arc}; -use util::{arc_cow::ArcCow, ResultExt}; +use util::ResultExt; -impl IntoAnyElement for ArcCow<'static, str> { +impl IntoAnyElement for SharedString { fn into_any(self) -> AnyElement { Text { text: self, @@ -19,7 +20,7 @@ impl IntoAnyElement for ArcCow<'static, str> { impl IntoAnyElement for &'static str { fn into_any(self) -> AnyElement { Text { - text: ArcCow::from(self), + text: self.into(), state_type: PhantomData, } .into_any() @@ -31,7 +32,7 @@ impl IntoAnyElement for &'static str { impl IntoAnyElement for String { fn into_any(self) -> AnyElement { Text { - text: ArcCow::from(self), + text: self.into(), state_type: PhantomData, } .into_any() @@ -39,7 +40,7 @@ impl IntoAnyElement for String { } pub struct Text { - text: ArcCow<'static, str>, + text: SharedString, state_type: PhantomData, } @@ -78,7 +79,7 @@ impl Element for Text { move |known_dimensions, _| { let Some(lines) = text_system .layout_text( - text.as_ref(), + &text, font_size, &[text_style.to_run(text.len())], known_dimensions.width, // Wrap if we know the width. diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index dbd14a9675..ea4a15f274 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -51,6 +51,7 @@ pub use util::arc_cow::ArcCow; pub use view::*; pub use window::*; +use derive_more::{Deref, DerefMut}; use std::{ any::{Any, TypeId}, mem, @@ -180,7 +181,7 @@ impl Flatten for Result { } } -#[derive(Clone, Eq, PartialEq, Hash)] +#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)] pub struct SharedString(ArcCow<'static, str>); impl Default for SharedString { diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 7caf192404..87f88a9702 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -171,7 +171,12 @@ 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: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout; + fn layout_line( + &self, + text: &SharedString, + 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 659be155f0..0dba74cc78 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: &str, + text: &SharedString, font_size: Pixels, font_runs: &[(usize, FontId)], ) -> LineLayout { @@ -339,7 +339,7 @@ impl MacTextSystemState { fn layout_line( &mut self, - text: &str, + text: &SharedString, font_size: Pixels, font_runs: &[(usize, FontId)], ) -> LineLayout { @@ -416,12 +416,12 @@ 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(), runs, font_size, - len: text.len(), } } diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs index be6cdef129..331a5702c4 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -147,7 +147,7 @@ impl TextSystem { pub fn layout_text( &self, - text: &str, + text: &SharedString, font_size: Pixels, runs: &[TextRun], wrap_width: Option, @@ -158,8 +158,9 @@ impl TextSystem { let mut lines = SmallVec::new(); let mut line_start = 0; - for line in text.split('\n') { - let line_end = line_start + line.len(); + for line_text in text.split('\n') { + let line_text = SharedString::from(line_text.to_string()); + let line_end = line_start + line_text.len(); let mut last_font: Option = None; let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new(); @@ -204,7 +205,7 @@ impl TextSystem { let layout = self .text_layout_cache - .layout_line(line, font_size, &font_runs); + .layout_line(&line_text, font_size, &font_runs); lines.push(Arc::new(Line::new(layout, decoration_runs))); line_start = line_end + 1; // Skip `\n` character. @@ -391,12 +392,12 @@ 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, - pub len: usize, } #[derive(Debug)] diff --git a/crates/gpui3/src/text_system/line.rs b/crates/gpui3/src/text_system/line.rs index e5ccc38c12..0c7f05b2a1 100644 --- a/crates/gpui3/src/text_system/line.rs +++ b/crates/gpui3/src/text_system/line.rs @@ -63,11 +63,11 @@ impl Line { } pub fn len(&self) -> usize { - self.layout.len + self.layout.text.len() } pub fn is_empty(&self) -> bool { - self.layout.len == 0 + self.layout.text.is_empty() } pub fn index_for_x(&self, x: Pixels) -> Option { @@ -139,7 +139,7 @@ impl Line { run_end += style_run.len as usize; color = style_run.color; } else { - run_end = self.layout.len; + run_end = self.layout.text.len(); finished_underline = current_underline.take(); } } @@ -247,7 +247,7 @@ impl Line { )); } } else { - style_run_end = self.layout.len; + style_run_end = self.layout.text.len(); _color = black(); finished_underline = current_underline.take(); } diff --git a/crates/gpui3/src/text_system/line_wrapper.rs b/crates/gpui3/src/text_system/line_wrapper.rs index 2236a0144e..ad6623f8bc 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}; +use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary, SharedString}; use collections::HashMap; use std::{iter, sync::Arc}; @@ -89,7 +89,7 @@ impl LineWrapper { pub fn wrap_shaped_line<'a>( &'a mut self, - str: &'a str, + str: &'a SharedString, line: &'a Line, wrap_width: Pixels, ) -> impl Iterator + 'a { @@ -183,7 +183,7 @@ impl LineWrapper { fn compute_width_for_char(&self, c: char) -> Pixels { self.platform_text_system - .layout_line(&c.to_string(), self.font_size, &[(1, self.font_id)]) + .layout_line(&c.to_string().into(), self.font_size, &[(1, self.font_id)]) .width } } @@ -295,10 +295,10 @@ mod tests { } } - let text = "aa bbb cccc ddddd eeee"; + let text = "aa bbb cccc ddddd eeee".into(); let lines = text_system .layout_text( - text, + &text, px(16.), &[ normal.with_len(4), @@ -319,7 +319,7 @@ mod tests { ); assert_eq!( wrapper - .wrap_shaped_line(text, &line, px(72.)) + .wrap_shaped_line(&text, &line, px(72.)) .collect::>(), &[ ShapedBoundary { diff --git a/crates/gpui3/src/text_system/text_layout_cache.rs b/crates/gpui3/src/text_system/text_layout_cache.rs index 47e6fa3b47..fe3d46e36c 100644 --- a/crates/gpui3/src/text_system/text_layout_cache.rs +++ b/crates/gpui3/src/text_system/text_layout_cache.rs @@ -1,4 +1,4 @@ -use crate::{FontId, LineLayout, Pixels, PlatformTextSystem, ShapedGlyph, ShapedRun}; +use crate::{FontId, LineLayout, Pixels, PlatformTextSystem, ShapedGlyph, ShapedRun, SharedString}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; use std::{ @@ -30,9 +30,9 @@ impl TextLayoutCache { curr_frame.clear(); } - pub fn layout_line<'a>( - &'a self, - text: &'a str, + pub fn layout_line( + &self, + text: &SharedString, font_size: Pixels, runs: &[(usize, FontId)], ) -> Arc { @@ -53,7 +53,7 @@ impl TextLayoutCache { } else { let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs)); let key = CacheKeyValue { - text: text.into(), + text: text.clone(), font_size, runs: SmallVec::from(runs), }; @@ -83,7 +83,7 @@ impl<'a> Hash for (dyn CacheKey + 'a) { #[derive(Eq)] struct CacheKeyValue { - text: String, + text: SharedString, font_size: Pixels, runs: SmallVec<[(usize, FontId); 1]>, } @@ -91,7 +91,7 @@ struct CacheKeyValue { impl CacheKey for CacheKeyValue { fn key(&self) -> CacheKeyRef { CacheKeyRef { - text: self.text.as_str(), + text: &self.text, font_size: self.font_size, runs: self.runs.as_slice(), } From 695a24d8a7a47e7456e8c559d05b0b9dbb6e7d19 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 17 Oct 2023 08:26:12 +0200 Subject: [PATCH 07/31] 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, From c6e20aed9be47b50be2e2d157608ac369b53aa08 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Oct 2023 08:32:33 +0200 Subject: [PATCH 08/31] Checkpoint --- crates/ui2/src/components/collab_panel.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/ui2/src/components/collab_panel.rs b/crates/ui2/src/components/collab_panel.rs index 86d5d12aaa..0e268664a9 100644 --- a/crates/ui2/src/components/collab_panel.rs +++ b/crates/ui2/src/components/collab_panel.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use gpui3::{img, svg, ArcCow}; +use gpui3::{img, svg, SharedString}; use crate::prelude::*; use crate::theme::{theme, Theme}; @@ -100,7 +100,7 @@ impl CollabPanel { fn list_section_header( &self, - label: impl Into>, + label: impl Into, expanded: bool, theme: &Theme, ) -> impl Element { @@ -128,8 +128,8 @@ impl CollabPanel { fn list_item( &self, - avatar_uri: impl Into>, - label: impl Into>, + avatar_uri: impl Into, + label: impl Into, theme: &Theme, ) -> impl Element { div() @@ -180,7 +180,11 @@ mod stories { } } - fn render(&mut self, _view: &mut S, cx: &mut ViewContext) -> impl Element { + fn render( + &mut self, + _view: &mut S, + cx: &mut ViewContext, + ) -> impl Element { Story::container(cx) .child(Story::title_for::<_, CollabPanel>(cx)) .child(Story::label(cx, "Default")) From b040ae8d4dbf9a967112bfae72fd1851b32b509d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Oct 2023 08:52:26 +0200 Subject: [PATCH 09/31] Checkpoint --- crates/gpui3/src/style.rs | 12 ++++++------ crates/storybook2/src/stories.rs | 9 +++++++-- crates/storybook2/src/stories/text.rs | 20 ++++++++++++++++++++ crates/storybook2/src/story_selector.rs | 15 +++++++-------- 4 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 crates/storybook2/src/stories/text.rs diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index 17cd2dc43f..f472e05c91 100644 --- a/crates/gpui3/src/style.rs +++ b/crates/gpui3/src/style.rs @@ -1,8 +1,8 @@ use crate::{ - phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, - CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, - FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, SharedString, Size, - SizeRefinement, TextRun, ViewContext, WindowContext, + black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, + Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, + FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, + SharedString, Size, SizeRefinement, TextRun, ViewContext, WindowContext, }; use refineable::{Cascade, Refineable}; use smallvec::SmallVec; @@ -125,8 +125,8 @@ pub struct TextStyle { impl Default for TextStyle { fn default() -> Self { TextStyle { - color: Hsla::default(), - font_family: SharedString::default(), + color: black(), + font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system") font_features: FontFeatures::default(), font_size: rems(1.), line_height: phi(), diff --git a/crates/storybook2/src/stories.rs b/crates/storybook2/src/stories.rs index a7b09443e6..f731f7e428 100644 --- a/crates/storybook2/src/stories.rs +++ b/crates/storybook2/src/stories.rs @@ -1,2 +1,7 @@ -pub mod kitchen_sink; -pub mod z_index; +mod kitchen_sink; +mod text; +mod z_index; + +pub use kitchen_sink::*; +pub use text::*; +pub use z_index::*; diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs new file mode 100644 index 0000000000..1f35d63725 --- /dev/null +++ b/crates/storybook2/src/stories/text.rs @@ -0,0 +1,20 @@ +use gpui3::{div, view, white, Context, ParentElement, StyleHelpers, View, WindowContext}; + +pub struct TextStory { + text: View<()>, +} + +impl TextStory { + pub fn view(cx: &mut WindowContext) -> View<()> { + view(cx.entity(|cx| ()), |_, cx| { + div() + .size_full() + .fill(white()) + .child(concat!( + "The quick brown fox jumps over the lazy dog. ", + "Meanwhile, the lazy dog decided it was time for a change. ", + "He started daily workout routines, ate healthier and became the fastest dog in town.", + )) + }) + } +} diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 0f6159d40f..1603a46b19 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -1,12 +1,12 @@ use std::str::FromStr; use std::sync::OnceLock; +use crate::stories::*; use anyhow::anyhow; use clap::builder::PossibleValue; use clap::ValueEnum; use gpui3::{view, AnyView, Context}; use strum::{EnumIter, EnumString, IntoEnumIterator}; - use ui::prelude::*; #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] @@ -18,6 +18,7 @@ pub enum ElementStory { Icon, Input, Label, + Text, ZIndex, } @@ -43,10 +44,10 @@ impl ElementStory { Self::Label => { view(cx.entity(|cx| ()), |_, _| ui::LabelStory::new().into_any()).into_any() } - Self::ZIndex => view(cx.entity(|cx| ()), |_, _| { - crate::stories::z_index::ZIndexStory::new().into_any() - }) - .into_any(), + Self::Text => TextStory::view(cx).into_any(), + Self::ZIndex => { + view(cx.entity(|cx| ()), |_, _| ZIndexStory::new().into_any()).into_any() + } } } } @@ -212,9 +213,7 @@ impl StorySelector { match self { Self::Element(element_story) => element_story.story(cx), Self::Component(component_story) => component_story.story(cx), - Self::KitchenSink => { - crate::stories::kitchen_sink::KitchenSinkStory::view(cx).into_any() - } + Self::KitchenSink => KitchenSinkStory::view(cx).into_any(), } } } From 9e7a579365f44b0b0f02427bb60e858ce73c8057 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Oct 2023 08:57:20 +0200 Subject: [PATCH 10/31] Checkpoint --- crates/gpui3/src/text_system/line_layout.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/gpui3/src/text_system/line_layout.rs b/crates/gpui3/src/text_system/line_layout.rs index 675a4a8760..97e0272f27 100644 --- a/crates/gpui3/src/text_system/line_layout.rs +++ b/crates/gpui3/src/text_system/line_layout.rs @@ -185,6 +185,7 @@ impl LineLayoutCache { text, font_size, runs, + wrap_width, } as &dyn AsCacheKeyRef; let curr_frame = self.curr_frame.upgradable_read(); if let Some(layout) = curr_frame.get(key) { @@ -210,6 +211,7 @@ impl LineLayoutCache { text: text.clone(), font_size, runs: SmallVec::from(runs), + wrap_width, }; curr_frame.insert(key, wrapped_line.clone()); wrapped_line @@ -226,6 +228,7 @@ struct CacheKey { text: SharedString, font_size: Pixels, runs: SmallVec<[(usize, FontId); 1]>, + wrap_width: Option, } #[derive(Copy, Clone, PartialEq, Eq)] @@ -233,6 +236,7 @@ struct CacheKeyRef<'a> { text: &'a str, font_size: Pixels, runs: &'a [(usize, FontId)], + wrap_width: Option, } impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) { @@ -255,6 +259,7 @@ impl AsCacheKeyRef for CacheKey { text: &self.text, font_size: self.font_size, runs: self.runs.as_slice(), + wrap_width: self.wrap_width, } } } From 88ae4679d1822449fa00bfe01e79fe761db97bfa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Oct 2023 09:03:01 +0200 Subject: [PATCH 11/31] Checkpoint --- crates/gpui3/src/platform.rs | 4 +-- crates/gpui3/src/platform/mac/text_system.rs | 26 ++++++-------------- crates/gpui3/src/text_system.rs | 15 ++++++----- crates/gpui3/src/text_system/line_layout.rs | 25 ++++++++----------- crates/gpui3/src/text_system/line_wrapper.rs | 11 +++++++-- 5 files changed, 36 insertions(+), 45 deletions(-) diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 7caf192404..8bfdfbccfa 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -5,7 +5,7 @@ mod mac; mod test; use crate::{ - AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, + AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, FontRun, GlobalPixels, GlyphId, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, }; @@ -171,7 +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: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout; + fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> 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 13c6e815fa..a4c56c3523 100644 --- a/crates/gpui3/src/platform/mac/text_system.rs +++ b/crates/gpui3/src/platform/mac/text_system.rs @@ -1,7 +1,7 @@ use crate::{ - point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle, - FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, - ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, + point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, + FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, + RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, }; use anyhow::anyhow; use cocoa::appkit::{CGFloat, CGPoint}; @@ -149,12 +149,7 @@ impl PlatformTextSystem for MacTextSystem { self.0.read().rasterize_glyph(glyph_id) } - fn layout_line( - &self, - text: &str, - font_size: Pixels, - font_runs: &[(usize, FontId)], - ) -> LineLayout { + fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { self.0.write().layout_line(text, font_size, font_runs) } @@ -337,12 +332,7 @@ impl MacTextSystemState { } } - fn layout_line( - &mut self, - text: &str, - font_size: Pixels, - font_runs: &[(usize, FontId)], - ) -> LineLayout { + fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { // Construct the attributed string, converting UTF8 ranges to UTF16 ranges. let mut string = CFMutableAttributedString::new(); { @@ -350,8 +340,8 @@ impl MacTextSystemState { let utf16_line_len = string.char_len() as usize; let mut ix_converter = StringIndexConverter::new(text); - for (run_len, font_id) in font_runs { - let utf8_end = ix_converter.utf8_ix + run_len; + for run in font_runs { + let utf8_end = ix_converter.utf8_ix + run.len; let utf16_start = ix_converter.utf16_ix; if utf16_start >= utf16_line_len { @@ -364,7 +354,7 @@ impl MacTextSystemState { let cf_range = CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize); - let font: &FontKitFont = &self.fonts[font_id.0]; + let font: &FontKitFont = &self.fonts[run.font_id.0]; unsafe { string.set_attribute( cf_range, diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs index f3a12f0767..5315e4e357 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -40,7 +40,7 @@ pub struct TextSystem { font_ids_by_font: RwLock>, font_metrics: RwLock>, wrapper_pool: Mutex>>, - font_runs_pool: Mutex>>, + font_runs_pool: Mutex>>, } impl TextSystem { @@ -153,8 +153,7 @@ impl TextSystem { wrap_width: Option, ) -> Result> { let mut runs = runs.iter().cloned().peekable(); - let mut font_runs: Vec<(usize, FontId)> = - self.font_runs_pool.lock().pop().unwrap_or_default(); + let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default(); let mut lines = SmallVec::new(); let mut line_start = 0; @@ -173,13 +172,13 @@ impl TextSystem { let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start; if last_font == Some(run.font.clone()) { - font_runs.last_mut().unwrap().0 += run_len_within_line; + font_runs.last_mut().unwrap().len += run_len_within_line; } else { last_font = Some(run.font.clone()); - font_runs.push(( - run_len_within_line, - self.platform_text_system.font_id(&run.font)?, - )); + font_runs.push(FontRun { + len: run_len_within_line, + font_id: self.platform_text_system.font_id(&run.font)?, + }); } if decoration_runs.last().map_or(false, |last_run| { diff --git a/crates/gpui3/src/text_system/line_layout.rs b/crates/gpui3/src/text_system/line_layout.rs index 97e0272f27..5d83bd69f6 100644 --- a/crates/gpui3/src/text_system/line_layout.rs +++ b/crates/gpui3/src/text_system/line_layout.rs @@ -178,7 +178,7 @@ impl LineLayoutCache { &self, text: &SharedString, font_size: Pixels, - runs: &[(usize, FontId)], + runs: &[FontRun], wrap_width: Option, ) -> Arc { let key = &CacheKeyRef { @@ -219,6 +219,12 @@ impl LineLayoutCache { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct FontRun { + pub(crate) len: usize, + pub(crate) font_id: FontId, +} + trait AsCacheKeyRef { fn as_cache_key_ref(&self) -> CacheKeyRef; } @@ -227,15 +233,15 @@ trait AsCacheKeyRef { struct CacheKey { text: SharedString, font_size: Pixels, - runs: SmallVec<[(usize, FontId); 1]>, + runs: SmallVec<[FontRun; 1]>, wrap_width: Option, } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] struct CacheKeyRef<'a> { text: &'a str, font_size: Pixels, - runs: &'a [(usize, FontId)], + runs: &'a [FontRun], wrap_width: Option, } @@ -287,14 +293,3 @@ impl<'a> AsCacheKeyRef for CacheKeyRef<'a> { *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 aa2af39a81..3dceec0572 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, Pixels, PlatformTextSystem}; +use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem}; use collections::HashMap; use std::{iter, sync::Arc}; @@ -112,7 +112,14 @@ impl LineWrapper { let mut buffer = [0; 4]; let buffer = c.encode_utf8(&mut buffer); self.platform_text_system - .layout_line(buffer, self.font_size, &[(1, self.font_id)]) + .layout_line( + buffer, + self.font_size, + &[FontRun { + len: 1, + font_id: self.font_id, + }], + ) .width } } From ac5b32c4916ca003d4922b7fd41d42021c90a97e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Oct 2023 10:14:22 +0200 Subject: [PATCH 12/31] Checkpoint --- crates/gpui3/src/elements/nested.rs | 153 +++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/crates/gpui3/src/elements/nested.rs b/crates/gpui3/src/elements/nested.rs index 94b67886c1..c1ef373842 100644 --- a/crates/gpui3/src/elements/nested.rs +++ b/crates/gpui3/src/elements/nested.rs @@ -1,7 +1,9 @@ use crate::{ - group_bounds, AnyElement, DispatchPhase, Element, IntoAnyElement, MouseMoveEvent, SharedString, - Style, StyleCascade, StyleRefinement, + group_bounds, AnyElement, DispatchPhase, Element, IdentifiedElement, IntoAnyElement, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, SharedString, Style, StyleCascade, + StyleRefinement, ViewContext, }; +use parking_lot::Mutex; use refineable::{CascadeSlot, Refineable}; use smallvec::SmallVec; use std::sync::{ @@ -222,6 +224,153 @@ where } } +pub trait Clickable: IdentifiedElement + Sized { + fn active_style(&mut self) -> &mut StyleRefinement; + fn listeners(&mut self) -> &mut ClickListeners; + + fn on_click( + &mut self, + f: impl Fn(&mut Self::ViewState, &MouseClickEvent, &mut ViewContext) + + 'static + + Send + + Sync, + ) where + Self: Sized, + { + self.listeners().push(Arc::new(f)); + } + + fn active(mut self, f: impl FnOnce(&mut StyleRefinement) -> &mut StyleRefinement) -> Self + where + Self: Sized, + { + f(self.active_style()); + self + } +} + +type ClickListeners = + SmallVec<[Arc) + Send + Sync>; 1]>; + +pub struct ClickableElementState { + mouse_down: Arc>>, + child_state: E::ElementState, +} + +pub struct MouseClickEvent { + down: MouseDownEvent, + up: MouseUpEvent, +} + +pub struct ClickableElement { + child: E, + listeners: ClickListeners, + active_style: StyleRefinement, + cascade_slot: CascadeSlot, +} + +impl IntoAnyElement for ClickableElement +where + E: IdentifiedElement + Styled, +{ + fn into_any(self) -> AnyElement { + AnyElement::new(self) + } +} + +impl Element for ClickableElement +where + E: IdentifiedElement + Styled, +{ + type ViewState = E::ViewState; + type ElementState = ClickableElementState; + + fn element_id(&self) -> Option { + Some(IdentifiedElement::element_id(&self.child)) + } + + fn layout( + &mut self, + state: &mut Self::ViewState, + element_state: Option, + cx: &mut crate::ViewContext, + ) -> (crate::LayoutId, Self::ElementState) { + if let Some(element_state) = element_state { + if element_state.mouse_down.lock().is_some() { + self.child + .style_cascade() + .set(self.cascade_slot, Some(self.active_style.clone())); + } + + let (layout_id, child_state) = + self.child + .layout(state, Some(element_state.child_state), cx); + ( + layout_id, + ClickableElementState { + mouse_down: element_state.mouse_down, + child_state, + }, + ) + } else { + let (layout_id, child_state) = self.child.layout(state, None, cx); + ( + layout_id, + ClickableElementState { + mouse_down: Default::default(), + child_state, + }, + ) + } + } + + fn paint( + &mut self, + bounds: crate::Bounds, + state: &mut Self::ViewState, + element_state: &mut Self::ElementState, + cx: &mut crate::ViewContext, + ) { + if !self.listeners.is_empty() || self.active_style.is_some() { + if let Some(mouse_down) = element_state.mouse_down.lock().clone() { + self.child + .style_cascade() + .set(self.cascade_slot, Some(self.active_style.clone())); + let listeners = self.listeners.clone(); + let mouse_down_mutex = element_state.mouse_down.clone(); + cx.on_mouse_event(move |view, up: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(up.position) { + for listener in &*listeners { + listener( + view, + &MouseClickEvent { + down: mouse_down.clone(), + up: up.clone(), + }, + cx, + ); + } + } + + mouse_down_mutex.lock().take(); + cx.notify(); + }); + } else { + let mouse_down_mutex = element_state.mouse_down.clone(); + cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(down.position) { + *mouse_down_mutex.lock() = Some(down.clone()); + cx.notify(); + } + }); + } + } + + self.child + .paint(bounds, state, &mut element_state.child_state, cx); + } +} + struct Div(HoverableElement>); impl LayoutNode for Div { From fb1e7eef6b571a6dfd0d126aa7638572ddb98f50 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Oct 2023 11:08:48 +0200 Subject: [PATCH 13/31] Checkpoint --- crates/gpui3/src/element.rs | 8 +- crates/gpui3/src/elements/clickable.rs | 2 +- crates/gpui3/src/elements/div.rs | 4 +- crates/gpui3/src/elements/group.rs | 4 +- crates/gpui3/src/elements/hoverable.rs | 4 +- crates/gpui3/src/elements/identified.rs | 2 +- crates/gpui3/src/elements/img.rs | 2 +- crates/gpui3/src/elements/nested.rs | 286 +++++++++++++++++------- crates/gpui3/src/elements/pressable.rs | 2 +- crates/gpui3/src/elements/svg.rs | 2 +- crates/gpui3/src/elements/text.rs | 2 +- crates/gpui3/src/view.rs | 12 +- crates/gpui3/src/window.rs | 2 +- crates/ui2/src/theme.rs | 2 +- 14 files changed, 223 insertions(+), 111 deletions(-) diff --git a/crates/gpui3/src/element.rs b/crates/gpui3/src/element.rs index ad4cfa97bc..8585bfdc9f 100644 --- a/crates/gpui3/src/element.rs +++ b/crates/gpui3/src/element.rs @@ -11,7 +11,7 @@ pub trait Element: 'static + Send + Sync + IntoAnyElement { type ViewState: 'static + Send + Sync; type ElementState: 'static + Send + Sync; - fn element_id(&self) -> Option; + fn id(&self) -> Option; fn layout( &mut self, @@ -38,7 +38,7 @@ pub trait Element: 'static + Send + Sync + IntoAnyElement { pub trait IdentifiedElement: Element { fn element_id(&self) -> ElementId { - Element::element_id(self).unwrap() + Element::id(self).unwrap() } fn on_click( @@ -126,7 +126,7 @@ impl RenderedElement { frame_state: &mut Option, cx: &mut ViewContext, ) { - if let Some(id) = self.element.element_id() { + if let Some(id) = self.element.id() { cx.with_element_state(id, |element_state, cx| { let mut element_state = element_state.unwrap(); self.element @@ -146,7 +146,7 @@ where S: 'static + Send + Sync, { fn layout(&mut self, state: &mut E::ViewState, cx: &mut ViewContext) -> LayoutId { - let (layout_id, frame_state) = if let Some(id) = self.element.element_id() { + let (layout_id, frame_state) = if let Some(id) = self.element.id() { let layout_id = cx.with_element_state(id, |element_state, cx| { self.element.layout(state, element_state, cx) }); diff --git a/crates/gpui3/src/elements/clickable.rs b/crates/gpui3/src/elements/clickable.rs index 861108a79a..0b99adcf1e 100644 --- a/crates/gpui3/src/elements/clickable.rs +++ b/crates/gpui3/src/elements/clickable.rs @@ -64,7 +64,7 @@ where type ViewState = E::ViewState; type ElementState = ClickableState; - fn element_id(&self) -> Option { + fn id(&self) -> Option { Some(IdentifiedElement::element_id(&self.child)) } diff --git a/crates/gpui3/src/elements/div.rs b/crates/gpui3/src/elements/div.rs index 444e095684..41874d9e89 100644 --- a/crates/gpui3/src/elements/div.rs +++ b/crates/gpui3/src/elements/div.rs @@ -47,7 +47,7 @@ where type ViewState = S; type ElementState = (); - fn element_id(&self) -> Option { + fn id(&self) -> Option { self.id.clone() } @@ -188,7 +188,7 @@ where cx: &mut ViewContext, f: impl FnOnce(&mut Self, &mut ViewContext) -> R, ) -> R { - if let Some(element_id) = self.element_id() { + if let Some(element_id) = self.id() { cx.with_element_id(element_id, |cx| f(self, cx)) } else { f(self, cx) diff --git a/crates/gpui3/src/elements/group.rs b/crates/gpui3/src/elements/group.rs index 398ac65e1b..ec9475a0d6 100644 --- a/crates/gpui3/src/elements/group.rs +++ b/crates/gpui3/src/elements/group.rs @@ -37,8 +37,8 @@ impl Element for Group { type ViewState = E::ViewState; type ElementState = E::ElementState; - fn element_id(&self) -> Option { - self.child.element_id() + fn id(&self) -> Option { + self.child.id() } fn layout( diff --git a/crates/gpui3/src/elements/hoverable.rs b/crates/gpui3/src/elements/hoverable.rs index 44d8c123cf..06450d8114 100644 --- a/crates/gpui3/src/elements/hoverable.rs +++ b/crates/gpui3/src/elements/hoverable.rs @@ -71,8 +71,8 @@ where type ViewState = E::ViewState; type ElementState = E::ElementState; - fn element_id(&self) -> Option { - self.child.element_id() + fn id(&self) -> Option { + self.child.id() } fn layout( diff --git a/crates/gpui3/src/elements/identified.rs b/crates/gpui3/src/elements/identified.rs index 3e6207912a..032afd19a3 100644 --- a/crates/gpui3/src/elements/identified.rs +++ b/crates/gpui3/src/elements/identified.rs @@ -21,7 +21,7 @@ impl Element for Identified { type ViewState = E::ViewState; type ElementState = E::ElementState; - fn element_id(&self) -> Option { + fn id(&self) -> Option { Some(self.id.clone()) } diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index 6b8c9b714c..8709707f31 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -48,7 +48,7 @@ impl Element for Img { type ViewState = S; type ElementState = (); - fn element_id(&self) -> Option { + fn id(&self) -> Option { None } diff --git a/crates/gpui3/src/elements/nested.rs b/crates/gpui3/src/elements/nested.rs index c1ef373842..bd1a8008fc 100644 --- a/crates/gpui3/src/elements/nested.rs +++ b/crates/gpui3/src/elements/nested.rs @@ -1,5 +1,5 @@ use crate::{ - group_bounds, AnyElement, DispatchPhase, Element, IdentifiedElement, IntoAnyElement, + group_bounds, AnyElement, DispatchPhase, Element, ElementId, IdentifiedElement, IntoAnyElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, SharedString, Style, StyleCascade, StyleRefinement, ViewContext, }; @@ -11,8 +11,8 @@ use std::sync::{ Arc, }; -trait LayoutNode { - fn state(&mut self) -> &mut LayoutNodeState; +trait LayoutNode { + fn state(&mut self) -> &mut LayoutNodeElement; fn child(mut self, child: impl IntoAnyElement) -> Self where @@ -35,26 +35,75 @@ trait LayoutNode { } } -struct LayoutNodeState { +pub trait ElementKind: 'static + Send + Sync { + fn id(&self) -> Option; +} + +pub struct Identified(ElementId); +pub struct Anonymous; + +impl ElementKind for Identified { + fn id(&self) -> Option { + Some(self.0.clone()) + } +} + +impl ElementKind for Anonymous { + fn id(&self) -> Option { + None + } +} + +struct LayoutNodeElement { style_cascade: StyleCascade, computed_style: Option