1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-02 06:40:06 -04:00

Make Galleys share Rows and store their offsets

This commit is contained in:
Hubert Głuchowski
2024-11-28 17:40:59 +01:00
parent 150c0f662b
commit db32a1ed44
13 changed files with 174 additions and 127 deletions

View File

@@ -39,11 +39,11 @@ pub fn update_accesskit_for_text_widget(
}; };
ctx.with_accessibility_parent(parent_id, || { ctx.with_accessibility_parent(parent_id, || {
for (row_index, row) in galley.rows.iter().enumerate() { for (row_index, (row, offset)) in galley.rows.iter().enumerate() {
let row_id = parent_id.with(row_index); let row_id = parent_id.with(row_index);
ctx.accesskit_node_builder(row_id, |builder| { ctx.accesskit_node_builder(row_id, |builder| {
builder.set_role(accesskit::Role::TextRun); builder.set_role(accesskit::Role::TextRun);
let rect = row.rect.translate(galley_pos.to_vec2()); let rect = row.rect.translate(offset.to_vec2() + galley_pos.to_vec2());
builder.set_bounds(accesskit::Rect { builder.set_bounds(accesskit::Rect {
x0: rect.min.x.into(), x0: rect.min.x.into(),
y0: rect.min.y.into(), y0: rect.min.y.into(),

View File

@@ -284,7 +284,7 @@ fn ccursor_from_accesskit_text_position(
position: &accesskit::TextPosition, position: &accesskit::TextPosition,
) -> Option<CCursor> { ) -> Option<CCursor> {
let mut total_length = 0usize; let mut total_length = 0usize;
for (i, row) in galley.rows.iter().enumerate() { for (i, (row, _)) in galley.rows.iter().enumerate() {
let row_id = id.with(i); let row_id = id.with(i);
if row_id.accesskit_id() == position.node { if row_id.accesskit_id() == position.node {
return Some(CCursor { return Some(CCursor {

View File

@@ -179,7 +179,10 @@ impl LabelSelectionState {
if let epaint::Shape::Text(text_shape) = &mut shape.shape { if let epaint::Shape::Text(text_shape) = &mut shape.shape {
let galley = Arc::make_mut(&mut text_shape.galley); let galley = Arc::make_mut(&mut text_shape.galley);
for row_selection in row_selections { for row_selection in row_selections {
if let Some(row) = galley.rows.get_mut(row_selection.row) { if let Some((row, _)) =
galley.rows.get_mut(row_selection.row)
{
let row = Arc::make_mut(row);
for vertex_index in row_selection.vertex_indices { for vertex_index in row_selection.vertex_indices {
if let Some(vertex) = row if let Some(vertex) = row
.visuals .visuals
@@ -659,7 +662,7 @@ fn selected_text(galley: &Galley, cursor_range: &CursorRange) -> String {
} }
fn estimate_row_height(galley: &Galley) -> f32 { fn estimate_row_height(galley: &Galley) -> f32 {
if let Some(row) = galley.rows.first() { if let Some((row, _)) = galley.rows.first() {
row.rect.height() row.rect.height()
} else { } else {
galley.size().y galley.size().y

View File

@@ -31,7 +31,9 @@ pub fn paint_text_selection(
let max = max.rcursor; let max = max.rcursor;
for ri in min.row..=max.row { for ri in min.row..=max.row {
let row = &mut galley.rows[ri]; let (row, _) = &mut galley.rows[ri];
let row = Arc::make_mut(row);
let left = if ri == min.row { let left = if ri == min.row {
row.x_offset(min.column) row.x_offset(min.column)
} else { } else {

View File

@@ -640,7 +640,7 @@ impl WidgetText {
Self::RichText(text) => text.font_height(fonts, style), Self::RichText(text) => text.font_height(fonts, style),
Self::LayoutJob(job) => job.font_height(fonts), Self::LayoutJob(job) => job.font_height(fonts),
Self::Galley(galley) => { Self::Galley(galley) => {
if let Some(row) = galley.rows.first() { if let Some((row, _)) = galley.rows.first() {
row.height() row.height()
} else { } else {
galley.size().y galley.size().y

View File

@@ -1,8 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{
epaint, pos2, text_selection, vec2, Align, Direction, FontSelection, Galley, Pos2, Response, epaint, pos2, text_selection, Align, Direction, FontSelection, Galley, Pos2, Response, Sense,
Sense, Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType, Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType,
}; };
use self::text_selection::LabelSelectionState; use self::text_selection::LabelSelectionState;
@@ -194,10 +194,13 @@ impl Label {
let pos = pos2(ui.max_rect().left(), ui.cursor().top()); let pos = pos2(ui.max_rect().left(), ui.cursor().top());
assert!(!galley.rows.is_empty(), "Galleys are never empty"); assert!(!galley.rows.is_empty(), "Galleys are never empty");
// collect a response from many rows: // collect a response from many rows:
let rect = galley.rows[0].rect.translate(vec2(pos.x, pos.y)); let rect = galley.rows[0]
.0
.rect
.translate(galley.rows[0].1.to_vec2() + pos.to_vec2());
let mut response = ui.allocate_rect(rect, sense); let mut response = ui.allocate_rect(rect, sense);
for row in galley.rows.iter().skip(1) { for (row, offset) in galley.rows.iter().skip(1) {
let rect = row.rect.translate(vec2(pos.x, pos.y)); let rect = row.rect.translate(offset.to_vec2() + pos.to_vec2());
response |= ui.allocate_rect(rect, sense); response |= ui.allocate_rect(rect, sense);
} }
(pos, galley, response) (pos, galley, response)

View File

@@ -433,7 +433,9 @@ impl Shape {
// Scale text: // Scale text:
let galley = Arc::make_mut(&mut text_shape.galley); let galley = Arc::make_mut(&mut text_shape.galley);
for row in &mut galley.rows { for (row, offset) in &mut galley.rows {
let row = Arc::make_mut(row);
*offset = *offset * transform.scaling;
row.visuals.mesh_bounds = transform.scaling * row.visuals.mesh_bounds; row.visuals.mesh_bounds = transform.scaling * row.visuals.mesh_bounds;
for v in &mut row.visuals.mesh.vertices { for v in &mut row.visuals.mesh.vertices {
v.pos = Pos2::new(transform.scaling * v.pos.x, transform.scaling * v.pos.y); v.pos = Pos2::new(transform.scaling * v.pos.x, transform.scaling * v.pos.y);

View File

@@ -88,7 +88,8 @@ pub fn adjust_colors(
if !galley.is_empty() { if !galley.is_empty() {
let galley = std::sync::Arc::make_mut(galley); let galley = std::sync::Arc::make_mut(galley);
for row in &mut galley.rows { for (row, _) in &mut galley.rows {
let row = Arc::make_mut(row);
for vertex in &mut row.visuals.mesh.vertices { for vertex in &mut row.visuals.mesh.vertices {
adjust_color(&mut vertex.color); adjust_color(&mut vertex.color);
} }

View File

@@ -88,7 +88,11 @@ impl AllocInfo {
pub fn from_galley(galley: &Galley) -> Self { pub fn from_galley(galley: &Galley) -> Self {
Self::from_slice(galley.text().as_bytes()) Self::from_slice(galley.text().as_bytes())
+ Self::from_slice(&galley.rows) + Self::from_slice(&galley.rows)
+ galley.rows.iter().map(Self::from_galley_row).sum() + galley
.rows
.iter()
.map(|(row, _)| Self::from_galley_row(row))
.sum()
} }
fn from_galley_row(row: &crate::text::Row) -> Self { fn from_galley_row(row: &crate::text::Row) -> Self {
@@ -213,8 +217,8 @@ impl PaintStats {
self.shape_text += AllocInfo::from_galley(&text_shape.galley); self.shape_text += AllocInfo::from_galley(&text_shape.galley);
for row in &text_shape.galley.rows { for row in &text_shape.galley.rows {
self.text_shape_indices += AllocInfo::from_slice(&row.visuals.mesh.indices); self.text_shape_indices += AllocInfo::from_slice(&row.0.visuals.mesh.indices);
self.text_shape_vertices += AllocInfo::from_slice(&row.visuals.mesh.vertices); self.text_shape_vertices += AllocInfo::from_slice(&row.0.visuals.mesh.vertices);
} }
} }
Shape::Mesh(mesh) => { Shape::Mesh(mesh) => {

View File

@@ -1778,16 +1778,18 @@ impl Tessellator {
let rotator = Rot2::from_angle(*angle); let rotator = Rot2::from_angle(*angle);
for row in &galley.rows { for (row, row_pos) in &galley.rows {
if row.visuals.mesh.is_empty() { if row.visuals.mesh.is_empty() {
continue; continue;
} }
let final_pos = galley_pos + row_pos.to_vec2();
let mut row_rect = row.visuals.mesh_bounds; let mut row_rect = row.visuals.mesh_bounds;
if *angle != 0.0 { if *angle != 0.0 {
row_rect = row_rect.rotate_bb(rotator); row_rect = row_rect.rotate_bb(rotator);
} }
row_rect = row_rect.translate(galley_pos.to_vec2()); row_rect = row_rect.translate(final_pos.to_vec2());
if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) { if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) {
// culling individual lines of text is important, since a single `Shape::Text` // culling individual lines of text is important, since a single `Shape::Text`
@@ -1836,7 +1838,7 @@ impl Tessellator {
}; };
Vertex { Vertex {
pos: galley_pos + offset, pos: final_pos + offset,
uv: (uv.to_vec2() * uv_normalizer).to_pos2(), uv: (uv.to_vec2() * uv_normalizer).to_pos2(),
color, color,
} }

View File

@@ -832,34 +832,14 @@ impl GalleyCache {
for galley in galleys { for galley in galleys {
let current_offset = emath::vec2(0.0, merged_galley.rect.height()); let current_offset = emath::vec2(0.0, merged_galley.rect.height());
merged_galley.rows.extend(galley.rows.iter().map(|row| { merged_galley
super::Row { .rows
// FIXME: what is this??? .extend(galley.rows.iter().map(|(row, prev_offset)| {
section_index_at_start: row.section_index_at_start, merged_galley.mesh_bounds =
glyphs: row merged_galley.mesh_bounds.union(row.visuals.mesh_bounds);
.glyphs
.iter() (row.clone(), *prev_offset + current_offset)
.cloned() }));
.map(|mut p| {
p.pos.y += current_offset.y;
p
})
.collect(),
rect: row.rect.translate(current_offset),
visuals: {
let mut visuals = row.visuals.clone();
for vertex in visuals.mesh.vertices.iter_mut() {
vertex.pos.y += current_offset.y;
}
visuals.mesh_bounds =
visuals.mesh_bounds.translate(current_offset);
merged_galley.mesh_bounds =
merged_galley.mesh_bounds.union(visuals.mesh_bounds);
visuals
},
ends_with_newline: row.ends_with_newline,
}
}));
merged_galley.rect = merged_galley merged_galley.rect = merged_galley
.rect .rect
.union(galley.rect.translate(current_offset)); .union(galley.rect.translate(current_offset));

View File

@@ -96,7 +96,8 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
let mut elided = false; let mut elided = false;
let mut rows = rows_from_paragraphs(paragraphs, &job, &mut elided); let mut rows = rows_from_paragraphs(paragraphs, &job, &mut elided);
if elided { if elided {
if let Some(last_row) = rows.last_mut() { if let Some((last_row, _)) = rows.last_mut() {
let last_row = Arc::get_mut(last_row).unwrap();
replace_last_glyph_with_overflow_character(fonts, &job, last_row); replace_last_glyph_with_overflow_character(fonts, &job, last_row);
if let Some(last) = last_row.glyphs.last() { if let Some(last) = last_row.glyphs.last() {
last_row.rect.max.x = last.max_x(); last_row.rect.max.x = last.max_x();
@@ -108,12 +109,12 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
if justify || job.halign != Align::LEFT { if justify || job.halign != Align::LEFT {
let num_rows = rows.len(); let num_rows = rows.len();
for (i, row) in rows.iter_mut().enumerate() { for (i, (row, _)) in rows.iter_mut().enumerate() {
let is_last_row = i + 1 == num_rows; let is_last_row = i + 1 == num_rows;
let justify_row = justify && !row.ends_with_newline && !is_last_row; let justify_row = justify && !row.ends_with_newline && !is_last_row;
halign_and_justify_row( halign_and_justify_row(
point_scale, point_scale,
row, Arc::get_mut(row).unwrap(),
job.halign, job.halign,
job.wrap.max_width, job.wrap.max_width,
justify_row, justify_row,
@@ -198,7 +199,7 @@ fn rows_from_paragraphs(
paragraphs: Vec<Paragraph>, paragraphs: Vec<Paragraph>,
job: &LayoutJob, job: &LayoutJob,
elided: &mut bool, elided: &mut bool,
) -> Vec<Row> { ) -> Vec<(Arc<Row>, Pos2)> {
let num_paragraphs = paragraphs.len(); let num_paragraphs = paragraphs.len();
let mut rows = vec![]; let mut rows = vec![];
@@ -212,31 +213,38 @@ fn rows_from_paragraphs(
let is_last_paragraph = (i + 1) == num_paragraphs; let is_last_paragraph = (i + 1) == num_paragraphs;
if paragraph.glyphs.is_empty() { if paragraph.glyphs.is_empty() {
rows.push(Row { rows.push((
section_index_at_start: paragraph.section_index_at_start, Arc::new(Row {
glyphs: vec![], section_index_at_start: paragraph.section_index_at_start,
visuals: Default::default(), glyphs: vec![],
rect: Rect::from_min_size( visuals: Default::default(),
pos2(paragraph.cursor_x, 0.0), rect: Rect::from_min_size(
vec2(0.0, paragraph.empty_paragraph_height), pos2(paragraph.cursor_x, 0.0),
), vec2(0.0, paragraph.empty_paragraph_height),
ends_with_newline: !is_last_paragraph, ),
}); ends_with_newline: !is_last_paragraph,
}),
Pos2::ZERO,
));
} else { } else {
let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x(); let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x();
if paragraph_max_x <= job.effective_wrap_width() { if paragraph_max_x <= job.effective_wrap_width() {
// Early-out optimization: the whole paragraph fits on one row. // Early-out optimization: the whole paragraph fits on one row.
let paragraph_min_x = paragraph.glyphs[0].pos.x; let paragraph_min_x = paragraph.glyphs[0].pos.x;
rows.push(Row { rows.push((
section_index_at_start: paragraph.section_index_at_start, Arc::new(Row {
glyphs: paragraph.glyphs, section_index_at_start: paragraph.section_index_at_start,
visuals: Default::default(), glyphs: paragraph.glyphs,
rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), visuals: Default::default(),
ends_with_newline: !is_last_paragraph, rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x),
}); ends_with_newline: !is_last_paragraph,
}),
Pos2::ZERO,
));
} else { } else {
line_break(&paragraph, job, &mut rows, elided); line_break(&paragraph, job, &mut rows, elided);
rows.last_mut().unwrap().ends_with_newline = !is_last_paragraph; let last_row = Arc::get_mut(&mut rows.last_mut().unwrap().0).unwrap();
last_row.ends_with_newline = !is_last_paragraph;
} }
} }
} }
@@ -244,7 +252,12 @@ fn rows_from_paragraphs(
rows rows
} }
fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec<Row>, elided: &mut bool) { fn line_break(
paragraph: &Paragraph,
job: &LayoutJob,
out_rows: &mut Vec<(Arc<Row>, Pos2)>,
elided: &mut bool,
) {
let wrap_width = job.effective_wrap_width(); let wrap_width = job.effective_wrap_width();
// Keeps track of good places to insert row break if we exceed `wrap_width`. // Keeps track of good places to insert row break if we exceed `wrap_width`.
@@ -270,13 +283,16 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec<Row>, e
{ {
// Allow the first row to be completely empty, because we know there will be more space on the next row: // Allow the first row to be completely empty, because we know there will be more space on the next row:
// TODO(emilk): this records the height of this first row as zero, though that is probably fine since first_row_indentation usually comes with a first_row_min_height. // TODO(emilk): this records the height of this first row as zero, though that is probably fine since first_row_indentation usually comes with a first_row_min_height.
out_rows.push(Row { out_rows.push((
section_index_at_start: paragraph.section_index_at_start, Arc::new(Row {
glyphs: vec![], section_index_at_start: paragraph.section_index_at_start,
visuals: Default::default(), glyphs: vec![],
rect: rect_from_x_range(first_row_indentation..=first_row_indentation), visuals: Default::default(),
ends_with_newline: false, rect: rect_from_x_range(first_row_indentation..=first_row_indentation),
}); ends_with_newline: false,
}),
Pos2::ZERO,
));
row_start_x += first_row_indentation; row_start_x += first_row_indentation;
first_row_indentation = 0.0; first_row_indentation = 0.0;
} else if let Some(last_kept_index) = row_break_candidates.get(job.wrap.break_anywhere) } else if let Some(last_kept_index) = row_break_candidates.get(job.wrap.break_anywhere)
@@ -294,13 +310,16 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec<Row>, e
let paragraph_min_x = glyphs[0].pos.x; let paragraph_min_x = glyphs[0].pos.x;
let paragraph_max_x = glyphs.last().unwrap().max_x(); let paragraph_max_x = glyphs.last().unwrap().max_x();
out_rows.push(Row { out_rows.push((
section_index_at_start, Arc::new(Row {
glyphs, section_index_at_start,
visuals: Default::default(), glyphs,
rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), visuals: Default::default(),
ends_with_newline: false, rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x),
}); ends_with_newline: false,
}),
Pos2::ZERO,
));
// Start a new row: // Start a new row:
row_start_idx = last_kept_index + 1; row_start_idx = last_kept_index + 1;
@@ -333,13 +352,16 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec<Row>, e
let paragraph_min_x = glyphs[0].pos.x; let paragraph_min_x = glyphs[0].pos.x;
let paragraph_max_x = glyphs.last().unwrap().max_x(); let paragraph_max_x = glyphs.last().unwrap().max_x();
out_rows.push(Row { out_rows.push((
section_index_at_start, Arc::new(Row {
glyphs, section_index_at_start,
visuals: Default::default(), glyphs,
rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), visuals: Default::default(),
ends_with_newline: false, rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x),
}); ends_with_newline: false,
}),
Pos2::ZERO,
));
} }
} }
} }
@@ -592,14 +614,15 @@ fn halign_and_justify_row(
fn galley_from_rows( fn galley_from_rows(
point_scale: PointScale, point_scale: PointScale,
job: Arc<LayoutJob>, job: Arc<LayoutJob>,
mut rows: Vec<Row>, mut rows: Vec<(Arc<Row>, Pos2)>,
elided: bool, elided: bool,
) -> Galley { ) -> Galley {
let mut first_row_min_height = job.first_row_min_height; let mut first_row_min_height = job.first_row_min_height;
let mut cursor_y = 0.0; let mut cursor_y = 0.0;
let mut min_x: f32 = 0.0; let mut min_x: f32 = 0.0;
let mut max_x: f32 = 0.0; let mut max_x: f32 = 0.0;
for row in &mut rows { for (row, _) in &mut rows {
let row = Arc::get_mut(row).unwrap();
let mut max_row_height = first_row_min_height.max(row.rect.height()); let mut max_row_height = first_row_min_height.max(row.rect.height());
first_row_min_height = 0.0; first_row_min_height = 0.0;
for glyph in &row.glyphs { for glyph in &row.glyphs {
@@ -639,7 +662,8 @@ fn galley_from_rows(
let mut num_vertices = 0; let mut num_vertices = 0;
let mut num_indices = 0; let mut num_indices = 0;
for row in &mut rows { for (row, _) in &mut rows {
let row = Arc::get_mut(row).unwrap();
row.visuals = tessellate_row(point_scale, &job, &format_summary, row); row.visuals = tessellate_row(point_scale, &job, &format_summary, row);
mesh_bounds = mesh_bounds.union(row.visuals.mesh_bounds); mesh_bounds = mesh_bounds.union(row.visuals.mesh_bounds);
num_vertices += row.visuals.mesh.vertices.len(); num_vertices += row.visuals.mesh.vertices.len();
@@ -1072,7 +1096,7 @@ mod tests {
assert!(galley.elided); assert!(galley.elided);
assert_eq!(galley.rows.len(), 1); assert_eq!(galley.rows.len(), 1);
let row_text = galley.rows[0].text(); let row_text = galley.rows[0].0.text();
assert!( assert!(
row_text.ends_with('…'), row_text.ends_with('…'),
"Expected row to end with `…`, got {row_text:?} when line-breaking the text {text:?} with max_width {max_width} and break_anywhere {break_anywhere}.", "Expected row to end with `…`, got {row_text:?} when line-breaking the text {text:?} with max_width {max_width} and break_anywhere {break_anywhere}.",
@@ -1091,7 +1115,7 @@ mod tests {
assert!(galley.elided); assert!(galley.elided);
assert_eq!(galley.rows.len(), 1); assert_eq!(galley.rows.len(), 1);
let row_text = galley.rows[0].text(); let row_text = galley.rows[0].0.text();
assert_eq!(row_text, "Hello…"); assert_eq!(row_text, "Hello…");
} }
} }
@@ -1106,7 +1130,11 @@ mod tests {
layout_job.wrap.max_width = 90.0; layout_job.wrap.max_width = 90.0;
let galley = layout(&mut fonts, layout_job.into()); let galley = layout(&mut fonts, layout_job.into());
assert_eq!( assert_eq!(
galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(), galley
.rows
.iter()
.map(|row| row.0.text())
.collect::<Vec<_>>(),
vec!["日本語と", "Englishの混在", "した文章"] vec!["日本語と", "Englishの混在", "した文章"]
); );
} }
@@ -1121,7 +1149,11 @@ mod tests {
layout_job.wrap.max_width = 110.0; layout_job.wrap.max_width = 110.0;
let galley = layout(&mut fonts, layout_job.into()); let galley = layout(&mut fonts, layout_job.into());
assert_eq!( assert_eq!(
galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(), galley
.rows
.iter()
.map(|row| row.0.text())
.collect::<Vec<_>>(),
vec!["日本語とEnglish", "の混在した文章"] vec!["日本語とEnglish", "の混在した文章"]
); );
} }
@@ -1136,10 +1168,14 @@ mod tests {
let galley = layout(&mut fonts, layout_job.into()); let galley = layout(&mut fonts, layout_job.into());
assert!(galley.elided); assert!(galley.elided);
assert_eq!( assert_eq!(
galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(), galley
.rows
.iter()
.map(|row| row.0.text())
.collect::<Vec<_>>(),
vec!["# DNA…"] vec!["# DNA…"]
); );
let row = &galley.rows[0]; let row = &galley.rows[0];
assert_eq!(row.rect.max.x, row.glyphs.last().unwrap().max_x()); assert_eq!(row.0.rect.max.x, row.0.glyphs.last().unwrap().max_x());
} }
} }

View File

@@ -499,14 +499,14 @@ pub struct Galley {
/// Contains the original string and style sections. /// Contains the original string and style sections.
pub job: Arc<LayoutJob>, pub job: Arc<LayoutJob>,
/// Rows of text, from top to bottom. /// Rows of text, from top to bottom, and their offsets.
/// ///
/// The number of characters in all rows sum up to `job.text.chars().count()` /// The number of characters in all rows sum up to `job.text.chars().count()`
/// unless [`Self::elided`] is `true`. /// unless [`Self::elided`] is `true`.
/// ///
/// Note that a paragraph (a piece of text separated with `\n`) /// Note that a paragraph (a piece of text separated with `\n`)
/// can be split up into multiple rows. /// can be split up into multiple rows.
pub rows: Vec<Row>, pub rows: Vec<(Arc<Row>, Pos2)>,
/// Set to true the text was truncated due to [`TextWrapping::max_rows`]. /// Set to true the text was truncated due to [`TextWrapping::max_rows`].
pub elided: bool, pub elided: bool,
@@ -755,7 +755,7 @@ impl std::ops::Deref for Galley {
impl Galley { impl Galley {
/// Zero-width rect past the last character. /// Zero-width rect past the last character.
fn end_pos(&self) -> Rect { fn end_pos(&self) -> Rect {
if let Some(row) = self.rows.last() { if let Some((row, _)) = self.rows.last() {
let x = row.rect.right(); let x = row.rect.right();
Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y())) Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
} else { } else {
@@ -773,7 +773,7 @@ impl Galley {
pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Rect { pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Rect {
let mut it = PCursor::default(); let mut it = PCursor::default();
for row in &self.rows { for (row, offset) in &self.rows {
if it.paragraph == pcursor.paragraph { if it.paragraph == pcursor.paragraph {
// Right paragraph, but is it the right row in the paragraph? // Right paragraph, but is it the right row in the paragraph?
@@ -787,8 +787,11 @@ impl Galley {
&& !row.ends_with_newline && !row.ends_with_newline
&& column >= row.char_count_excluding_newline(); && column >= row.char_count_excluding_newline();
if !select_next_row_instead { if !select_next_row_instead {
let x = row.x_offset(column); let x = row.x_offset(column) + offset.x;
return Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y())); return Rect::from_min_max(
pos2(x, row.min_y() + offset.y),
pos2(x, row.max_y() + offset.y),
);
} }
} }
} }
@@ -822,13 +825,13 @@ impl Galley {
/// same as a cursor at the end. /// same as a cursor at the end.
/// This allows implementing text-selection by dragging above/below the galley. /// This allows implementing text-selection by dragging above/below the galley.
pub fn cursor_from_pos(&self, pos: Vec2) -> Cursor { pub fn cursor_from_pos(&self, pos: Vec2) -> Cursor {
if let Some(first_row) = self.rows.first() { if let Some((first_row, offset)) = self.rows.first() {
if pos.y < first_row.min_y() { if pos.y < first_row.min_y() + offset.y {
return self.begin(); return self.begin();
} }
} }
if let Some(last_row) = self.rows.last() { if let Some((last_row, offset)) = self.rows.last() {
if last_row.max_y() < pos.y { if last_row.max_y() + offset.y < pos.y {
return self.end(); return self.end();
} }
} }
@@ -839,9 +842,12 @@ impl Galley {
let mut ccursor_index = 0; let mut ccursor_index = 0;
let mut pcursor_it = PCursor::default(); let mut pcursor_it = PCursor::default();
for (row_nr, row) in self.rows.iter().enumerate() { for (row_nr, (row, offset)) in self.rows.iter().enumerate() {
let is_pos_within_row = row.min_y() <= pos.y && pos.y <= row.max_y(); let min_y = row.min_y() + offset.y;
let y_dist = (row.min_y() - pos.y).abs().min((row.max_y() - pos.y).abs()); let max_y = row.max_y() + offset.y;
let is_pos_within_row = min_y <= pos.y && pos.y <= max_y;
let y_dist = (min_y - pos.y).abs().min((max_y - pos.y).abs());
if is_pos_within_row || y_dist < best_y_dist { if is_pos_within_row || y_dist < best_y_dist {
best_y_dist = y_dist; best_y_dist = y_dist;
let column = row.char_at(pos.x); let column = row.char_at(pos.x);
@@ -904,7 +910,7 @@ impl Galley {
offset: 0, offset: 0,
prefer_next_row: true, prefer_next_row: true,
}; };
for row in &self.rows { for (row, _) in &self.rows {
let row_char_count = row.char_count_including_newline(); let row_char_count = row.char_count_including_newline();
ccursor.index += row_char_count; ccursor.index += row_char_count;
if row.ends_with_newline { if row.ends_with_newline {
@@ -922,7 +928,7 @@ impl Galley {
} }
pub fn end_rcursor(&self) -> RCursor { pub fn end_rcursor(&self) -> RCursor {
if let Some(last_row) = self.rows.last() { if let Some((last_row, _)) = self.rows.last() {
RCursor { RCursor {
row: self.rows.len() - 1, row: self.rows.len() - 1,
column: last_row.char_count_including_newline(), column: last_row.char_count_including_newline(),
@@ -948,7 +954,7 @@ impl Galley {
prefer_next_row, prefer_next_row,
}; };
for (row_nr, row) in self.rows.iter().enumerate() { for (row_nr, (row, _)) in self.rows.iter().enumerate() {
let row_char_count = row.char_count_excluding_newline(); let row_char_count = row.char_count_excluding_newline();
if ccursor_it.index <= ccursor.index if ccursor_it.index <= ccursor.index
@@ -993,7 +999,7 @@ impl Galley {
} }
let prefer_next_row = let prefer_next_row =
rcursor.column < self.rows[rcursor.row].char_count_excluding_newline(); rcursor.column < self.rows[rcursor.row].0.char_count_excluding_newline();
let mut ccursor_it = CCursor { let mut ccursor_it = CCursor {
index: 0, index: 0,
prefer_next_row, prefer_next_row,
@@ -1004,7 +1010,7 @@ impl Galley {
prefer_next_row, prefer_next_row,
}; };
for (row_nr, row) in self.rows.iter().enumerate() { for (row_nr, (row, _)) in self.rows.iter().enumerate() {
if row_nr == rcursor.row { if row_nr == rcursor.row {
ccursor_it.index += rcursor.column.at_most(row.char_count_excluding_newline()); ccursor_it.index += rcursor.column.at_most(row.char_count_excluding_newline());
@@ -1048,7 +1054,7 @@ impl Galley {
prefer_next_row, prefer_next_row,
}; };
for (row_nr, row) in self.rows.iter().enumerate() { for (row_nr, (row, _)) in self.rows.iter().enumerate() {
if pcursor_it.paragraph == pcursor.paragraph { if pcursor_it.paragraph == pcursor.paragraph {
// Right paragraph, but is it the right row in the paragraph? // Right paragraph, but is it the right row in the paragraph?
@@ -1122,7 +1128,9 @@ impl Galley {
let new_row = cursor.rcursor.row - 1; let new_row = cursor.rcursor.row - 1;
let cursor_is_beyond_end_of_current_row = cursor.rcursor.column let cursor_is_beyond_end_of_current_row = cursor.rcursor.column
>= self.rows[cursor.rcursor.row].char_count_excluding_newline(); >= self.rows[cursor.rcursor.row]
.0
.char_count_excluding_newline();
let new_rcursor = if cursor_is_beyond_end_of_current_row { let new_rcursor = if cursor_is_beyond_end_of_current_row {
// keep same column // keep same column
@@ -1133,11 +1141,12 @@ impl Galley {
} else { } else {
// keep same X coord // keep same X coord
let x = self.pos_from_cursor(cursor).center().x; let x = self.pos_from_cursor(cursor).center().x;
let column = if x > self.rows[new_row].rect.right() { let (row, offset) = &self.rows[new_row];
let column = if x > row.rect.right() + offset.x {
// beyond the end of this row - keep same column // beyond the end of this row - keep same column
cursor.rcursor.column cursor.rcursor.column
} else { } else {
self.rows[new_row].char_at(x) row.char_at(x)
}; };
RCursor { RCursor {
row: new_row, row: new_row,
@@ -1153,7 +1162,9 @@ impl Galley {
let new_row = cursor.rcursor.row + 1; let new_row = cursor.rcursor.row + 1;
let cursor_is_beyond_end_of_current_row = cursor.rcursor.column let cursor_is_beyond_end_of_current_row = cursor.rcursor.column
>= self.rows[cursor.rcursor.row].char_count_excluding_newline(); >= self.rows[cursor.rcursor.row]
.0
.char_count_excluding_newline();
let new_rcursor = if cursor_is_beyond_end_of_current_row { let new_rcursor = if cursor_is_beyond_end_of_current_row {
// keep same column // keep same column
@@ -1164,11 +1175,12 @@ impl Galley {
} else { } else {
// keep same X coord // keep same X coord
let x = self.pos_from_cursor(cursor).center().x; let x = self.pos_from_cursor(cursor).center().x;
let column = if x > self.rows[new_row].rect.right() { let (row, offset) = &self.rows[new_row];
let column = if x > row.rect.right() + offset.x {
// beyond the end of the next row - keep same column // beyond the end of the next row - keep same column
cursor.rcursor.column cursor.rcursor.column
} else { } else {
self.rows[new_row].char_at(x) row.char_at(x)
}; };
RCursor { RCursor {
row: new_row, row: new_row,
@@ -1192,7 +1204,9 @@ impl Galley {
pub fn cursor_end_of_row(&self, cursor: &Cursor) -> Cursor { pub fn cursor_end_of_row(&self, cursor: &Cursor) -> Cursor {
self.from_rcursor(RCursor { self.from_rcursor(RCursor {
row: cursor.rcursor.row, row: cursor.rcursor.row,
column: self.rows[cursor.rcursor.row].char_count_excluding_newline(), column: self.rows[cursor.rcursor.row]
.0
.char_count_excluding_newline(),
}) })
} }
} }