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 6423e44ae0..9b9a80ea04 100644 --- a/crates/gpui3/src/elements/text.rs +++ b/crates/gpui3/src/elements/text.rs @@ -1,11 +1,13 @@ use crate::{ - AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, Size, ViewContext, + AnyElement, BorrowWindow, 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, @@ -18,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() @@ -30,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() @@ -38,7 +40,7 @@ impl IntoAnyElement for String { } pub struct Text { - text: ArcCow<'static, str>, + text: SharedString, state_type: PhantomData, } @@ -74,12 +76,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 |_, _| { - let Some(line_layout) = text_system - .layout_line( - text.as_ref(), + move |known_dimensions, _| { + let Some(lines) = text_system + .layout_text( + &text, 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() else { @@ -87,14 +90,13 @@ impl Element for Text { }; let size = Size { - width: line_layout.width(), - height: line_height, + width: lines.iter().map(|line| line.layout.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 } @@ -110,22 +112,20 @@ 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 { + line.paint(line_origin, line_height, cx).log_err(); + line_origin.y += line.size(line_height).height; } - - line.paint(bounds, bounds, line_height, cx).log_err(); } } pub struct TextElementState { - line: Arc, + lines: SmallVec<[Line; 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/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 6d9cf3ea75..8bfdfbccfa 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -5,9 +5,9 @@ mod mac; mod test; use crate::{ - AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, - GlobalPixels, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, - Result, Scene, ShapedLine, SharedString, Size, + AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, FontRun, + 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: &[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 0320f91386..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, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph, - ShapedLine, 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)], - ) -> ShapedLine { + 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)], - ) -> ShapedLine { + 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, @@ -394,7 +384,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,13 +405,12 @@ 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(), runs, font_size, - len: text.len(), } } diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index 24e0dfaa17..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, RunStyle, SharedString, - Size, SizeRefinement, 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(), @@ -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 4ac98556b4..5315e4e357 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -1,13 +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::*; -pub use text_layout_cache::*; +use smallvec::SmallVec; use crate::{ px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, @@ -17,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}, @@ -33,18 +35,18 @@ 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>, wrapper_pool: Mutex>>, - font_runs_pool: Mutex>>, + font_runs_pool: Mutex>>, } 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()), @@ -143,38 +145,82 @@ impl TextSystem { } } - pub fn layout_line( + pub fn layout_text( &self, - text: &str, + text: &SharedString, font_size: Pixels, - runs: &[(usize, RunStyle)], - ) -> Result { + runs: &[TextRun], + wrap_width: Option, + ) -> Result> { + let mut runs = runs.iter().cloned().peekable(); let mut font_runs = 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; + let mut lines = SmallVec::new(); + let mut line_start = 0; + 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(); + let mut run_start = line_start; + while run_start < line_end { + let Some(run) = 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.font.clone()) { + font_runs.last_mut().unwrap().len += run_len_within_line; + } else { + last_font = Some(run.font.clone()); + 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| { + 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.color, + underline: run.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; } - last_font = Some(&style.font); - font_runs.push((*len, self.font_id(&style.font)?)); + + let layout = self + .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(); } - let layout = self - .text_layout_cache - .layout_line(text, font_size, &font_runs); - - font_runs.clear(); self.font_runs_pool.lock().push(font_runs); - Ok(Line::new(layout.clone(), runs)) + Ok(lines) } pub fn end_frame(&self) { - self.text_layout_cache.end_frame() + self.line_layout_cache.end_frame() } pub fn line_wrapper( @@ -317,7 +363,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, @@ -345,30 +392,6 @@ impl From for GlyphId { } } -#[derive(Default, Debug)] -pub struct ShapedLine { - pub font_size: Pixels, - pub width: Pixels, - pub ascent: Pixels, - pub descent: Pixels, - pub runs: Vec, - pub len: usize, -} - -#[derive(Debug)] -pub struct ShapedRun { - pub font_id: FontId, - pub glyphs: Vec, -} - -#[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 c63c18825b..deb87e9796 100644 --- a/crates/gpui3/src/text_system/line.rs +++ b/crates/gpui3/src/text_system/line.rs @@ -1,144 +1,94 @@ use crate::{ - black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine, - 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, - style_runs: SmallVec<[StyleRun; 32]>, +#[derive(Debug, Clone)] +pub struct DecorationRun { + pub len: u32, + pub color: Hsla, + pub underline: Option, } -#[derive(Debug, Clone)] -struct StyleRun { - len: u32, - color: Hsla, - underline: UnderlineStyle, +#[derive(Clone, Default, Debug)] +pub struct Line { + pub(crate) layout: Arc, + pub(crate) decorations: SmallVec<[DecorationRun; 32]>, } 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(), - }); - } - Self { layout, style_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.len - } - - pub fn is_empty(&self) -> bool { - self.layout.len == 0 - } - - 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.style_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 underline = None; + 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 { 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), + origin.y + + baseline_offset.y + + (self.layout.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, }, )); } @@ -146,15 +96,11 @@ impl Line { run_end += style_run.len as usize; color = style_run.color; } else { - run_end = self.layout.len; - finished_underline = underline.take(); + run_end = self.layout.text.len(); + finished_underline = current_underline.take(); } } - 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, @@ -163,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)) = underline.take() { - let line_end_x = origin.x + self.layout.width; + if let Some((underline_start, underline_style)) = current_underline.take() { + let line_end_x = origin.x + self.layout.layout.width; cx.paint_underline( underline_start, line_end_x - underline_start.x, @@ -188,125 +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.style_runs.iter(); - let mut style_run_end = 0; - let mut _color = black(); // todo! - let mut 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)) = 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 underline { - if style_run.underline != *underline_style { - finished_underline = underline.take(); - } - } - if style_run.underline.thickness > px(0.) { - 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, - }, - )); - } - } else { - style_run_end = self.layout.len; - _color = black(); - finished_underline = 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)) = 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..5d83bd69f6 --- /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: &[FontRun], + wrap_width: Option, + ) -> Arc { + let key = &CacheKeyRef { + 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) { + 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), + wrap_width, + }; + curr_frame.insert(key, wrapped_line.clone()); + wrapped_line + } + } +} + +#[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; +} + +#[derive(Eq)] +struct CacheKey { + text: SharedString, + font_size: Pixels, + runs: SmallVec<[FontRun; 1]>, + wrap_width: Option, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct CacheKeyRef<'a> { + text: &'a str, + font_size: Pixels, + runs: &'a [FontRun], + wrap_width: Option, +} + +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(), + wrap_width: self.wrap_width, + } + } +} + +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 + } +} diff --git a/crates/gpui3/src/text_system/line_wrapper.rs b/crates/gpui3/src/text_system/line_wrapper.rs index 87c75cc9ac..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, Line, Pixels, PlatformTextSystem, ShapedBoundary}; +use crate::{px, FontId, FontRun, 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 str, - 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,17 @@ 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(), self.font_size, &[(1, self.font_id)]) + .layout_line( + buffer, + self.font_size, + &[FontRun { + len: 1, + font_id: self.font_id, + }], + ) .width } } @@ -203,7 +139,7 @@ impl Boundary { #[cfg(test)] mod tests { use super::*; - use crate::{font, App, RunStyle}; + use crate::{font, App}; #[test] fn test_wrap_line() { @@ -268,62 +204,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 = RunStyle { - font: font("Helvetica"), - color: Default::default(), - underline: Default::default(), - }; - let bold = RunStyle { - 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(), + // }; - let text = "aa bbb cccc ddddd eeee"; - let line = text_system - .layout_line( - text, - px(16.), - &[ - (4, normal.clone()), - (5, bold.clone()), - (6, normal.clone()), - (1, bold.clone()), - (7, normal.clone()), - ], - ) - .unwrap(); + // impl TextRun { + // fn with_len(&self, len: usize) -> Self { + // let mut this = self.clone(); + // this.len = len; + // this + // } + // } - 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 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 + // } + // ], + // ); + // }); + // } } 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 e9768aaf21..0000000000 --- a/crates/gpui3/src/text_system/text_layout_cache.rs +++ /dev/null @@ -1,153 +0,0 @@ -use crate::{FontId, Pixels, PlatformTextSystem, ShapedGlyph, ShapedLine, ShapedRun}; -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(fonts: Arc) -> Self { - Self { - prev_frame: Mutex::new(HashMap::new()), - curr_frame: RwLock::new(HashMap::new()), - platform_text_system: fonts, - } - } - - 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<'a>( - &'a self, - text: &'a str, - 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.into(), - 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: String, - font_size: Pixels, - runs: SmallVec<[(usize, FontId); 1]>, -} - -impl CacheKey for CacheKeyValue { - fn key(&self) -> CacheKeyRef { - CacheKeyRef { - text: self.text.as_str(), - 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, 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() } 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(), } } } 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"))