Rename "row" to "line"

We now have "paragraphs" separated by \n,
which are word-wrapped onto one or more rows.
This commit is contained in:
Emil Ernerfeldt 2020-11-13 01:23:36 +01:00
parent 9ab00b8e50
commit c84431e473
7 changed files with 299 additions and 319 deletions

View file

@ -9,7 +9,7 @@ use {
use crate::{ use crate::{
math::{vec2, Vec2}, math::{vec2, Vec2},
mutex::Mutex, mutex::Mutex,
paint::{Galley, Line}, paint::{Galley, Row},
}; };
use super::texture_atlas::TextureAtlas; use super::texture_atlas::TextureAtlas;
@ -110,12 +110,8 @@ impl Font {
(point * self.pixels_per_point).round() / self.pixels_per_point (point * self.pixels_per_point).round() / self.pixels_per_point
} }
/// Height of one line of text. In points /// Height of one row of text. In points
/// TODO: rename height ? pub fn row_height(&self) -> f32 {
pub fn line_spacing(&self) -> f32 {
self.scale_in_pixels / self.pixels_per_point
}
pub fn height(&self) -> f32 {
self.scale_in_pixels / self.pixels_per_point self.scale_in_pixels / self.pixels_per_point
} }
@ -145,22 +141,22 @@ impl Font {
glyph_info glyph_info
} }
/// Typeset the given text onto one line. /// Typeset the given text onto one row.
/// Any `\n` will show up as `REPLACEMENT_CHAR` ('?'). /// Any `\n` will show up as `REPLACEMENT_CHAR` ('?').
/// Always returns exactly one `Line` in the `Galley`. /// Always returns exactly one `Row` in the `Galley`.
pub fn layout_single_line(&self, text: String) -> Galley { pub fn layout_single_line(&self, text: String) -> Galley {
let x_offsets = self.layout_single_line_fragment(&text); let x_offsets = self.layout_single_row_fragment(&text);
let line = Line { let row = Row {
x_offsets, x_offsets,
y_min: 0.0, y_min: 0.0,
y_max: self.height(), y_max: self.row_height(),
ends_with_newline: false, ends_with_newline: false,
}; };
let width = line.max_x(); let width = row.max_x();
let size = vec2(width, self.height()); let size = vec2(width, self.row_height());
let galley = Galley { let galley = Galley {
text, text,
lines: vec![line], rows: vec![row],
size, size,
}; };
galley.sanity_check(); galley.sanity_check();
@ -168,9 +164,9 @@ impl Font {
} }
pub fn layout_multiline(&self, text: String, max_width_in_points: f32) -> Galley { pub fn layout_multiline(&self, text: String, max_width_in_points: f32) -> Galley {
let line_spacing = self.line_spacing(); let line_spacing = self.row_height();
let mut cursor_y = 0.0; let mut cursor_y = 0.0;
let mut lines = Vec::new(); let mut rows = Vec::new();
let mut paragraph_start = 0; let mut paragraph_start = 0;
@ -182,25 +178,25 @@ impl Font {
assert!(paragraph_start <= paragraph_end); assert!(paragraph_start <= paragraph_end);
let paragraph_text = &text[paragraph_start..paragraph_end]; let paragraph_text = &text[paragraph_start..paragraph_end];
let mut paragraph_lines = let mut paragraph_rows =
self.layout_paragraph_max_width(paragraph_text, max_width_in_points); self.layout_paragraph_max_width(paragraph_text, max_width_in_points);
assert!(!paragraph_lines.is_empty()); assert!(!paragraph_rows.is_empty());
paragraph_lines.last_mut().unwrap().ends_with_newline = next_newline.is_some(); paragraph_rows.last_mut().unwrap().ends_with_newline = next_newline.is_some();
for line in &mut paragraph_lines { for row in &mut paragraph_rows {
line.y_min += cursor_y; row.y_min += cursor_y;
line.y_max += cursor_y; row.y_max += cursor_y;
} }
cursor_y = paragraph_lines.last().unwrap().y_max; cursor_y = paragraph_rows.last().unwrap().y_max;
cursor_y += line_spacing * 0.4; // Extra spacing between paragraphs. TODO: less hacky cursor_y += line_spacing * 0.4; // Extra spacing between paragraphs. TODO: less hacky
lines.append(&mut paragraph_lines); rows.append(&mut paragraph_rows);
paragraph_start = paragraph_end + 1; paragraph_start = paragraph_end + 1;
} }
if text.is_empty() || text.ends_with('\n') { if text.is_empty() || text.ends_with('\n') {
lines.push(Line { rows.push(Row {
x_offsets: vec![0.0], x_offsets: vec![0.0],
y_min: cursor_y, y_min: cursor_y,
y_max: cursor_y + line_spacing, y_max: cursor_y + line_spacing,
@ -208,21 +204,21 @@ impl Font {
}); });
} }
let mut widest_line = 0.0; let mut widest_row = 0.0;
for line in &lines { for row in &rows {
widest_line = line.max_x().max(widest_line); widest_row = row.max_x().max(widest_row);
} }
let size = vec2(widest_line, lines.last().unwrap().y_max); let size = vec2(widest_row, rows.last().unwrap().y_max);
let galley = Galley { text, lines, size }; let galley = Galley { text, rows, size };
galley.sanity_check(); galley.sanity_check();
galley galley
} }
/// Typeset the given text onto one line. /// Typeset the given text onto one row.
/// Assumes there are no `\n` in the text. /// Assumes there are no `\n` in the text.
/// Return `x_offsets`, one longer than the number of characters in the text. /// Return `x_offsets`, one longer than the number of characters in the text.
fn layout_single_line_fragment(&self, text: &str) -> Vec<f32> { fn layout_single_row_fragment(&self, text: &str) -> Vec<f32> {
let scale_in_pixels = Scale::uniform(self.scale_in_pixels); let scale_in_pixels = Scale::uniform(self.scale_in_pixels);
let mut x_offsets = Vec::with_capacity(text.chars().count() + 1); let mut x_offsets = Vec::with_capacity(text.chars().count() + 1);
@ -252,68 +248,68 @@ impl Font {
/// A paragraph is text with no line break character in it. /// A paragraph is text with no line break character in it.
/// The text will be wrapped by the given `max_width_in_points`. /// The text will be wrapped by the given `max_width_in_points`.
fn layout_paragraph_max_width(&self, text: &str, max_width_in_points: f32) -> Vec<Line> { fn layout_paragraph_max_width(&self, text: &str, max_width_in_points: f32) -> Vec<Row> {
if text == "" { if text == "" {
return vec![Line { return vec![Row {
x_offsets: vec![0.0], x_offsets: vec![0.0],
y_min: 0.0, y_min: 0.0,
y_max: self.height(), y_max: self.row_height(),
ends_with_newline: false, ends_with_newline: false,
}]; }];
} }
let full_x_offsets = self.layout_single_line_fragment(text); let full_x_offsets = self.layout_single_row_fragment(text);
let mut line_start_x = full_x_offsets[0]; let mut row_start_x = full_x_offsets[0];
{ {
#![allow(clippy::float_cmp)] #![allow(clippy::float_cmp)]
assert_eq!(line_start_x, 0.0); assert_eq!(row_start_x, 0.0);
} }
let mut cursor_y = 0.0; let mut cursor_y = 0.0;
let mut line_start_idx = 0; let mut row_start_idx = 0;
// start index of the last space. A candidate for a new line. // start index of the last space. A candidate for a new row.
let mut last_space = None; let mut last_space = None;
let mut out_lines = vec![]; let mut out_rows = vec![];
for (i, (x, chr)) in full_x_offsets.iter().skip(1).zip(text.chars()).enumerate() { for (i, (x, chr)) in full_x_offsets.iter().skip(1).zip(text.chars()).enumerate() {
debug_assert!(chr != '\n'); debug_assert!(chr != '\n');
let line_width = x - line_start_x; let potential_row_width = x - row_start_x;
if line_width > max_width_in_points { if potential_row_width > max_width_in_points {
if let Some(last_space_idx) = last_space { if let Some(last_space_idx) = last_space {
let include_trailing_space = true; let include_trailing_space = true;
let line = if include_trailing_space { let row = if include_trailing_space {
Line { Row {
x_offsets: full_x_offsets[line_start_idx..=last_space_idx + 1] x_offsets: full_x_offsets[row_start_idx..=last_space_idx + 1]
.iter() .iter()
.map(|x| x - line_start_x) .map(|x| x - row_start_x)
.collect(), .collect(),
y_min: cursor_y, y_min: cursor_y,
y_max: cursor_y + self.height(), y_max: cursor_y + self.row_height(),
ends_with_newline: false, ends_with_newline: false,
} }
} else { } else {
Line { Row {
x_offsets: full_x_offsets[line_start_idx..=last_space_idx] x_offsets: full_x_offsets[row_start_idx..=last_space_idx]
.iter() .iter()
.map(|x| x - line_start_x) .map(|x| x - row_start_x)
.collect(), .collect(),
y_min: cursor_y, y_min: cursor_y,
y_max: cursor_y + self.height(), y_max: cursor_y + self.row_height(),
ends_with_newline: false, ends_with_newline: false,
} }
}; };
line.sanity_check(); row.sanity_check();
out_lines.push(line); out_rows.push(row);
line_start_idx = last_space_idx + 1; row_start_idx = last_space_idx + 1;
line_start_x = full_x_offsets[line_start_idx]; row_start_x = full_x_offsets[row_start_idx];
last_space = None; last_space = None;
cursor_y += self.line_spacing(); cursor_y += self.row_height();
cursor_y = self.round_to_pixel(cursor_y); cursor_y = self.round_to_pixel(cursor_y);
} }
} }
@ -324,21 +320,21 @@ impl Font {
} }
} }
if line_start_idx + 1 < full_x_offsets.len() { if row_start_idx + 1 < full_x_offsets.len() {
let line = Line { let row = Row {
x_offsets: full_x_offsets[line_start_idx..] x_offsets: full_x_offsets[row_start_idx..]
.iter() .iter()
.map(|x| x - line_start_x) .map(|x| x - row_start_x)
.collect(), .collect(),
y_min: cursor_y, y_min: cursor_y,
y_max: cursor_y + self.height(), y_max: cursor_y + self.row_height(),
ends_with_newline: false, ends_with_newline: false,
}; };
line.sanity_check(); row.sanity_check();
out_lines.push(line); out_rows.push(row);
} }
out_lines out_rows
} }
} }

View file

@ -1,3 +1,21 @@
//! This is going to get complicated.
//!
//! To avoid confusion, we never use the word "line".
//! The `\n` character demarcates the split of text into "paragraphs".
//! Each paragraph is wrapped at some width onto one or more "rows".
//!
//! If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
//! do we prefer the next row?
//! For instance, consider this single paragraph, word wrapped:
//! ``` text
//! Hello_
//! world!
//! ```
//!
//! The offset `6` is both the end of the first row
//! and the start of the second row.
//! The `prefer_next_row` selects which.
use crate::math::{vec2, NumExt, Vec2}; use crate::math::{vec2, NumExt, Vec2};
/// Character cursor /// Character cursor
@ -7,31 +25,22 @@ pub struct CCursor {
/// Character offset (NOT byte offset!). /// Character offset (NOT byte offset!).
pub index: usize, pub index: usize,
/// If this cursors sits right at the border of a wrapped line (NOT `\n`), /// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
/// do we prefer the next line? /// do we prefer the next row?
/// For instance, consider this text, word wrapped: pub prefer_next_row: bool,
/// ``` text
/// Hello_
/// world!
/// ```
///
/// The offset `6` is both the end of the first line
/// and the start of the second line.
/// The `prefer_next_line` selects which.
pub prefer_next_line: bool,
} }
impl CCursor { impl CCursor {
pub fn new(index: usize) -> Self { pub fn new(index: usize) -> Self {
Self { Self {
index, index,
prefer_next_line: false, prefer_next_row: false,
} }
} }
} }
/// Two `CCursor`s are considered equal if they refer to the same character boundary, /// Two `CCursor`s are considered equal if they refer to the same character boundary,
/// even if one prefers the start of the next line. /// even if one prefers the start of the next row.
impl PartialEq for CCursor { impl PartialEq for CCursor {
fn eq(&self, other: &CCursor) -> bool { fn eq(&self, other: &CCursor) -> bool {
self.index == other.index self.index == other.index
@ -43,7 +52,7 @@ impl std::ops::Add<usize> for CCursor {
fn add(self, rhs: usize) -> Self::Output { fn add(self, rhs: usize) -> Self::Output {
CCursor { CCursor {
index: self.index.saturating_add(rhs), index: self.index.saturating_add(rhs),
prefer_next_line: self.prefer_next_line, prefer_next_row: self.prefer_next_row,
} }
} }
} }
@ -53,23 +62,23 @@ impl std::ops::Sub<usize> for CCursor {
fn sub(self, rhs: usize) -> Self::Output { fn sub(self, rhs: usize) -> Self::Output {
CCursor { CCursor {
index: self.index.saturating_sub(rhs), index: self.index.saturating_sub(rhs),
prefer_next_line: self.prefer_next_line, prefer_next_row: self.prefer_next_row,
} }
} }
} }
/// Line Cursor /// Row Cursor
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct LCursor { pub struct RCursor {
/// 0 is first line, and so on. /// 0 is first row, and so on.
/// Note that a single paragraph can span multiple lines. /// Note that a single paragraph can span multiple rows.
/// (a paragraph is text separated by `\n`). /// (a paragraph is text separated by `\n`).
pub line: usize, pub row: usize,
/// Character based (NOT bytes). /// Character based (NOT bytes).
/// It is fine if this points to something beyond the end of the current line. /// It is fine if this points to something beyond the end of the current row.
/// When moving up/down it may again be within the next line. /// When moving up/down it may again be within the next row.
pub column: usize, pub column: usize,
} }
@ -78,31 +87,22 @@ pub struct LCursor {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct PCursor { pub struct PCursor {
/// 0 is first paragraph, and so on. /// 0 is first paragraph, and so on.
/// Note that a single paragraph can span multiple lines. /// Note that a single paragraph can span multiple rows.
/// (a paragraph is text separated by `\n`). /// (a paragraph is text separated by `\n`).
pub paragraph: usize, pub paragraph: usize,
/// Character based (NOT bytes). /// Character based (NOT bytes).
/// It is fine if this points to something beyond the end of the current line. /// It is fine if this points to something beyond the end of the current paragraph.
/// When moving up/down it may again be within the next line. /// When moving up/down it may again be within the next paragraph.
pub offset: usize, pub offset: usize,
/// If this cursors sits right at the border of a wrapped line (NOT `\n`), /// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
/// do we prefer the next line? /// do we prefer the next row?
/// For instance, consider this text, word wrapped: pub prefer_next_row: bool,
/// ``` text
/// Hello_
/// world!
/// ```
///
/// The offset `6` is both the end of the first line
/// and the start of the second line.
/// The `prefer_next_line` selects which.
pub prefer_next_line: bool,
} }
/// Two `PCursor`s are considered equal if they refer to the same character boundary, /// Two `PCursor`s are considered equal if they refer to the same character boundary,
/// even if one prefers the start of the next line. /// even if one prefers the start of the next row.
impl PartialEq for PCursor { impl PartialEq for PCursor {
fn eq(&self, other: &PCursor) -> bool { fn eq(&self, other: &PCursor) -> bool {
self.paragraph == other.paragraph && self.offset == other.offset self.paragraph == other.paragraph && self.offset == other.offset
@ -115,7 +115,7 @@ impl PartialEq for PCursor {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Cursor { pub struct Cursor {
pub ccursor: CCursor, pub ccursor: CCursor,
pub lcursor: LCursor, pub rcursor: RCursor,
pub pcursor: PCursor, pub pcursor: PCursor,
} }
@ -125,20 +125,19 @@ pub struct Galley {
/// The full text, including any an all `\n`. /// The full text, including any an all `\n`.
pub text: String, pub text: String,
/// Lines of text, from top to bottom. /// Rows of text, from top to bottom.
/// The number of chars in all lines sum up to text.chars().count(). /// The number of chars in all rows sum up to text.chars().count().
/// Note that each paragraph (pieces of text separated with `\n`) /// Note that each paragraph (pieces of text separated with `\n`)
/// can be split up into multiple lines. /// can be split up into multiple rows.
pub lines: Vec<Line>, pub rows: Vec<Row>,
// Optimization: calculated once and reused. // Optimization: calculated once and reused.
pub size: Vec2, pub size: Vec2,
} }
// TODO: should this maybe be renamed `Row` to avoid confusion with lines as 'that which is broken by \n'. /// A typeset piece of text on a single row.
/// A typeset piece of text on a single line.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Line { pub struct Row {
/// The start of each character, probably starting at zero. /// The start of each character, probably starting at zero.
/// The last element is the end of the last character. /// The last element is the end of the last character.
/// This is never empty. /// This is never empty.
@ -147,34 +146,34 @@ pub struct Line {
/// `x_offsets.len() + (ends_with_newline as usize) == text.chars().count() + 1` /// `x_offsets.len() + (ends_with_newline as usize) == text.chars().count() + 1`
pub x_offsets: Vec<f32>, pub x_offsets: Vec<f32>,
/// Top of the line, offset within the Galley. /// Top of the row, offset within the Galley.
/// Unit: points. /// Unit: points.
pub y_min: f32, pub y_min: f32,
/// Bottom of the line, offset within the Galley. /// Bottom of the row, offset within the Galley.
/// Unit: points. /// Unit: points.
pub y_max: f32, pub y_max: f32,
/// If true, this Line came from a paragraph ending with a `\n`. /// If true, this `Row` came from a paragraph ending with a `\n`.
/// The `\n` itself is omitted from `x_offsets`. /// The `\n` itself is omitted from `x_offsets`.
/// A `\n` in the input text always creates a new `Line` below it, /// A `\n` in the input text always creates a new `Row` below it,
/// so that text that ends with `\n` has an empty `Line` last. /// so that text that ends with `\n` has an empty `Row` last.
/// This also implies that the last `Line` in a `Galley` always has `ends_with_newline == false`. /// This also implies that the last `Row` in a `Galley` always has `ends_with_newline == false`.
pub ends_with_newline: bool, pub ends_with_newline: bool,
} }
impl Line { impl Row {
pub fn sanity_check(&self) { pub fn sanity_check(&self) {
assert!(!self.x_offsets.is_empty()); assert!(!self.x_offsets.is_empty());
} }
/// Excludes the implicit `\n` after the `Line`, if any. /// Excludes the implicit `\n` after the `Row`, if any.
pub fn char_count_excluding_newline(&self) -> usize { pub fn char_count_excluding_newline(&self) -> usize {
assert!(!self.x_offsets.is_empty()); assert!(!self.x_offsets.is_empty());
self.x_offsets.len() - 1 self.x_offsets.len() - 1
} }
/// Includes the implicit `\n` after the `Line`, if any. /// Includes the implicit `\n` after the `Row`, if any.
pub fn char_count_including_newline(&self) -> usize { pub fn char_count_including_newline(&self) -> usize {
self.char_count_excluding_newline() + (self.ends_with_newline as usize) self.char_count_excluding_newline() + (self.ends_with_newline as usize)
} }
@ -203,15 +202,15 @@ impl Line {
impl Galley { impl Galley {
pub fn sanity_check(&self) { pub fn sanity_check(&self) {
let mut char_count = 0; let mut char_count = 0;
for line in &self.lines { for row in &self.rows {
line.sanity_check(); row.sanity_check();
char_count += line.char_count_including_newline(); char_count += row.char_count_including_newline();
} }
assert_eq!(char_count, self.text.chars().count()); assert_eq!(char_count, self.text.chars().count());
if let Some(last_line) = self.lines.last() { if let Some(last_row) = self.rows.last() {
debug_assert!( debug_assert!(
!last_line.ends_with_newline, !last_row.ends_with_newline,
"If the text ends with '\\n', there would be an empty Line last.\n\ "If the text ends with '\\n', there would be an empty row last.\n\
Galley: {:#?}", Galley: {:#?}",
self self
); );
@ -222,7 +221,7 @@ impl Galley {
/// ## Physical positions /// ## Physical positions
impl Galley { impl Galley {
pub fn last_pos(&self) -> Vec2 { pub fn last_pos(&self) -> Vec2 {
if let Some(last) = self.lines.last() { if let Some(last) = self.rows.last() {
vec2(last.max_x(), last.y_min) vec2(last.max_x(), last.y_min)
} else { } else {
vec2(0.0, 0.0) // Empty galley vec2(0.0, 0.0) // Empty galley
@ -232,30 +231,30 @@ impl Galley {
pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Vec2 { pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Vec2 {
let mut it = PCursor::default(); let mut it = PCursor::default();
for line in &self.lines { for row in &self.rows {
if it.paragraph == pcursor.paragraph { if it.paragraph == pcursor.paragraph {
// Right paragraph, but is it the right line in the paragraph? // Right paragraph, but is it the right row in the paragraph?
if it.offset <= pcursor.offset if it.offset <= pcursor.offset
&& pcursor.offset <= it.offset + line.char_count_excluding_newline() && pcursor.offset <= it.offset + row.char_count_excluding_newline()
{ {
let column = pcursor.offset - it.offset; let column = pcursor.offset - it.offset;
let column = column.at_most(line.char_count_excluding_newline()); let column = column.at_most(row.char_count_excluding_newline());
let select_next_line_instead = pcursor.prefer_next_line let select_next_line_instead = pcursor.prefer_next_row
&& !line.ends_with_newline && !row.ends_with_newline
&& column == line.char_count_excluding_newline(); && column == row.char_count_excluding_newline();
if !select_next_line_instead { if !select_next_line_instead {
return vec2(line.x_offsets[column], line.y_min); return vec2(row.x_offsets[column], row.y_min);
} }
} }
} }
if line.ends_with_newline { if row.ends_with_newline {
it.paragraph += 1; it.paragraph += 1;
it.offset = 0; it.offset = 0;
} else { } else {
it.offset += line.char_count_including_newline(); it.offset += row.char_count_including_newline();
} }
} }
@ -263,7 +262,7 @@ impl Galley {
} }
pub fn pos_from_cursor(&self, cursor: &Cursor) -> Vec2 { pub fn pos_from_cursor(&self, cursor: &Cursor) -> Vec2 {
// self.pos_from_lcursor(cursor.lcursor) // self.pos_from_rcursor(cursor.rcursor)
self.pos_from_pcursor(cursor.pcursor) // The one TextEdit stores self.pos_from_pcursor(cursor.pcursor) // The one TextEdit stores
} }
@ -275,33 +274,33 @@ 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 (line_nr, line) in self.lines.iter().enumerate() { for (line_nr, row) in self.rows.iter().enumerate() {
let y_dist = (line.y_min - pos.y).abs().min((line.y_max - pos.y).abs()); let y_dist = (row.y_min - pos.y).abs().min((row.y_max - pos.y).abs());
if y_dist < best_y_dist { if y_dist < best_y_dist {
best_y_dist = y_dist; best_y_dist = y_dist;
let column = line.char_at(pos.x); let column = row.char_at(pos.x);
cursor = Cursor { cursor = Cursor {
ccursor: CCursor { ccursor: CCursor {
index: ccursor_index + column, index: ccursor_index + column,
prefer_next_line: column == 0, prefer_next_row: column == 0,
}, },
lcursor: LCursor { rcursor: RCursor {
line: line_nr, row: line_nr,
column, column,
}, },
pcursor: PCursor { pcursor: PCursor {
paragraph: pcursor_it.paragraph, paragraph: pcursor_it.paragraph,
offset: pcursor_it.offset + column, offset: pcursor_it.offset + column,
prefer_next_line: column == 0, prefer_next_row: column == 0,
}, },
} }
} }
ccursor_index += line.char_count_including_newline(); ccursor_index += row.char_count_including_newline();
if line.ends_with_newline { if row.ends_with_newline {
pcursor_it.paragraph += 1; pcursor_it.paragraph += 1;
pcursor_it.offset = 0; pcursor_it.offset = 0;
} else { } else {
pcursor_it.offset += line.char_count_including_newline(); pcursor_it.offset += row.char_count_including_newline();
} }
} }
cursor cursor
@ -312,22 +311,22 @@ impl Galley {
impl Galley { impl Galley {
/// Cursor to one-past last character. /// Cursor to one-past last character.
pub fn end(&self) -> Cursor { pub fn end(&self) -> Cursor {
if self.lines.is_empty() { if self.rows.is_empty() {
return Default::default(); return Default::default();
} }
let mut ccursor = CCursor { let mut ccursor = CCursor {
index: 0, index: 0,
prefer_next_line: true, prefer_next_row: true,
}; };
let mut pcursor = PCursor { let mut pcursor = PCursor {
paragraph: 0, paragraph: 0,
offset: 0, offset: 0,
prefer_next_line: true, prefer_next_row: true,
}; };
for line in &self.lines { for row in &self.rows {
let line_char_count = line.char_count_including_newline(); let line_char_count = row.char_count_including_newline();
ccursor.index += line_char_count; ccursor.index += line_char_count;
if line.ends_with_newline { if row.ends_with_newline {
pcursor.paragraph += 1; pcursor.paragraph += 1;
pcursor.offset = 0; pcursor.offset = 0;
} else { } else {
@ -336,17 +335,17 @@ impl Galley {
} }
Cursor { Cursor {
ccursor, ccursor,
lcursor: self.end_lcursor(), rcursor: self.end_rcursor(),
pcursor, pcursor,
} }
} }
pub fn end_lcursor(&self) -> LCursor { pub fn end_rcursor(&self) -> RCursor {
if let Some(last_line) = self.lines.last() { if let Some(last_row) = self.rows.last() {
debug_assert!(!last_line.ends_with_newline); debug_assert!(!last_row.ends_with_newline);
LCursor { RCursor {
line: self.lines.len() - 1, row: self.rows.len() - 1,
column: last_line.char_count_excluding_newline(), column: last_row.char_count_excluding_newline(),
} }
} else { } else {
Default::default() Default::default()
@ -358,19 +357,19 @@ impl Galley {
impl Galley { impl Galley {
// TODO: return identical cursor, or clamp? // TODO: return identical cursor, or clamp?
pub fn from_ccursor(&self, ccursor: CCursor) -> Cursor { pub fn from_ccursor(&self, ccursor: CCursor) -> Cursor {
let prefer_next_line = ccursor.prefer_next_line; let prefer_next_line = ccursor.prefer_next_row;
let mut ccursor_it = CCursor { let mut ccursor_it = CCursor {
index: 0, index: 0,
prefer_next_line, prefer_next_row: prefer_next_line,
}; };
let mut pcursor_it = PCursor { let mut pcursor_it = PCursor {
paragraph: 0, paragraph: 0,
offset: 0, offset: 0,
prefer_next_line, prefer_next_row: prefer_next_line,
}; };
for (line_nr, line) in self.lines.iter().enumerate() { for (line_nr, row) in self.rows.iter().enumerate() {
let line_char_count = line.char_count_excluding_newline(); let line_char_count = row.char_count_excluding_newline();
if ccursor_it.index <= ccursor.index if ccursor_it.index <= ccursor.index
&& ccursor.index <= ccursor_it.index + line_char_count && ccursor.index <= ccursor_it.index + line_char_count
@ -378,118 +377,118 @@ impl Galley {
let column = ccursor.index - ccursor_it.index; let column = ccursor.index - ccursor_it.index;
let select_next_line_instead = prefer_next_line let select_next_line_instead = prefer_next_line
&& !line.ends_with_newline && !row.ends_with_newline
&& column == line.char_count_excluding_newline(); && column == row.char_count_excluding_newline();
if !select_next_line_instead { if !select_next_line_instead {
pcursor_it.offset += column; pcursor_it.offset += column;
return Cursor { return Cursor {
ccursor, ccursor,
lcursor: LCursor { rcursor: RCursor {
line: line_nr, row: line_nr,
column, column,
}, },
pcursor: pcursor_it, pcursor: pcursor_it,
}; };
} }
} }
ccursor_it.index += line.char_count_including_newline(); ccursor_it.index += row.char_count_including_newline();
if line.ends_with_newline { if row.ends_with_newline {
pcursor_it.paragraph += 1; pcursor_it.paragraph += 1;
pcursor_it.offset = 0; pcursor_it.offset = 0;
} else { } else {
pcursor_it.offset += line.char_count_including_newline(); pcursor_it.offset += row.char_count_including_newline();
} }
} }
debug_assert_eq!(ccursor_it, self.end().ccursor); debug_assert_eq!(ccursor_it, self.end().ccursor);
Cursor { Cursor {
ccursor: ccursor_it, // clamp ccursor: ccursor_it, // clamp
lcursor: self.end_lcursor(), rcursor: self.end_rcursor(),
pcursor: pcursor_it, pcursor: pcursor_it,
} }
} }
// TODO: return identical cursor, or clamp? // TODO: return identical cursor, or clamp?
pub fn from_lcursor(&self, lcursor: LCursor) -> Cursor { pub fn from_rcursor(&self, rcursor: RCursor) -> Cursor {
if lcursor.line >= self.lines.len() { if rcursor.row >= self.rows.len() {
return self.end(); return self.end();
} }
let prefer_next_line = lcursor.column == 0; let prefer_next_line = rcursor.column == 0;
let mut ccursor_it = CCursor { let mut ccursor_it = CCursor {
index: 0, index: 0,
prefer_next_line, prefer_next_row: prefer_next_line,
}; };
let mut pcursor_it = PCursor { let mut pcursor_it = PCursor {
paragraph: 0, paragraph: 0,
offset: 0, offset: 0,
prefer_next_line, prefer_next_row: prefer_next_line,
}; };
for (line_nr, line) in self.lines.iter().enumerate() { for (line_nr, row) in self.rows.iter().enumerate() {
if line_nr == lcursor.line { if line_nr == rcursor.row {
let column = lcursor.column.at_most(line.char_count_excluding_newline()); let column = rcursor.column.at_most(row.char_count_excluding_newline());
let select_next_line_instead = prefer_next_line let select_next_line_instead = prefer_next_line
&& !line.ends_with_newline && !row.ends_with_newline
&& column == line.char_count_excluding_newline(); && column == row.char_count_excluding_newline();
if !select_next_line_instead { if !select_next_line_instead {
ccursor_it.index += column; ccursor_it.index += column;
pcursor_it.offset += column; pcursor_it.offset += column;
return Cursor { return Cursor {
ccursor: ccursor_it, ccursor: ccursor_it,
lcursor, rcursor,
pcursor: pcursor_it, pcursor: pcursor_it,
}; };
} }
} }
ccursor_it.index += line.char_count_including_newline(); ccursor_it.index += row.char_count_including_newline();
if line.ends_with_newline { if row.ends_with_newline {
pcursor_it.paragraph += 1; pcursor_it.paragraph += 1;
pcursor_it.offset = 0; pcursor_it.offset = 0;
} else { } else {
pcursor_it.offset += line.char_count_including_newline(); pcursor_it.offset += row.char_count_including_newline();
} }
} }
Cursor { Cursor {
ccursor: ccursor_it, ccursor: ccursor_it,
lcursor: self.end_lcursor(), rcursor: self.end_rcursor(),
pcursor: pcursor_it, pcursor: pcursor_it,
} }
} }
// TODO: return identical cursor, or clamp? // TODO: return identical cursor, or clamp?
pub fn from_pcursor(&self, pcursor: PCursor) -> Cursor { pub fn from_pcursor(&self, pcursor: PCursor) -> Cursor {
let prefer_next_line = pcursor.prefer_next_line; let prefer_next_line = pcursor.prefer_next_row;
let mut ccursor_it = CCursor { let mut ccursor_it = CCursor {
index: 0, index: 0,
prefer_next_line, prefer_next_row: prefer_next_line,
}; };
let mut pcursor_it = PCursor { let mut pcursor_it = PCursor {
paragraph: 0, paragraph: 0,
offset: 0, offset: 0,
prefer_next_line, prefer_next_row: prefer_next_line,
}; };
for (line_nr, line) in self.lines.iter().enumerate() { for (line_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 line in the paragraph? // Right paragraph, but is it the right row in the paragraph?
if pcursor_it.offset <= pcursor.offset if pcursor_it.offset <= pcursor.offset
&& pcursor.offset <= pcursor_it.offset + line.char_count_excluding_newline() && pcursor.offset <= pcursor_it.offset + row.char_count_excluding_newline()
{ {
let column = pcursor.offset - pcursor_it.offset; let column = pcursor.offset - pcursor_it.offset;
let column = column.at_most(line.char_count_excluding_newline()); let column = column.at_most(row.char_count_excluding_newline());
let select_next_line_instead = pcursor.prefer_next_line let select_next_line_instead = pcursor.prefer_next_row
&& !line.ends_with_newline && !row.ends_with_newline
&& column == line.char_count_excluding_newline(); && column == row.char_count_excluding_newline();
if !select_next_line_instead { if !select_next_line_instead {
ccursor_it.index += column; ccursor_it.index += column;
return Cursor { return Cursor {
ccursor: ccursor_it, ccursor: ccursor_it,
lcursor: LCursor { rcursor: RCursor {
line: line_nr, row: line_nr,
column, column,
}, },
pcursor, pcursor,
@ -498,17 +497,17 @@ impl Galley {
} }
} }
ccursor_it.index += line.char_count_including_newline(); ccursor_it.index += row.char_count_including_newline();
if line.ends_with_newline { if row.ends_with_newline {
pcursor_it.paragraph += 1; pcursor_it.paragraph += 1;
pcursor_it.offset = 0; pcursor_it.offset = 0;
} else { } else {
pcursor_it.offset += line.char_count_including_newline(); pcursor_it.offset += row.char_count_including_newline();
} }
} }
Cursor { Cursor {
ccursor: ccursor_it, ccursor: ccursor_it,
lcursor: self.end_lcursor(), rcursor: self.end_rcursor(),
pcursor, pcursor,
} }
} }
@ -529,38 +528,38 @@ impl Galley {
} }
pub fn cursor_up_one_line(&self, cursor: &Cursor) -> Cursor { pub fn cursor_up_one_line(&self, cursor: &Cursor) -> Cursor {
if cursor.lcursor.line == 0 { if cursor.rcursor.row == 0 {
Cursor::default() Cursor::default()
} else { } else {
let x = self.pos_from_cursor(cursor).x; let x = self.pos_from_cursor(cursor).x;
let line = cursor.lcursor.line - 1; let row = cursor.rcursor.row - 1;
let column = self.lines[line].char_at(x).max(cursor.lcursor.column); let column = self.rows[row].char_at(x).max(cursor.rcursor.column);
self.from_lcursor(LCursor { line, column }) self.from_rcursor(RCursor { row, column })
} }
} }
pub fn cursor_down_one_line(&self, cursor: &Cursor) -> Cursor { pub fn cursor_down_one_line(&self, cursor: &Cursor) -> Cursor {
if cursor.lcursor.line + 1 < self.lines.len() { if cursor.rcursor.row + 1 < self.rows.len() {
let x = self.pos_from_cursor(cursor).x; let x = self.pos_from_cursor(cursor).x;
let line = cursor.lcursor.line + 1; let row = cursor.rcursor.row + 1;
let column = self.lines[line].char_at(x).max(cursor.lcursor.column); let column = self.rows[row].char_at(x).max(cursor.rcursor.column);
self.from_lcursor(LCursor { line, column }) self.from_rcursor(RCursor { row, column })
} else { } else {
self.end() self.end()
} }
} }
pub fn cursor_begin_of_line(&self, cursor: &Cursor) -> Cursor { pub fn cursor_begin_of_line(&self, cursor: &Cursor) -> Cursor {
self.from_lcursor(LCursor { self.from_rcursor(RCursor {
line: cursor.lcursor.line, row: cursor.rcursor.row,
column: 0, column: 0,
}) })
} }
pub fn cursor_end_of_line(&self, cursor: &Cursor) -> Cursor { pub fn cursor_end_of_line(&self, cursor: &Cursor) -> Cursor {
self.from_lcursor(LCursor { self.from_rcursor(RCursor {
line: cursor.lcursor.line, row: cursor.rcursor.row,
column: self.lines[cursor.lcursor.line].char_count_excluding_newline(), column: self.rows[cursor.rcursor.row].char_count_excluding_newline(),
}) })
} }
} }
@ -579,172 +578,160 @@ fn test_text_layout() {
let font = Font::new(atlas, typeface_data, 13.0, pixels_per_point); let font = Font::new(atlas, typeface_data, 13.0, pixels_per_point);
let galley = font.layout_multiline("".to_owned(), 1024.0); let galley = font.layout_multiline("".to_owned(), 1024.0);
assert_eq!(galley.lines.len(), 1); assert_eq!(galley.rows.len(), 1);
assert_eq!(galley.lines[0].ends_with_newline, false); assert_eq!(galley.rows[0].ends_with_newline, false);
assert_eq!(galley.lines[0].x_offsets, vec![0.0]); assert_eq!(galley.rows[0].x_offsets, vec![0.0]);
let galley = font.layout_multiline("\n".to_owned(), 1024.0); let galley = font.layout_multiline("\n".to_owned(), 1024.0);
assert_eq!(galley.lines.len(), 2); assert_eq!(galley.rows.len(), 2);
assert_eq!(galley.lines[0].ends_with_newline, true); assert_eq!(galley.rows[0].ends_with_newline, true);
assert_eq!(galley.lines[1].ends_with_newline, false); assert_eq!(galley.rows[1].ends_with_newline, false);
assert_eq!(galley.lines[1].x_offsets, vec![0.0]); assert_eq!(galley.rows[1].x_offsets, vec![0.0]);
let galley = font.layout_multiline("\n\n".to_owned(), 1024.0); let galley = font.layout_multiline("\n\n".to_owned(), 1024.0);
assert_eq!(galley.lines.len(), 3); assert_eq!(galley.rows.len(), 3);
assert_eq!(galley.lines[0].ends_with_newline, true); assert_eq!(galley.rows[0].ends_with_newline, true);
assert_eq!(galley.lines[1].ends_with_newline, true); assert_eq!(galley.rows[1].ends_with_newline, true);
assert_eq!(galley.lines[2].ends_with_newline, false); assert_eq!(galley.rows[2].ends_with_newline, false);
assert_eq!(galley.lines[2].x_offsets, vec![0.0]); assert_eq!(galley.rows[2].x_offsets, vec![0.0]);
let galley = font.layout_multiline(" ".to_owned(), 1024.0); let galley = font.layout_multiline(" ".to_owned(), 1024.0);
assert_eq!(galley.lines.len(), 1); assert_eq!(galley.rows.len(), 1);
assert_eq!(galley.lines[0].ends_with_newline, false); assert_eq!(galley.rows[0].ends_with_newline, false);
let galley = font.layout_multiline("One line".to_owned(), 1024.0); let galley = font.layout_multiline("One row!".to_owned(), 1024.0);
assert_eq!(galley.lines.len(), 1); assert_eq!(galley.rows.len(), 1);
assert_eq!(galley.lines[0].ends_with_newline, false); assert_eq!(galley.rows[0].ends_with_newline, false);
let galley = font.layout_multiline("First line\n".to_owned(), 1024.0); let galley = font.layout_multiline("First row!\n".to_owned(), 1024.0);
assert_eq!(galley.lines.len(), 2); assert_eq!(galley.rows.len(), 2);
assert_eq!(galley.lines[0].ends_with_newline, true); assert_eq!(galley.rows[0].ends_with_newline, true);
assert_eq!(galley.lines[1].ends_with_newline, false); assert_eq!(galley.rows[1].ends_with_newline, false);
assert_eq!(galley.lines[1].x_offsets, vec![0.0]); assert_eq!(galley.rows[1].x_offsets, vec![0.0]);
let galley = font.layout_multiline("line\nbreak".to_owned(), 10.0); let galley = font.layout_multiline("line\nbreak".to_owned(), 10.0);
assert_eq!(galley.lines.len(), 2); assert_eq!(galley.rows.len(), 2);
assert_eq!(galley.lines[0].ends_with_newline, true); assert_eq!(galley.rows[0].ends_with_newline, true);
assert_eq!(galley.lines[1].ends_with_newline, false); assert_eq!(galley.rows[1].ends_with_newline, false);
// Test wrapping: // Test wrapping:
let galley = font.layout_multiline("line wrap".to_owned(), 10.0); let galley = font.layout_multiline("word wrap".to_owned(), 10.0);
assert_eq!(galley.lines.len(), 2); assert_eq!(galley.rows.len(), 2);
assert_eq!(galley.lines[0].ends_with_newline, false); assert_eq!(galley.rows[0].ends_with_newline, false);
assert_eq!(galley.lines[1].ends_with_newline, false); assert_eq!(galley.rows[1].ends_with_newline, false);
{ {
// Test wrapping: // Test wrapping:
let galley = font.layout_multiline("Line wrap.\nNew paragraph.".to_owned(), 10.0); let galley = font.layout_multiline("word wrap.\nNew paragraph.".to_owned(), 10.0);
assert_eq!(galley.lines.len(), 4); assert_eq!(galley.rows.len(), 4);
assert_eq!(galley.lines[0].ends_with_newline, false); assert_eq!(galley.rows[0].ends_with_newline, false);
assert_eq!(galley.rows[0].char_count_excluding_newline(), "word ".len());
assert_eq!(galley.rows[0].char_count_including_newline(), "word ".len());
assert_eq!(galley.rows[1].ends_with_newline, true);
assert_eq!(galley.rows[1].char_count_excluding_newline(), "wrap.".len());
assert_eq!( assert_eq!(
galley.lines[0].char_count_excluding_newline(), galley.rows[1].char_count_including_newline(),
"Line ".len()
);
assert_eq!(
galley.lines[0].char_count_including_newline(),
"Line ".len()
);
assert_eq!(galley.lines[1].ends_with_newline, true);
assert_eq!(
galley.lines[1].char_count_excluding_newline(),
"wrap.".len()
);
assert_eq!(
galley.lines[1].char_count_including_newline(),
"wrap.\n".len() "wrap.\n".len()
); );
assert_eq!(galley.lines[2].ends_with_newline, false); assert_eq!(galley.rows[2].ends_with_newline, false);
assert_eq!(galley.lines[3].ends_with_newline, false); assert_eq!(galley.rows[3].ends_with_newline, false);
let cursor = Cursor::default(); let cursor = Cursor::default();
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor)); assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
assert_eq!(cursor, galley.from_lcursor(cursor.lcursor)); assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor)); assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
let cursor = galley.end(); let cursor = galley.end();
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor)); assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
assert_eq!(cursor, galley.from_lcursor(cursor.lcursor)); assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor)); assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
assert_eq!( assert_eq!(
cursor, cursor,
Cursor { Cursor {
ccursor: CCursor::new(25), ccursor: CCursor::new(25),
lcursor: LCursor { rcursor: RCursor { row: 3, column: 10 },
line: 3,
column: 10
},
pcursor: PCursor { pcursor: PCursor {
paragraph: 1, paragraph: 1,
offset: 14, offset: 14,
prefer_next_line: false, prefer_next_row: false,
} }
} }
); );
let cursor = galley.from_ccursor(CCursor::new(1)); let cursor = galley.from_ccursor(CCursor::new(1));
assert_eq!(cursor.lcursor, LCursor { line: 0, column: 1 }); assert_eq!(cursor.rcursor, RCursor { row: 0, column: 1 });
assert_eq!( assert_eq!(
cursor.pcursor, cursor.pcursor,
PCursor { PCursor {
paragraph: 0, paragraph: 0,
offset: 1, offset: 1,
prefer_next_line: false, prefer_next_row: false,
} }
); );
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor)); assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
assert_eq!(cursor, galley.from_lcursor(cursor.lcursor)); assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor)); assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
let cursor = galley.from_pcursor(PCursor { let cursor = galley.from_pcursor(PCursor {
paragraph: 1, paragraph: 1,
offset: 2, offset: 2,
prefer_next_line: false, prefer_next_row: false,
}); });
assert_eq!(cursor.lcursor, LCursor { line: 2, column: 2 }); assert_eq!(cursor.rcursor, RCursor { row: 2, column: 2 });
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor)); assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
assert_eq!(cursor, galley.from_lcursor(cursor.lcursor)); assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor)); assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
let cursor = galley.from_pcursor(PCursor { let cursor = galley.from_pcursor(PCursor {
paragraph: 1, paragraph: 1,
offset: 6, offset: 6,
prefer_next_line: false, prefer_next_row: false,
}); });
assert_eq!(cursor.lcursor, LCursor { line: 3, column: 2 }); assert_eq!(cursor.rcursor, RCursor { row: 3, column: 2 });
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor)); assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
assert_eq!(cursor, galley.from_lcursor(cursor.lcursor)); assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor)); assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
// On the border between two lines within the same paragraph: // On the border between two rows within the same paragraph:
let cursor = galley.from_lcursor(LCursor { line: 0, column: 5 }); let cursor = galley.from_rcursor(RCursor { row: 0, column: 5 });
assert_eq!( assert_eq!(
cursor, cursor,
Cursor { Cursor {
ccursor: CCursor::new(5), ccursor: CCursor::new(5),
lcursor: LCursor { line: 0, column: 5 }, rcursor: RCursor { row: 0, column: 5 },
pcursor: PCursor { pcursor: PCursor {
paragraph: 0, paragraph: 0,
offset: 5, offset: 5,
prefer_next_line: false, prefer_next_row: false,
} }
} }
); );
assert_eq!(cursor, galley.from_lcursor(cursor.lcursor)); assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
let cursor = galley.from_lcursor(LCursor { line: 1, column: 0 }); let cursor = galley.from_rcursor(RCursor { row: 1, column: 0 });
assert_eq!( assert_eq!(
cursor, cursor,
Cursor { Cursor {
ccursor: CCursor::new(5), ccursor: CCursor::new(5),
lcursor: LCursor { line: 1, column: 0 }, rcursor: RCursor { row: 1, column: 0 },
pcursor: PCursor { pcursor: PCursor {
paragraph: 0, paragraph: 0,
offset: 5, offset: 5,
prefer_next_line: false, prefer_next_row: false,
} }
} }
); );
assert_eq!(cursor, galley.from_lcursor(cursor.lcursor)); assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
} }
{ {
// Test cursor movement: // Test cursor movement:
let galley = font.layout_multiline("Line wrap.\nNew paragraph.".to_owned(), 10.0); let galley = font.layout_multiline("word wrap.\nNew paragraph.".to_owned(), 10.0);
assert_eq!(galley.lines.len(), 4); assert_eq!(galley.rows.len(), 4);
assert_eq!(galley.lines[0].ends_with_newline, false); assert_eq!(galley.rows[0].ends_with_newline, false);
assert_eq!(galley.lines[1].ends_with_newline, true); assert_eq!(galley.rows[1].ends_with_newline, true);
assert_eq!(galley.lines[2].ends_with_newline, false); assert_eq!(galley.rows[2].ends_with_newline, false);
assert_eq!(galley.lines[3].ends_with_newline, false); assert_eq!(galley.rows[3].ends_with_newline, false);
let cursor = Cursor::default(); let cursor = Cursor::default();
@ -755,11 +742,11 @@ fn test_text_layout() {
galley.cursor_end_of_line(&cursor), galley.cursor_end_of_line(&cursor),
Cursor { Cursor {
ccursor: CCursor::new(5), ccursor: CCursor::new(5),
lcursor: LCursor { line: 0, column: 5 }, rcursor: RCursor { row: 0, column: 5 },
pcursor: PCursor { pcursor: PCursor {
paragraph: 0, paragraph: 0,
offset: 5, offset: 5,
prefer_next_line: false, prefer_next_row: false,
} }
} }
); );
@ -768,11 +755,11 @@ fn test_text_layout() {
galley.cursor_down_one_line(&cursor), galley.cursor_down_one_line(&cursor),
Cursor { Cursor {
ccursor: CCursor::new(5), ccursor: CCursor::new(5),
lcursor: LCursor { line: 1, column: 0 }, rcursor: RCursor { row: 1, column: 0 },
pcursor: PCursor { pcursor: PCursor {
paragraph: 0, paragraph: 0,
offset: 5, offset: 5,
prefer_next_line: false, prefer_next_row: false,
} }
} }
); );
@ -782,11 +769,11 @@ fn test_text_layout() {
galley.cursor_down_one_line(&galley.cursor_down_one_line(&cursor)), galley.cursor_down_one_line(&galley.cursor_down_one_line(&cursor)),
Cursor { Cursor {
ccursor: CCursor::new(11), ccursor: CCursor::new(11),
lcursor: LCursor { line: 2, column: 0 }, rcursor: RCursor { row: 2, column: 0 },
pcursor: PCursor { pcursor: PCursor {
paragraph: 1, paragraph: 1,
offset: 0, offset: 0,
prefer_next_line: false, prefer_next_row: false,
} }
} }
); );
@ -801,14 +788,11 @@ fn test_text_layout() {
galley.cursor_up_one_line(&galley.end()), galley.cursor_up_one_line(&galley.end()),
Cursor { Cursor {
ccursor: CCursor::new(15), ccursor: CCursor::new(15),
lcursor: LCursor { rcursor: RCursor { row: 2, column: 10 },
line: 2,
column: 10
},
pcursor: PCursor { pcursor: PCursor {
paragraph: 1, paragraph: 1,
offset: 4, offset: 4,
prefer_next_line: false, prefer_next_row: false,
} }
} }
); );

View file

@ -67,7 +67,7 @@ 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.lines) Self::from_slice(galley.text.as_bytes()) + Self::from_slice(&galley.rows)
} }
pub fn from_triangles(triangles: &Triangles) -> Self { pub fn from_triangles(triangles: &Triangles) -> Self {

View file

@ -791,9 +791,9 @@ fn tessellate_paint_command(
let font = &fonts[text_style]; let font = &fonts[text_style];
let mut chars = galley.text.chars(); let mut chars = galley.text.chars();
for line in &galley.lines { for line in &galley.rows {
let line_min_y = pos.y + line.y_min + text_offset.x; let line_min_y = pos.y + line.y_min + text_offset.x;
let line_max_y = line_min_y + font.height(); let line_max_y = line_min_y + font.row_height();
let is_line_visible = let is_line_visible =
line_max_y >= clip_rect.min.y && line_min_y <= clip_rect.max.y; line_max_y >= clip_rect.min.y && line_min_y <= clip_rect.max.y;

View file

@ -98,7 +98,7 @@ impl Label {
pub fn font_height(&self, fonts: &Fonts, style: &Style) -> f32 { pub fn font_height(&self, fonts: &Fonts, style: &Style) -> f32 {
let text_style = self.text_style_or_default(style); let text_style = self.text_style_or_default(style);
fonts[text_style].height() fonts[text_style].row_height()
} }
// TODO: this should return a LabelLayout which has a paint method. // TODO: this should return a LabelLayout which has a paint method.
@ -222,7 +222,7 @@ impl Widget for Hyperlink {
if response.hovered { if response.hovered {
// Underline: // Underline:
for line in &galley.lines { for line in &galley.rows {
let pos = response.rect.min; let pos = response.rect.min;
let y = pos.y + line.y_max; let y = pos.y + line.y_max;
let y = ui.painter().round_to_pixel(y); let y = ui.painter().round_to_pixel(y);

View file

@ -367,7 +367,7 @@ impl<'a> Widget for Slider<'a> {
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &ui.fonts()[text_style]; let font = &ui.fonts()[text_style];
let height = font let height = font
.line_spacing() .row_height()
.at_least(ui.style().spacing.interact_size.y); .at_least(ui.style().spacing.interact_size.y);
if self.text.is_some() { if self.text.is_some() {

View file

@ -7,8 +7,8 @@ pub(crate) struct State {
/// We store as PCursor (paragraph number, and character offset within that paragraph). /// We store as PCursor (paragraph number, and character offset within that paragraph).
/// This is so what if we resize the `TextEdit` region, and text wrapping changes, /// This is so what if we resize the `TextEdit` region, and text wrapping changes,
/// we keep the same byte character offset from the beginning of the text, /// we keep the same byte character offset from the beginning of the text,
/// even though the number of lines changes /// even though the number of rows changes
/// (each paragraph can be several lines, due to word wrapping). /// (each paragraph can be several rows, due to word wrapping).
/// The column (character offset) should be able to extend beyond the last word so that we can /// The column (character offset) should be able to extend beyond the last word so that we can
/// go down and still end up on the same column when we return. /// go down and still end up on the same column when we return.
pcursor: Option<PCursor>, pcursor: Option<PCursor>,
@ -156,7 +156,7 @@ impl<'t> Widget for TextEdit<'t> {
let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style); let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style);
let font = &ui.fonts()[text_style]; let font = &ui.fonts()[text_style];
let line_spacing = font.line_spacing(); let line_spacing = font.row_height();
let available_width = ui.available().width(); let available_width = ui.available().width();
let mut galley = if multiline { let mut galley = if multiline {
font.layout_multiline(text.clone(), available_width) font.layout_multiline(text.clone(), available_width)