Merge remote-tracking branch 'origin/main' into simplify-action-dispatch

This commit is contained in:
Antonio Scandurra 2023-04-28 17:31:12 +02:00
commit 489b1f6a63
23 changed files with 903 additions and 337 deletions

View file

@ -7,7 +7,8 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle, ShapedBoundary},
Element, FontCache, SceneBuilder, SizeConstraint, TextLayoutCache, View, ViewContext,
AppContext, Element, FontCache, SceneBuilder, SizeConstraint, TextLayoutCache, View,
ViewContext,
};
use log::warn;
use serde_json::json;
@ -17,7 +18,11 @@ pub struct Text {
text: Cow<'static, str>,
style: TextStyle,
soft_wrap: bool,
highlights: Vec<(Range<usize>, HighlightStyle)>,
highlights: Option<Box<[(Range<usize>, HighlightStyle)]>>,
custom_runs: Option<(
Box<[Range<usize>]>,
Box<dyn FnMut(usize, RectF, &mut SceneBuilder, &mut AppContext)>,
)>,
}
pub struct LayoutState {
@ -32,7 +37,8 @@ impl Text {
text: text.into(),
style,
soft_wrap: true,
highlights: Vec::new(),
highlights: None,
custom_runs: None,
}
}
@ -41,8 +47,20 @@ impl Text {
self
}
pub fn with_highlights(mut self, runs: Vec<(Range<usize>, HighlightStyle)>) -> Self {
self.highlights = runs;
pub fn with_highlights(
mut self,
runs: impl Into<Box<[(Range<usize>, HighlightStyle)]>>,
) -> Self {
self.highlights = Some(runs.into());
self
}
pub fn with_custom_runs(
mut self,
runs: impl Into<Box<[Range<usize>]>>,
callback: impl 'static + FnMut(usize, RectF, &mut SceneBuilder, &mut AppContext),
) -> Self {
self.custom_runs = Some((runs.into(), Box::new(callback)));
self
}
@ -65,7 +83,12 @@ impl<V: View> Element<V> for Text {
// Convert the string and highlight ranges into an iterator of highlighted chunks.
let mut offset = 0;
let mut highlight_ranges = self.highlights.iter().peekable();
let mut highlight_ranges = self
.highlights
.as_ref()
.map_or(Default::default(), AsRef::as_ref)
.iter()
.peekable();
let chunks = std::iter::from_fn(|| {
let result;
if let Some((range, highlight_style)) = highlight_ranges.peek() {
@ -152,6 +175,20 @@ impl<V: View> Element<V> for Text {
) -> Self::PaintState {
let mut origin = bounds.origin();
let empty = Vec::new();
let mut callback = |_, _, _: &mut SceneBuilder, _: &mut AppContext| {};
let mouse_runs;
let custom_run_callback;
if let Some((runs, build_region)) = &mut self.custom_runs {
mouse_runs = runs.iter();
custom_run_callback = build_region.as_mut();
} else {
mouse_runs = [].iter();
custom_run_callback = &mut callback;
}
let mut custom_runs = mouse_runs.enumerate().peekable();
let mut offset = 0;
for (ix, line) in layout.shaped_lines.iter().enumerate() {
let wrap_boundaries = layout.wrap_boundaries.get(ix).unwrap_or(&empty);
let boundaries = RectF::new(
@ -169,13 +206,103 @@ impl<V: View> Element<V> for Text {
origin,
visible_bounds,
layout.line_height,
wrap_boundaries.iter().copied(),
wrap_boundaries,
cx,
);
} else {
line.paint(scene, origin, visible_bounds, layout.line_height, cx);
}
}
// Paint any custom runs that intersect this line.
let end_offset = offset + line.len();
if let Some((custom_run_ix, custom_run_range)) = custom_runs.peek().cloned() {
if custom_run_range.start < end_offset {
let mut current_custom_run = None;
if custom_run_range.start <= offset {
current_custom_run = Some((custom_run_ix, custom_run_range.end, origin));
}
let mut glyph_origin = origin;
let mut prev_position = 0.;
let mut wrap_boundaries = wrap_boundaries.iter().copied().peekable();
for (run_ix, glyph_ix, glyph) in
line.runs().iter().enumerate().flat_map(|(run_ix, run)| {
run.glyphs()
.iter()
.enumerate()
.map(move |(ix, glyph)| (run_ix, ix, glyph))
})
{
glyph_origin.set_x(glyph_origin.x() + glyph.position.x() - prev_position);
prev_position = glyph.position.x();
// If we've reached a soft wrap position, move down one line. If there
// is a custom run in-progress, paint it.
if wrap_boundaries
.peek()
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
{
if let Some((run_ix, _, run_origin)) = &mut current_custom_run {
let bounds = RectF::from_points(
*run_origin,
glyph_origin + vec2f(0., layout.line_height),
);
custom_run_callback(*run_ix, bounds, scene, cx);
*run_origin =
vec2f(origin.x(), glyph_origin.y() + layout.line_height);
}
wrap_boundaries.next();
glyph_origin = vec2f(origin.x(), glyph_origin.y() + layout.line_height);
}
// If we've reached the end of the current custom run, paint it.
if let Some((run_ix, run_end_offset, run_origin)) = current_custom_run {
if offset + glyph.index == run_end_offset {
current_custom_run.take();
let bounds = RectF::from_points(
run_origin,
glyph_origin + vec2f(0., layout.line_height),
);
custom_run_callback(run_ix, bounds, scene, cx);
custom_runs.next();
}
if let Some((_, run_range)) = custom_runs.peek() {
if run_range.start >= end_offset {
break;
}
if run_range.start == offset + glyph.index {
current_custom_run =
Some((run_ix, run_range.end, glyph_origin));
}
}
}
// If we've reached the start of a new custom run, start tracking it.
if let Some((run_ix, run_range)) = custom_runs.peek() {
if offset + glyph.index == run_range.start {
current_custom_run = Some((*run_ix, run_range.end, glyph_origin));
}
}
}
// If a custom run extends beyond the end of the line, paint it.
if let Some((run_ix, run_end_offset, run_origin)) = current_custom_run {
let line_end = glyph_origin + vec2f(line.width() - prev_position, 0.);
let bounds = RectF::from_points(
run_origin,
line_end + vec2f(0., layout.line_height),
);
custom_run_callback(run_ix, bounds, scene, cx);
if end_offset == run_end_offset {
custom_runs.next();
}
}
}
}
offset = end_offset + 1;
origin.set_y(boundaries.max_y());
}
}

View file

@ -177,7 +177,14 @@ impl<'a> Hash for CacheKeyRef<'a> {
#[derive(Default, Debug, Clone)]
pub struct Line {
layout: Arc<LineLayout>,
style_runs: SmallVec<[(u32, Color, Underline); 32]>,
style_runs: SmallVec<[StyleRun; 32]>,
}
#[derive(Debug, Clone, Copy)]
struct StyleRun {
len: u32,
color: Color,
underline: Underline,
}
#[derive(Default, Debug)]
@ -208,7 +215,11 @@ impl Line {
fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
let mut style_runs = SmallVec::new();
for (len, style) in runs {
style_runs.push((*len as u32, style.color, style.underline));
style_runs.push(StyleRun {
len: *len as u32,
color: style.color,
underline: style.underline,
});
}
Self { layout, style_runs }
}
@ -301,28 +312,30 @@ impl Line {
let mut finished_underline = None;
if glyph.index >= run_end {
if let Some((run_len, run_color, run_underline)) = style_runs.next() {
if let Some(style_run) = style_runs.next() {
if let Some((_, underline_style)) = underline {
if *run_underline != underline_style {
if style_run.underline != underline_style {
finished_underline = underline.take();
}
}
if run_underline.thickness.into_inner() > 0. {
if style_run.underline.thickness.into_inner() > 0. {
underline.get_or_insert((
vec2f(
glyph_origin.x(),
origin.y() + baseline_offset.y() + 0.618 * self.layout.descent,
),
Underline {
color: Some(run_underline.color.unwrap_or(*run_color)),
thickness: run_underline.thickness,
squiggly: run_underline.squiggly,
color: Some(
style_run.underline.color.unwrap_or(style_run.color),
),
thickness: style_run.underline.thickness,
squiggly: style_run.underline.squiggly,
},
));
}
run_end += *run_len as usize;
color = *run_color;
run_end += style_run.len as usize;
color = style_run.color;
} else {
run_end = self.layout.len;
finished_underline = underline.take();
@ -380,41 +393,85 @@ impl Line {
origin: Vector2F,
visible_bounds: RectF,
line_height: f32,
boundaries: impl IntoIterator<Item = ShapedBoundary>,
boundaries: &[ShapedBoundary],
cx: &mut WindowContext,
) {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
let mut boundaries = boundaries.into_iter().peekable();
let mut color_runs = self.style_runs.iter();
let mut color_end = 0;
let mut style_run_end = 0;
let mut color = Color::black();
let mut underline: Option<(Vector2F, Underline)> = None;
let mut glyph_origin = vec2f(0., 0.);
let mut glyph_origin = origin;
let mut prev_position = 0.;
for run in &self.layout.runs {
for (run_ix, run) in self.layout.runs.iter().enumerate() {
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
if boundaries.peek().map_or(false, |b| b.glyph_ix == glyph_ix) {
glyph_origin.set_x(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();
glyph_origin = vec2f(0., glyph_origin.y() + line_height);
} else {
glyph_origin.set_x(glyph_origin.x() + glyph.position.x() - prev_position);
if let Some((underline_origin, underline_style)) = underline {
scene.push_underline(scene::Underline {
origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(),
color: underline_style.color.unwrap(),
squiggly: underline_style.squiggly,
});
}
glyph_origin = vec2f(origin.x(), glyph_origin.y() + line_height);
}
prev_position = glyph.position.x();
if glyph.index >= color_end {
if let Some(next_run) = color_runs.next() {
color_end += next_run.0 as usize;
color = next_run.1;
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)) = underline {
if style_run.underline != underline_style {
finished_underline = underline.take();
}
}
if style_run.underline.thickness.into_inner() > 0. {
underline.get_or_insert((
glyph_origin
+ vec2f(0., baseline_offset.y() + 0.618 * self.layout.descent),
Underline {
color: Some(
style_run.underline.color.unwrap_or(style_run.color),
),
thickness: style_run.underline.thickness,
squiggly: style_run.underline.squiggly,
},
));
}
} else {
color_end = self.layout.len;
style_run_end = self.layout.len;
color = Color::black();
finished_underline = underline.take();
}
}
if let Some((underline_origin, underline_style)) = finished_underline {
scene.push_underline(scene::Underline {
origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(),
color: underline_style.color.unwrap(),
squiggly: underline_style.squiggly,
});
}
let glyph_bounds = RectF::new(
origin + glyph_origin,
glyph_origin,
cx.font_cache
.bounding_box(run.font_id, self.layout.font_size),
);
@ -424,20 +481,31 @@ impl Line {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_bounds.origin() + baseline_origin,
origin: glyph_bounds.origin() + baseline_offset,
});
} else {
scene.push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_bounds.origin() + baseline_origin,
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;
scene.push_underline(scene::Underline {
origin: underline_origin,
width: line_end_x - underline_origin.x(),
thickness: underline_style.thickness.into(),
color: underline_style.color.unwrap(),
squiggly: underline_style.squiggly,
});
}
}
}