From db32a1ed4474d4bf36d06878cca771ab08b385e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hubert=20G=C5=82uchowski?= Date: Thu, 28 Nov 2024 17:40:59 +0100 Subject: [PATCH] Make Galleys share Rows and store their offsets --- .../egui/src/text_selection/accesskit_text.rs | 4 +- .../egui/src/text_selection/cursor_range.rs | 2 +- .../text_selection/label_text_selection.rs | 7 +- crates/egui/src/text_selection/visuals.rs | 4 +- crates/egui/src/widget_text.rs | 2 +- crates/egui/src/widgets/label.rs | 13 +- crates/epaint/src/shape.rs | 4 +- crates/epaint/src/shape_transform.rs | 3 +- crates/epaint/src/stats.rs | 10 +- crates/epaint/src/tessellator.rs | 8 +- crates/epaint/src/text/fonts.rs | 36 +---- crates/epaint/src/text/text_layout.rs | 142 +++++++++++------- crates/epaint/src/text/text_layout_types.rs | 66 ++++---- 13 files changed, 174 insertions(+), 127 deletions(-) diff --git a/crates/egui/src/text_selection/accesskit_text.rs b/crates/egui/src/text_selection/accesskit_text.rs index d0c3869038d..ad11c16a4d9 100644 --- a/crates/egui/src/text_selection/accesskit_text.rs +++ b/crates/egui/src/text_selection/accesskit_text.rs @@ -39,11 +39,11 @@ pub fn update_accesskit_for_text_widget( }; 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); ctx.accesskit_node_builder(row_id, |builder| { 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 { x0: rect.min.x.into(), y0: rect.min.y.into(), diff --git a/crates/egui/src/text_selection/cursor_range.rs b/crates/egui/src/text_selection/cursor_range.rs index bd3f496fd8e..dbdba5ba8eb 100644 --- a/crates/egui/src/text_selection/cursor_range.rs +++ b/crates/egui/src/text_selection/cursor_range.rs @@ -284,7 +284,7 @@ fn ccursor_from_accesskit_text_position( position: &accesskit::TextPosition, ) -> Option { 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); if row_id.accesskit_id() == position.node { return Some(CCursor { diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index fe5eac00e78..321d8d94609 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -179,7 +179,10 @@ impl LabelSelectionState { if let epaint::Shape::Text(text_shape) = &mut shape.shape { let galley = Arc::make_mut(&mut text_shape.galley); 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 { if let Some(vertex) = row .visuals @@ -659,7 +662,7 @@ fn selected_text(galley: &Galley, cursor_range: &CursorRange) -> String { } fn estimate_row_height(galley: &Galley) -> f32 { - if let Some(row) = galley.rows.first() { + if let Some((row, _)) = galley.rows.first() { row.rect.height() } else { galley.size().y diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index d86f9dc56c2..0000632e77c 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -31,7 +31,9 @@ pub fn paint_text_selection( let max = max.rcursor; 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 { row.x_offset(min.column) } else { diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 011a4adcbb0..bde229828be 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -640,7 +640,7 @@ impl WidgetText { Self::RichText(text) => text.font_height(fonts, style), Self::LayoutJob(job) => job.font_height(fonts), Self::Galley(galley) => { - if let Some(row) = galley.rows.first() { + if let Some((row, _)) = galley.rows.first() { row.height() } else { galley.size().y diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index b6ade45ae30..276c9576b61 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use crate::{ - epaint, pos2, text_selection, vec2, Align, Direction, FontSelection, Galley, Pos2, Response, - Sense, Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType, + epaint, pos2, text_selection, Align, Direction, FontSelection, Galley, Pos2, Response, Sense, + Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType, }; use self::text_selection::LabelSelectionState; @@ -194,10 +194,13 @@ impl Label { let pos = pos2(ui.max_rect().left(), ui.cursor().top()); assert!(!galley.rows.is_empty(), "Galleys are never empty"); // 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); - for row in galley.rows.iter().skip(1) { - let rect = row.rect.translate(vec2(pos.x, pos.y)); + for (row, offset) in galley.rows.iter().skip(1) { + let rect = row.rect.translate(offset.to_vec2() + pos.to_vec2()); response |= ui.allocate_rect(rect, sense); } (pos, galley, response) diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 6f67a2bc6a6..0d2d77d47ad 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -433,7 +433,9 @@ impl Shape { // Scale text: 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; for v in &mut row.visuals.mesh.vertices { v.pos = Pos2::new(transform.scaling * v.pos.x, transform.scaling * v.pos.y); diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index f072393f557..4d74c938513 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -88,7 +88,8 @@ pub fn adjust_colors( if !galley.is_empty() { 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 { adjust_color(&mut vertex.color); } diff --git a/crates/epaint/src/stats.rs b/crates/epaint/src/stats.rs index 68bba622ed2..ad3705bea4b 100644 --- a/crates/epaint/src/stats.rs +++ b/crates/epaint/src/stats.rs @@ -88,7 +88,11 @@ impl AllocInfo { pub fn from_galley(galley: &Galley) -> Self { Self::from_slice(galley.text().as_bytes()) + 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 { @@ -213,8 +217,8 @@ impl PaintStats { self.shape_text += AllocInfo::from_galley(&text_shape.galley); for row in &text_shape.galley.rows { - self.text_shape_indices += AllocInfo::from_slice(&row.visuals.mesh.indices); - self.text_shape_vertices += AllocInfo::from_slice(&row.visuals.mesh.vertices); + self.text_shape_indices += AllocInfo::from_slice(&row.0.visuals.mesh.indices); + self.text_shape_vertices += AllocInfo::from_slice(&row.0.visuals.mesh.vertices); } } Shape::Mesh(mesh) => { diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index fdbe270914e..393edfbbf67 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1778,16 +1778,18 @@ impl Tessellator { let rotator = Rot2::from_angle(*angle); - for row in &galley.rows { + for (row, row_pos) in &galley.rows { if row.visuals.mesh.is_empty() { continue; } + let final_pos = galley_pos + row_pos.to_vec2(); + let mut row_rect = row.visuals.mesh_bounds; if *angle != 0.0 { 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) { // culling individual lines of text is important, since a single `Shape::Text` @@ -1836,7 +1838,7 @@ impl Tessellator { }; Vertex { - pos: galley_pos + offset, + pos: final_pos + offset, uv: (uv.to_vec2() * uv_normalizer).to_pos2(), color, } diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index c981279dd93..eccd2fd06b8 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -832,34 +832,14 @@ impl GalleyCache { for galley in galleys { let current_offset = emath::vec2(0.0, merged_galley.rect.height()); - merged_galley.rows.extend(galley.rows.iter().map(|row| { - super::Row { - // FIXME: what is this??? - section_index_at_start: row.section_index_at_start, - glyphs: row - .glyphs - .iter() - .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 + .rows + .extend(galley.rows.iter().map(|(row, prev_offset)| { + merged_galley.mesh_bounds = + merged_galley.mesh_bounds.union(row.visuals.mesh_bounds); + + (row.clone(), *prev_offset + current_offset) + })); merged_galley.rect = merged_galley .rect .union(galley.rect.translate(current_offset)); diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 9db77f888af..ed7347418e0 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -96,7 +96,8 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { let mut elided = false; let mut rows = rows_from_paragraphs(paragraphs, &job, &mut 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); if let Some(last) = last_row.glyphs.last() { last_row.rect.max.x = last.max_x(); @@ -108,12 +109,12 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { if justify || job.halign != Align::LEFT { 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 justify_row = justify && !row.ends_with_newline && !is_last_row; halign_and_justify_row( point_scale, - row, + Arc::get_mut(row).unwrap(), job.halign, job.wrap.max_width, justify_row, @@ -198,7 +199,7 @@ fn rows_from_paragraphs( paragraphs: Vec, job: &LayoutJob, elided: &mut bool, -) -> Vec { +) -> Vec<(Arc, Pos2)> { let num_paragraphs = paragraphs.len(); let mut rows = vec![]; @@ -212,31 +213,38 @@ fn rows_from_paragraphs( let is_last_paragraph = (i + 1) == num_paragraphs; if paragraph.glyphs.is_empty() { - rows.push(Row { - section_index_at_start: paragraph.section_index_at_start, - glyphs: vec![], - visuals: Default::default(), - rect: Rect::from_min_size( - pos2(paragraph.cursor_x, 0.0), - vec2(0.0, paragraph.empty_paragraph_height), - ), - ends_with_newline: !is_last_paragraph, - }); + rows.push(( + Arc::new(Row { + section_index_at_start: paragraph.section_index_at_start, + glyphs: vec![], + visuals: Default::default(), + rect: Rect::from_min_size( + pos2(paragraph.cursor_x, 0.0), + vec2(0.0, paragraph.empty_paragraph_height), + ), + ends_with_newline: !is_last_paragraph, + }), + Pos2::ZERO, + )); } else { let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x(); if paragraph_max_x <= job.effective_wrap_width() { // Early-out optimization: the whole paragraph fits on one row. let paragraph_min_x = paragraph.glyphs[0].pos.x; - rows.push(Row { - section_index_at_start: paragraph.section_index_at_start, - glyphs: paragraph.glyphs, - visuals: Default::default(), - rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), - ends_with_newline: !is_last_paragraph, - }); + rows.push(( + Arc::new(Row { + section_index_at_start: paragraph.section_index_at_start, + glyphs: paragraph.glyphs, + visuals: Default::default(), + rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), + ends_with_newline: !is_last_paragraph, + }), + Pos2::ZERO, + )); } else { line_break(¶graph, 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 } -fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, elided: &mut bool) { +fn line_break( + paragraph: &Paragraph, + job: &LayoutJob, + out_rows: &mut Vec<(Arc, Pos2)>, + elided: &mut bool, +) { let wrap_width = job.effective_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, e { // 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. - out_rows.push(Row { - section_index_at_start: paragraph.section_index_at_start, - glyphs: vec![], - visuals: Default::default(), - rect: rect_from_x_range(first_row_indentation..=first_row_indentation), - ends_with_newline: false, - }); + out_rows.push(( + Arc::new(Row { + section_index_at_start: paragraph.section_index_at_start, + glyphs: vec![], + visuals: Default::default(), + rect: rect_from_x_range(first_row_indentation..=first_row_indentation), + ends_with_newline: false, + }), + Pos2::ZERO, + )); row_start_x += first_row_indentation; first_row_indentation = 0.0; } 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, e let paragraph_min_x = glyphs[0].pos.x; let paragraph_max_x = glyphs.last().unwrap().max_x(); - out_rows.push(Row { - section_index_at_start, - glyphs, - visuals: Default::default(), - rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), - ends_with_newline: false, - }); + out_rows.push(( + Arc::new(Row { + section_index_at_start, + glyphs, + visuals: Default::default(), + rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), + ends_with_newline: false, + }), + Pos2::ZERO, + )); // Start a new row: row_start_idx = last_kept_index + 1; @@ -333,13 +352,16 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, e let paragraph_min_x = glyphs[0].pos.x; let paragraph_max_x = glyphs.last().unwrap().max_x(); - out_rows.push(Row { - section_index_at_start, - glyphs, - visuals: Default::default(), - rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), - ends_with_newline: false, - }); + out_rows.push(( + Arc::new(Row { + section_index_at_start, + glyphs, + visuals: Default::default(), + 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( point_scale: PointScale, job: Arc, - mut rows: Vec, + mut rows: Vec<(Arc, Pos2)>, elided: bool, ) -> Galley { let mut first_row_min_height = job.first_row_min_height; let mut cursor_y = 0.0; let mut min_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()); first_row_min_height = 0.0; for glyph in &row.glyphs { @@ -639,7 +662,8 @@ fn galley_from_rows( let mut num_vertices = 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); mesh_bounds = mesh_bounds.union(row.visuals.mesh_bounds); num_vertices += row.visuals.mesh.vertices.len(); @@ -1072,7 +1096,7 @@ mod tests { assert!(galley.elided); assert_eq!(galley.rows.len(), 1); - let row_text = galley.rows[0].text(); + let row_text = galley.rows[0].0.text(); assert!( 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}.", @@ -1091,7 +1115,7 @@ mod tests { assert!(galley.elided); 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…"); } } @@ -1106,7 +1130,11 @@ mod tests { layout_job.wrap.max_width = 90.0; let galley = layout(&mut fonts, layout_job.into()); assert_eq!( - galley.rows.iter().map(|row| row.text()).collect::>(), + galley + .rows + .iter() + .map(|row| row.0.text()) + .collect::>(), vec!["日本語と", "Englishの混在", "した文章"] ); } @@ -1121,7 +1149,11 @@ mod tests { layout_job.wrap.max_width = 110.0; let galley = layout(&mut fonts, layout_job.into()); assert_eq!( - galley.rows.iter().map(|row| row.text()).collect::>(), + galley + .rows + .iter() + .map(|row| row.0.text()) + .collect::>(), vec!["日本語とEnglish", "の混在した文章"] ); } @@ -1136,10 +1168,14 @@ mod tests { let galley = layout(&mut fonts, layout_job.into()); assert!(galley.elided); assert_eq!( - galley.rows.iter().map(|row| row.text()).collect::>(), + galley + .rows + .iter() + .map(|row| row.0.text()) + .collect::>(), vec!["# DNA…"] ); 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()); } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 17826e6afb1..31a44c5dd3a 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -499,14 +499,14 @@ pub struct Galley { /// Contains the original string and style sections. pub job: Arc, - /// 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()` /// unless [`Self::elided`] is `true`. /// /// Note that a paragraph (a piece of text separated with `\n`) /// can be split up into multiple rows. - pub rows: Vec, + pub rows: Vec<(Arc, Pos2)>, /// Set to true the text was truncated due to [`TextWrapping::max_rows`]. pub elided: bool, @@ -755,7 +755,7 @@ impl std::ops::Deref for Galley { impl Galley { /// Zero-width rect past the last character. fn end_pos(&self) -> Rect { - if let Some(row) = self.rows.last() { + if let Some((row, _)) = self.rows.last() { let x = row.rect.right(); Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y())) } else { @@ -773,7 +773,7 @@ impl Galley { pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Rect { let mut it = PCursor::default(); - for row in &self.rows { + for (row, offset) in &self.rows { if it.paragraph == pcursor.paragraph { // Right paragraph, but is it the right row in the paragraph? @@ -787,8 +787,11 @@ impl Galley { && !row.ends_with_newline && column >= row.char_count_excluding_newline(); if !select_next_row_instead { - let x = row.x_offset(column); - return Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y())); + let x = row.x_offset(column) + offset.x; + 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. /// This allows implementing text-selection by dragging above/below the galley. pub fn cursor_from_pos(&self, pos: Vec2) -> Cursor { - if let Some(first_row) = self.rows.first() { - if pos.y < first_row.min_y() { + if let Some((first_row, offset)) = self.rows.first() { + if pos.y < first_row.min_y() + offset.y { return self.begin(); } } - if let Some(last_row) = self.rows.last() { - if last_row.max_y() < pos.y { + if let Some((last_row, offset)) = self.rows.last() { + if last_row.max_y() + offset.y < pos.y { return self.end(); } } @@ -839,9 +842,12 @@ impl Galley { let mut ccursor_index = 0; let mut pcursor_it = PCursor::default(); - for (row_nr, row) in self.rows.iter().enumerate() { - let is_pos_within_row = row.min_y() <= pos.y && pos.y <= row.max_y(); - let y_dist = (row.min_y() - pos.y).abs().min((row.max_y() - pos.y).abs()); + for (row_nr, (row, offset)) in self.rows.iter().enumerate() { + let min_y = row.min_y() + offset.y; + 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 { best_y_dist = y_dist; let column = row.char_at(pos.x); @@ -904,7 +910,7 @@ impl Galley { offset: 0, prefer_next_row: true, }; - for row in &self.rows { + for (row, _) in &self.rows { let row_char_count = row.char_count_including_newline(); ccursor.index += row_char_count; if row.ends_with_newline { @@ -922,7 +928,7 @@ impl Galley { } pub fn end_rcursor(&self) -> RCursor { - if let Some(last_row) = self.rows.last() { + if let Some((last_row, _)) = self.rows.last() { RCursor { row: self.rows.len() - 1, column: last_row.char_count_including_newline(), @@ -948,7 +954,7 @@ impl Galley { 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(); if ccursor_it.index <= ccursor.index @@ -993,7 +999,7 @@ impl Galley { } 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 { index: 0, prefer_next_row, @@ -1004,7 +1010,7 @@ impl Galley { 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 { ccursor_it.index += rcursor.column.at_most(row.char_count_excluding_newline()); @@ -1048,7 +1054,7 @@ impl Galley { 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 { // 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 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 { // keep same column @@ -1133,11 +1141,12 @@ impl Galley { } else { // keep same X coord 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 cursor.rcursor.column } else { - self.rows[new_row].char_at(x) + row.char_at(x) }; RCursor { row: new_row, @@ -1153,7 +1162,9 @@ impl Galley { let new_row = cursor.rcursor.row + 1; 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 { // keep same column @@ -1164,11 +1175,12 @@ impl Galley { } else { // keep same X coord 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 cursor.rcursor.column } else { - self.rows[new_row].char_at(x) + row.char_at(x) }; RCursor { row: new_row, @@ -1192,7 +1204,9 @@ impl Galley { pub fn cursor_end_of_row(&self, cursor: &Cursor) -> Cursor { self.from_rcursor(RCursor { 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(), }) } }