Merge branch 'gpui2-text-wrap' into gpui2

This commit is contained in:
Antonio Scandurra 2023-10-17 09:03:16 +02:00
commit b526fc070d
18 changed files with 677 additions and 665 deletions

View file

@ -155,8 +155,9 @@ impl Refineable for TextStyleRefinement {
} }
} }
fn refined(self, refinement: Self::Refinement) -> Self { fn refined(mut self, refinement: Self::Refinement) -> Self {
todo!() self.refine(&refinement);
self
} }
} }

View file

@ -1,11 +1,13 @@
use crate::{ 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 parking_lot::Mutex;
use smallvec::SmallVec;
use std::{marker::PhantomData, sync::Arc}; use std::{marker::PhantomData, sync::Arc};
use util::{arc_cow::ArcCow, ResultExt}; use util::ResultExt;
impl<S: 'static + Send + Sync> IntoAnyElement<S> for ArcCow<'static, str> { impl<S: 'static + Send + Sync> IntoAnyElement<S> for SharedString {
fn into_any(self) -> AnyElement<S> { fn into_any(self) -> AnyElement<S> {
Text { Text {
text: self, text: self,
@ -18,7 +20,7 @@ impl<S: 'static + Send + Sync> IntoAnyElement<S> for ArcCow<'static, str> {
impl<V: 'static + Send + Sync> IntoAnyElement<V> for &'static str { impl<V: 'static + Send + Sync> IntoAnyElement<V> for &'static str {
fn into_any(self) -> AnyElement<V> { fn into_any(self) -> AnyElement<V> {
Text { Text {
text: ArcCow::from(self), text: self.into(),
state_type: PhantomData, state_type: PhantomData,
} }
.into_any() .into_any()
@ -30,7 +32,7 @@ impl<V: 'static + Send + Sync> IntoAnyElement<V> for &'static str {
impl<S: 'static + Send + Sync> IntoAnyElement<S> for String { impl<S: 'static + Send + Sync> IntoAnyElement<S> for String {
fn into_any(self) -> AnyElement<S> { fn into_any(self) -> AnyElement<S> {
Text { Text {
text: ArcCow::from(self), text: self.into(),
state_type: PhantomData, state_type: PhantomData,
} }
.into_any() .into_any()
@ -38,7 +40,7 @@ impl<S: 'static + Send + Sync> IntoAnyElement<S> for String {
} }
pub struct Text<S> { pub struct Text<S> {
text: ArcCow<'static, str>, text: SharedString,
state_type: PhantomData<S>, state_type: PhantomData<S>,
} }
@ -74,12 +76,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
let rem_size = cx.rem_size(); let rem_size = cx.rem_size();
let layout_id = cx.request_measured_layout(Default::default(), rem_size, { let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
let element_state = element_state.clone(); let element_state = element_state.clone();
move |_, _| { move |known_dimensions, _| {
let Some(line_layout) = text_system let Some(lines) = text_system
.layout_line( .layout_text(
text.as_ref(), &text,
font_size, 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() .log_err()
else { else {
@ -87,14 +90,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
}; };
let size = Size { let size = Size {
width: line_layout.width(), width: lines.iter().map(|line| line.layout.width).max().unwrap(),
height: line_height, height: line_height * lines.len(),
}; };
element_state.lock().replace(TextElementState { element_state
line: Arc::new(line_layout), .lock()
line_height, .replace(TextElementState { lines, line_height });
});
size size
} }
@ -110,22 +112,20 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
element_state: &mut Self::ElementState, element_state: &mut Self::ElementState,
cx: &mut ViewContext<S>, cx: &mut ViewContext<S>,
) { ) {
let line; let element_state = element_state.lock();
let line_height; let element_state = element_state
{ .as_ref()
let element_state = element_state.lock(); .expect("measurement has not been performed");
let element_state = element_state let line_height = element_state.line_height;
.as_ref() let mut line_origin = bounds.origin;
.expect("measurement has not been performed"); for line in &element_state.lines {
line = element_state.line.clone(); line.paint(line_origin, line_height, cx).log_err();
line_height = element_state.line_height; line_origin.y += line.size(line_height).height;
} }
line.paint(bounds, bounds, line_height, cx).log_err();
} }
} }
pub struct TextElementState { pub struct TextElementState {
line: Arc<Line>, lines: SmallVec<[Line; 1]>,
line_height: Pixels, line_height: Pixels,
} }

View file

@ -656,6 +656,14 @@ impl Mul<f32> for Pixels {
} }
} }
impl Mul<usize> for Pixels {
type Output = Pixels;
fn mul(self, other: usize) -> Pixels {
Pixels(self.0 * other as f32)
}
}
impl Mul<Pixels> for f32 { impl Mul<Pixels> for f32 {
type Output = Pixels; type Output = Pixels;

View file

@ -51,6 +51,7 @@ pub use util::arc_cow::ArcCow;
pub use view::*; pub use view::*;
pub use window::*; pub use window::*;
use derive_more::{Deref, DerefMut};
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
mem, mem,
@ -180,7 +181,7 @@ impl<T> Flatten<T> for Result<T> {
} }
} }
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
pub struct SharedString(ArcCow<'static, str>); pub struct SharedString(ArcCow<'static, str>);
impl Default for SharedString { impl Default for SharedString {

View file

@ -5,9 +5,9 @@ mod mac;
mod test; mod test;
use crate::{ use crate::{
AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, FontRun,
GlobalPixels, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, GlobalPixels, GlyphId, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams,
Result, Scene, ShapedLine, SharedString, Size, RenderSvgParams, Result, Scene, SharedString, Size,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use async_task::Runnable; use async_task::Runnable;
@ -171,7 +171,7 @@ pub trait PlatformTextSystem: Send + Sync {
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>; fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>; fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>; fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
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( fn wrap_line(
&self, &self,
text: &str, text: &str,

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle, point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun,
FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph, FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use cocoa::appkit::{CGFloat, CGPoint}; use cocoa::appkit::{CGFloat, CGPoint};
@ -149,12 +149,7 @@ impl PlatformTextSystem for MacTextSystem {
self.0.read().rasterize_glyph(glyph_id) self.0.read().rasterize_glyph(glyph_id)
} }
fn layout_line( fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
&self,
text: &str,
font_size: Pixels,
font_runs: &[(usize, FontId)],
) -> ShapedLine {
self.0.write().layout_line(text, font_size, font_runs) self.0.write().layout_line(text, font_size, font_runs)
} }
@ -337,12 +332,7 @@ impl MacTextSystemState {
} }
} }
fn layout_line( fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
&mut self,
text: &str,
font_size: Pixels,
font_runs: &[(usize, FontId)],
) -> ShapedLine {
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges. // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
let mut string = CFMutableAttributedString::new(); let mut string = CFMutableAttributedString::new();
{ {
@ -350,8 +340,8 @@ impl MacTextSystemState {
let utf16_line_len = string.char_len() as usize; let utf16_line_len = string.char_len() as usize;
let mut ix_converter = StringIndexConverter::new(text); let mut ix_converter = StringIndexConverter::new(text);
for (run_len, font_id) in font_runs { for run in font_runs {
let utf8_end = ix_converter.utf8_ix + run_len; let utf8_end = ix_converter.utf8_ix + run.len;
let utf16_start = ix_converter.utf16_ix; let utf16_start = ix_converter.utf16_ix;
if utf16_start >= utf16_line_len { if utf16_start >= utf16_line_len {
@ -364,7 +354,7 @@ impl MacTextSystemState {
let cf_range = let cf_range =
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize); 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 { unsafe {
string.set_attribute( string.set_attribute(
cf_range, cf_range,
@ -394,7 +384,7 @@ impl MacTextSystemState {
let font_id = self.id_for_native_font(font); let font_id = self.id_for_native_font(font);
let mut ix_converter = StringIndexConverter::new(text); 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 for ((glyph_id, position), glyph_utf16_ix) in run
.glyphs() .glyphs()
.iter() .iter()
@ -415,13 +405,12 @@ impl MacTextSystemState {
} }
let typographic_bounds = line.get_typographic_bounds(); let typographic_bounds = line.get_typographic_bounds();
ShapedLine { LineLayout {
width: typographic_bounds.width.into(), width: typographic_bounds.width.into(),
ascent: typographic_bounds.ascent.into(), ascent: typographic_bounds.ascent.into(),
descent: typographic_bounds.descent.into(), descent: typographic_bounds.descent.into(),
runs, runs,
font_size, font_size,
len: text.len(),
} }
} }

View file

@ -1,8 +1,8 @@
use crate::{ use crate::{
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures,
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result,
Size, SizeRefinement, ViewContext, WindowContext, SharedString, Size, SizeRefinement, TextRun, ViewContext, WindowContext,
}; };
use refineable::{Cascade, Refineable}; use refineable::{Cascade, Refineable};
use smallvec::SmallVec; use smallvec::SmallVec;
@ -125,8 +125,8 @@ pub struct TextStyle {
impl Default for TextStyle { impl Default for TextStyle {
fn default() -> Self { fn default() -> Self {
TextStyle { TextStyle {
color: Hsla::default(), color: black(),
font_family: SharedString::default(), font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system")
font_features: FontFeatures::default(), font_features: FontFeatures::default(),
font_size: rems(1.), font_size: rems(1.),
line_height: phi(), line_height: phi(),
@ -161,8 +161,9 @@ impl TextStyle {
Ok(self) Ok(self)
} }
pub fn to_run(&self) -> RunStyle { pub fn to_run(&self, len: usize) -> TextRun {
RunStyle { TextRun {
len,
font: Font { font: Font {
family: self.font_family.clone(), family: self.font_family.clone(),
features: Default::default(), features: Default::default(),

View file

@ -1,13 +1,14 @@
mod font_features; mod font_features;
mod line; mod line;
mod line_layout;
mod line_wrapper; mod line_wrapper;
mod text_layout_cache;
use anyhow::anyhow; use anyhow::anyhow;
pub use font_features::*; pub use font_features::*;
pub use line::*; pub use line::*;
pub use line_layout::*;
use line_wrapper::*; use line_wrapper::*;
pub use text_layout_cache::*; use smallvec::SmallVec;
use crate::{ use crate::{
px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
@ -17,6 +18,7 @@ use collections::HashMap;
use core::fmt; use core::fmt;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use std::{ use std::{
cmp,
fmt::{Debug, Display, Formatter}, fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
@ -33,18 +35,18 @@ pub struct FontFamilyId(pub usize);
pub const SUBPIXEL_VARIANTS: u8 = 4; pub const SUBPIXEL_VARIANTS: u8 = 4;
pub struct TextSystem { pub struct TextSystem {
text_layout_cache: Arc<TextLayoutCache>, line_layout_cache: Arc<LineLayoutCache>,
platform_text_system: Arc<dyn PlatformTextSystem>, platform_text_system: Arc<dyn PlatformTextSystem>,
font_ids_by_font: RwLock<HashMap<Font, FontId>>, font_ids_by_font: RwLock<HashMap<Font, FontId>>,
font_metrics: RwLock<HashMap<FontId, FontMetrics>>, font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>, wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>, font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
} }
impl TextSystem { impl TextSystem {
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self { pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
TextSystem { 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, platform_text_system,
font_metrics: RwLock::new(HashMap::default()), font_metrics: RwLock::new(HashMap::default()),
font_ids_by_font: 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, &self,
text: &str, text: &SharedString,
font_size: Pixels, font_size: Pixels,
runs: &[(usize, RunStyle)], runs: &[TextRun],
) -> Result<Line> { wrap_width: Option<Pixels>,
) -> Result<SmallVec<[Line; 1]>> {
let mut runs = runs.iter().cloned().peekable();
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default(); let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
let mut last_font: Option<&Font> = None; let mut lines = SmallVec::new();
for (len, style) in runs { let mut line_start = 0;
if let Some(last_font) = last_font.as_ref() { for line_text in text.split('\n') {
if **last_font == style.font { let line_text = SharedString::from(line_text.to_string());
font_runs.last_mut().unwrap().0 += len; let line_end = line_start + line_text.len();
continue;
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) = 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); self.font_runs_pool.lock().push(font_runs);
Ok(Line::new(layout.clone(), runs)) Ok(lines)
} }
pub fn end_frame(&self) { pub fn end_frame(&self) {
self.text_layout_cache.end_frame() self.line_layout_cache.end_frame()
} }
pub fn line_wrapper( pub fn line_wrapper(
@ -317,7 +363,8 @@ impl Display for FontStyle {
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct RunStyle { pub struct TextRun {
pub len: usize,
pub font: Font, pub font: Font,
pub color: Hsla, pub color: Hsla,
pub underline: Option<UnderlineStyle>, pub underline: Option<UnderlineStyle>,
@ -345,30 +392,6 @@ impl From<u32> for GlyphId {
} }
} }
#[derive(Default, Debug)]
pub struct ShapedLine {
pub font_size: Pixels,
pub width: Pixels,
pub ascent: Pixels,
pub descent: Pixels,
pub runs: Vec<ShapedRun>,
pub len: usize,
}
#[derive(Debug)]
pub struct ShapedRun {
pub font_id: FontId,
pub glyphs: Vec<ShapedGlyph>,
}
#[derive(Clone, Debug)]
pub struct ShapedGlyph {
pub id: GlyphId,
pub position: Point<Pixels>,
pub index: usize,
pub is_emoji: bool,
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct RenderGlyphParams { pub struct RenderGlyphParams {
pub(crate) font_id: FontId, pub(crate) font_id: FontId,

View file

@ -1,144 +1,94 @@
use crate::{ use crate::{
black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine, black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
ShapedRun, UnderlineStyle, WindowContext, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
}; };
use anyhow::Result;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::sync::Arc; use std::sync::Arc;
#[derive(Default, Debug, Clone)] #[derive(Debug, Clone)]
pub struct Line { pub struct DecorationRun {
layout: Arc<ShapedLine>, pub len: u32,
style_runs: SmallVec<[StyleRun; 32]>, pub color: Hsla,
pub underline: Option<UnderlineStyle>,
} }
#[derive(Debug, Clone)] #[derive(Clone, Default, Debug)]
struct StyleRun { pub struct Line {
len: u32, pub(crate) layout: Arc<WrappedLineLayout>,
color: Hsla, pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
underline: UnderlineStyle,
} }
impl Line { impl Line {
pub fn new(layout: Arc<ShapedLine>, runs: &[(usize, RunStyle)]) -> Self { pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
let mut style_runs = SmallVec::new(); size(
for (len, style) in runs { self.layout.width,
style_runs.push(StyleRun { line_height * (self.layout.wrap_boundaries.len() + 1),
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<FontId> {
for run in &self.layout.runs {
for glyph in &run.glyphs {
if glyph.index >= index {
return Some(run.font_id);
}
}
}
None
}
pub fn len(&self) -> usize {
self.layout.len
}
pub fn is_empty(&self) -> bool {
self.layout.len == 0
}
pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
if x >= self.layout.width {
None
} else {
for run in self.layout.runs.iter().rev() {
for glyph in run.glyphs.iter().rev() {
if glyph.position.x <= x {
return Some(glyph.index);
}
}
}
Some(0)
}
} }
pub fn paint( pub fn paint(
&self, &self,
bounds: Bounds<Pixels>, origin: Point<Pixels>,
visible_bounds: Bounds<Pixels>, // todo!("use clipping")
line_height: Pixels, line_height: Pixels,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Result<()> { ) -> Result<()> {
let origin = bounds.origin; let padding_top =
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.; (line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + self.layout.ascent); 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 run_end = 0;
let mut color = black(); let mut color = black();
let mut underline = None; let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let text_system = cx.text_system().clone(); let text_system = cx.text_system().clone();
for run in &self.layout.runs { let mut glyph_origin = origin;
let max_glyph_width = text_system let mut prev_glyph_position = Point::default();
.bounding_box(run.font_id, self.layout.font_size)? for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
.size let max_glyph_size = text_system
.width; .bounding_box(run.font_id, self.layout.layout.font_size)?
.size;
for glyph in &run.glyphs { for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
let glyph_origin = origin + baseline_offset + glyph.position; glyph_origin.x += glyph.position.x - prev_glyph_position.x;
if glyph_origin.x > visible_bounds.upper_right().x {
break; 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<Pixels>, UnderlineStyle)> = None; let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
if glyph.index >= run_end { if glyph.index >= run_end {
if let Some(style_run) = style_runs.next() { if let Some(style_run) = style_runs.next() {
if let Some((_, underline_style)) = &mut underline { if let Some((_, underline_style)) = &mut current_underline {
if style_run.underline != *underline_style { if style_run.underline.as_ref() != Some(underline_style) {
finished_underline = underline.take(); finished_underline = current_underline.take();
} }
} }
if style_run.underline.thickness > px(0.) { if let Some(run_underline) = style_run.underline.as_ref() {
underline.get_or_insert(( current_underline.get_or_insert((
point( point(
glyph_origin.x, 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 { UnderlineStyle {
color: Some( color: Some(run_underline.color.unwrap_or(style_run.color)),
style_run.underline.color.unwrap_or(style_run.color), thickness: run_underline.thickness,
), wavy: run_underline.wavy,
thickness: style_run.underline.thickness,
wavy: style_run.underline.wavy,
}, },
)); ));
} }
@ -146,15 +96,11 @@ impl Line {
run_end += style_run.len as usize; run_end += style_run.len as usize;
color = style_run.color; color = style_run.color;
} else { } else {
run_end = self.layout.len; run_end = self.layout.text.len();
finished_underline = underline.take(); 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 { if let Some((underline_origin, underline_style)) = finished_underline {
cx.paint_underline( cx.paint_underline(
underline_origin, underline_origin,
@ -163,22 +109,35 @@ impl Line {
)?; )?;
} }
if glyph.is_emoji { let max_glyph_bounds = Bounds {
cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?; origin: glyph_origin,
} else { size: max_glyph_size,
cx.paint_glyph( };
glyph_origin,
run.font_id, let content_mask = cx.content_mask();
glyph.id, if max_glyph_bounds.intersects(&content_mask.bounds) {
self.layout.font_size, if glyph.is_emoji {
color, 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() { 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( cx.paint_underline(
underline_start, underline_start,
line_end_x - underline_start.x, line_end_x - underline_start.x,
@ -188,125 +147,4 @@ impl Line {
Ok(()) Ok(())
} }
pub fn paint_wrapped(
&self,
origin: Point<Pixels>,
_visible_bounds: Bounds<Pixels>, // todo!("use clipping")
line_height: Pixels,
boundaries: &[ShapedBoundary],
cx: &mut WindowContext,
) -> Result<()> {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
let mut boundaries = boundaries.into_iter().peekable();
let mut color_runs = self.style_runs.iter();
let mut style_run_end = 0;
let mut _color = black(); // todo!
let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let mut glyph_origin = origin;
let mut prev_position = px(0.);
for (run_ix, run) in self.layout.runs.iter().enumerate() {
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_position;
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(())
}
} }

View file

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

View file

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

View file

@ -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<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
platform_text_system: Arc<dyn PlatformTextSystem>,
}
impl TextLayoutCache {
pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> 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<ShapedLine> {
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<H: Hasher>(&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<H: Hasher>(&self, state: &mut H) {
self.key().hash(state);
}
}
impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
fn borrow(&self) -> &(dyn CacheKey + 'a) {
self as &dyn CacheKey
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
struct CacheKeyRef<'a> {
text: &'a str,
font_size: Pixels,
runs: &'a [(usize, FontId)],
}
impl<'a> CacheKey for CacheKeyRef<'a> {
fn key(&self) -> CacheKeyRef {
*self
}
}
impl<'a> Hash for CacheKeyRef<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.text.hash(state);
self.font_size.hash(state);
for (len, font_id) in self.runs {
len.hash(state);
font_id.hash(state);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ShapedBoundary {
pub run_ix: usize,
pub glyph_ix: usize,
}
impl ShapedRun {
pub fn glyphs(&self) -> &[ShapedGlyph] {
&self.glyphs
}
}

View file

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

View file

@ -157,7 +157,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
}) })
.collect(); .collect();
let refinement_refined_assignments: Vec<TokenStream2> = fields let refinement_refined_assigments: Vec<TokenStream2> = fields
.iter() .iter()
.map(|field| { .map(|field| {
let name = &field.ident; let name = &field.ident;
@ -169,14 +169,37 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
} }
} else { } else {
quote! { quote! {
if refinement.#name.is_some() { if let Some(value) = refinement.#name {
self.#name = refinement.#name; self.#name = Some(value);
} }
} }
} }
}) })
.collect(); .collect();
let from_refinement_assigments: Vec<TokenStream2> = 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 debug_impl = if impl_debug_on_refinement {
let refinement_field_debugs: Vec<TokenStream2> = fields let refinement_field_debugs: Vec<TokenStream2> = fields
.iter() .iter()
@ -243,11 +266,21 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
} }
fn refined(mut self, refinement: Self::Refinement) -> Self { fn refined(mut self, refinement: Self::Refinement) -> Self {
#( #refinement_refined_assignments )* #( #refinement_refined_assigments )*
self 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 impl #impl_generics ::core::default::Default for #refinement_ident #ty_generics
#where_clause #where_clause
{ {
@ -273,7 +306,6 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
#debug_impl #debug_impl
}; };
gen.into() gen.into()
} }

View file

@ -1,2 +1,7 @@
pub mod kitchen_sink; mod kitchen_sink;
pub mod z_index; mod text;
mod z_index;
pub use kitchen_sink::*;
pub use text::*;
pub use z_index::*;

View file

@ -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.",
))
})
}
}

View file

@ -1,12 +1,12 @@
use std::str::FromStr; use std::str::FromStr;
use std::sync::OnceLock; use std::sync::OnceLock;
use crate::stories::*;
use anyhow::anyhow; use anyhow::anyhow;
use clap::builder::PossibleValue; use clap::builder::PossibleValue;
use clap::ValueEnum; use clap::ValueEnum;
use gpui3::{view, AnyView, Context}; use gpui3::{view, AnyView, Context};
use strum::{EnumIter, EnumString, IntoEnumIterator}; use strum::{EnumIter, EnumString, IntoEnumIterator};
use ui::prelude::*; use ui::prelude::*;
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
@ -18,6 +18,7 @@ pub enum ElementStory {
Icon, Icon,
Input, Input,
Label, Label,
Text,
ZIndex, ZIndex,
} }
@ -43,10 +44,10 @@ impl ElementStory {
Self::Label => { Self::Label => {
view(cx.entity(|cx| ()), |_, _| ui::LabelStory::new().into_any()).into_any() view(cx.entity(|cx| ()), |_, _| ui::LabelStory::new().into_any()).into_any()
} }
Self::ZIndex => view(cx.entity(|cx| ()), |_, _| { Self::Text => TextStory::view(cx).into_any(),
crate::stories::z_index::ZIndexStory::new().into_any() Self::ZIndex => {
}) view(cx.entity(|cx| ()), |_, _| ZIndexStory::new().into_any()).into_any()
.into_any(), }
} }
} }
} }
@ -212,9 +213,7 @@ impl StorySelector {
match self { match self {
Self::Element(element_story) => element_story.story(cx), Self::Element(element_story) => element_story.story(cx),
Self::Component(component_story) => component_story.story(cx), Self::Component(component_story) => component_story.story(cx),
Self::KitchenSink => { Self::KitchenSink => KitchenSinkStory::view(cx).into_any(),
crate::stories::kitchen_sink::KitchenSinkStory::view(cx).into_any()
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use gpui3::{img, svg, ArcCow}; use gpui3::{img, svg, SharedString};
use crate::prelude::*; use crate::prelude::*;
use crate::theme::{theme, Theme}; use crate::theme::{theme, Theme};
@ -100,7 +100,7 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
fn list_section_header( fn list_section_header(
&self, &self,
label: impl Into<ArcCow<'static, str>>, label: impl Into<SharedString>,
expanded: bool, expanded: bool,
theme: &Theme, theme: &Theme,
) -> impl Element<ViewState = S> { ) -> impl Element<ViewState = S> {
@ -128,8 +128,8 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
fn list_item( fn list_item(
&self, &self,
avatar_uri: impl Into<ArcCow<'static, str>>, avatar_uri: impl Into<SharedString>,
label: impl Into<ArcCow<'static, str>>, label: impl Into<SharedString>,
theme: &Theme, theme: &Theme,
) -> impl Element<ViewState = S> { ) -> impl Element<ViewState = S> {
div() div()
@ -180,7 +180,11 @@ mod stories {
} }
} }
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> { fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
Story::container(cx) Story::container(cx)
.child(Story::title_for::<_, CollabPanel<S>>(cx)) .child(Story::title_for::<_, CollabPanel<S>>(cx))
.child(Story::label(cx, "Default")) .child(Story::label(cx, "Default"))