From 938dd8b9cab7803718372f4646440325349de9be Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 16 Oct 2023 20:16:35 +0200 Subject: [PATCH] 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, } }