2020-11-13 00:23:36 +00:00
|
|
|
//! 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.
|
|
|
|
|
2020-11-14 22:07:16 +00:00
|
|
|
use crate::math::{pos2, NumExt, Rect, Vec2};
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
/// Character cursor
|
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
|
|
pub struct CCursor {
|
|
|
|
/// Character offset (NOT byte offset!).
|
|
|
|
pub index: usize,
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
|
|
|
|
/// do we prefer the next row?
|
2020-11-13 22:22:22 +00:00
|
|
|
/// This is *almost* always what you want, *except* for when
|
|
|
|
/// explicitly clicking the end of a row or pressing the end key.
|
2020-11-13 00:23:36 +00:00
|
|
|
pub prefer_next_row: bool,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl CCursor {
|
|
|
|
pub fn new(index: usize) -> Self {
|
|
|
|
Self {
|
|
|
|
index,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Two `CCursor`s are considered equal if they refer to the same character boundary,
|
2020-11-13 00:23:36 +00:00
|
|
|
/// even if one prefers the start of the next row.
|
2020-11-09 20:55:56 +00:00
|
|
|
impl PartialEq for CCursor {
|
|
|
|
fn eq(&self, other: &CCursor) -> bool {
|
|
|
|
self.index == other.index
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::Add<usize> for CCursor {
|
|
|
|
type Output = CCursor;
|
|
|
|
fn add(self, rhs: usize) -> Self::Output {
|
|
|
|
CCursor {
|
|
|
|
index: self.index.saturating_add(rhs),
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: self.prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::Sub<usize> for CCursor {
|
|
|
|
type Output = CCursor;
|
|
|
|
fn sub(self, rhs: usize) -> Self::Output {
|
|
|
|
CCursor {
|
|
|
|
index: self.index.saturating_sub(rhs),
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: self.prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// Row Cursor
|
2020-11-09 20:55:56 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
2020-11-13 00:23:36 +00:00
|
|
|
pub struct RCursor {
|
|
|
|
/// 0 is first row, and so on.
|
|
|
|
/// Note that a single paragraph can span multiple rows.
|
2020-11-09 20:55:56 +00:00
|
|
|
/// (a paragraph is text separated by `\n`).
|
2020-11-13 00:23:36 +00:00
|
|
|
pub row: usize,
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
/// Character based (NOT bytes).
|
2020-11-13 00:23:36 +00:00
|
|
|
/// 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 row.
|
2020-11-09 20:55:56 +00:00
|
|
|
pub column: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Paragraph Cursor
|
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
|
|
pub struct PCursor {
|
|
|
|
/// 0 is first paragraph, and so on.
|
2020-11-13 00:23:36 +00:00
|
|
|
/// Note that a single paragraph can span multiple rows.
|
2020-11-09 20:55:56 +00:00
|
|
|
/// (a paragraph is text separated by `\n`).
|
|
|
|
pub paragraph: usize,
|
|
|
|
|
|
|
|
/// Character based (NOT bytes).
|
2020-11-13 00:23:36 +00:00
|
|
|
/// 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 paragraph.
|
2020-11-09 20:55:56 +00:00
|
|
|
pub offset: usize,
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
|
|
|
|
/// do we prefer the next row?
|
2020-11-13 22:22:22 +00:00
|
|
|
/// This is *almost* always what you want, *except* for when
|
|
|
|
/// explicitly clicking the end of a row or pressing the end key.
|
2020-11-13 00:23:36 +00:00
|
|
|
pub prefer_next_row: bool,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Two `PCursor`s are considered equal if they refer to the same character boundary,
|
2020-11-13 00:23:36 +00:00
|
|
|
/// even if one prefers the start of the next row.
|
2020-11-09 20:55:56 +00:00
|
|
|
impl PartialEq for PCursor {
|
|
|
|
fn eq(&self, other: &PCursor) -> bool {
|
|
|
|
self.paragraph == other.paragraph && self.offset == other.offset
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// All different types of cursors together.
|
|
|
|
/// They all point to the same place, but in their own different ways.
|
2020-11-14 23:38:04 +00:00
|
|
|
/// pcursor/rcursor can also point to after the end of the paragraph/row.
|
|
|
|
/// Does not implement `PartialEq` because you must think which cursor should be equivalent.
|
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
2020-11-09 20:55:56 +00:00
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
|
|
pub struct Cursor {
|
|
|
|
pub ccursor: CCursor,
|
2020-11-13 00:23:36 +00:00
|
|
|
pub rcursor: RCursor,
|
2020-11-09 20:55:56 +00:00
|
|
|
pub pcursor: PCursor,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A collection of text locked into place.
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
pub struct Galley {
|
|
|
|
/// The full text, including any an all `\n`.
|
|
|
|
pub text: String,
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// Rows of text, from top to bottom.
|
|
|
|
/// The number of chars in all rows sum up to text.chars().count().
|
2020-11-09 20:55:56 +00:00
|
|
|
/// Note that each paragraph (pieces of text separated with `\n`)
|
2020-11-13 00:23:36 +00:00
|
|
|
/// can be split up into multiple rows.
|
|
|
|
pub rows: Vec<Row>,
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
// Optimization: calculated once and reused.
|
|
|
|
pub size: Vec2,
|
|
|
|
}
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// A typeset piece of text on a single row.
|
2020-11-09 20:55:56 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2020-11-13 00:23:36 +00:00
|
|
|
pub struct Row {
|
2020-11-09 20:55:56 +00:00
|
|
|
/// The start of each character, probably starting at zero.
|
|
|
|
/// The last element is the end of the last character.
|
|
|
|
/// This is never empty.
|
|
|
|
/// Unit: points.
|
|
|
|
///
|
|
|
|
/// `x_offsets.len() + (ends_with_newline as usize) == text.chars().count() + 1`
|
|
|
|
pub x_offsets: Vec<f32>,
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// Top of the row, offset within the Galley.
|
2020-11-09 20:55:56 +00:00
|
|
|
/// Unit: points.
|
|
|
|
pub y_min: f32,
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// Bottom of the row, offset within the Galley.
|
2020-11-09 20:55:56 +00:00
|
|
|
/// Unit: points.
|
|
|
|
pub y_max: f32,
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// If true, this `Row` came from a paragraph ending with a `\n`.
|
2020-11-09 20:55:56 +00:00
|
|
|
/// The `\n` itself is omitted from `x_offsets`.
|
2020-11-13 00:23:36 +00:00
|
|
|
/// A `\n` in the input text always creates a new `Row` below it,
|
|
|
|
/// so that text that ends with `\n` has an empty `Row` last.
|
|
|
|
/// This also implies that the last `Row` in a `Galley` always has `ends_with_newline == false`.
|
2020-11-09 20:55:56 +00:00
|
|
|
pub ends_with_newline: bool,
|
|
|
|
}
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
impl Row {
|
2020-11-09 20:55:56 +00:00
|
|
|
pub fn sanity_check(&self) {
|
|
|
|
assert!(!self.x_offsets.is_empty());
|
|
|
|
}
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// Excludes the implicit `\n` after the `Row`, if any.
|
2020-11-09 20:55:56 +00:00
|
|
|
pub fn char_count_excluding_newline(&self) -> usize {
|
|
|
|
assert!(!self.x_offsets.is_empty());
|
|
|
|
self.x_offsets.len() - 1
|
|
|
|
}
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
/// Includes the implicit `\n` after the `Row`, if any.
|
2020-11-09 20:55:56 +00:00
|
|
|
pub fn char_count_including_newline(&self) -> usize {
|
|
|
|
self.char_count_excluding_newline() + (self.ends_with_newline as usize)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn min_x(&self) -> f32 {
|
|
|
|
*self.x_offsets.first().unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn max_x(&self) -> f32 {
|
|
|
|
*self.x_offsets.last().unwrap()
|
|
|
|
}
|
|
|
|
|
2020-11-15 13:21:21 +00:00
|
|
|
pub fn height(&self) -> f32 {
|
|
|
|
self.y_max - self.y_min
|
|
|
|
}
|
|
|
|
|
2020-11-09 20:55:56 +00:00
|
|
|
/// Closest char at the desired x coordinate.
|
|
|
|
/// Returns something in the range `[0, char_count_excluding_newline()]`.
|
|
|
|
pub fn char_at(&self, desired_x: f32) -> usize {
|
|
|
|
for (i, char_x_bounds) in self.x_offsets.windows(2).enumerate() {
|
|
|
|
let char_center_x = 0.5 * (char_x_bounds[0] + char_x_bounds[1]);
|
|
|
|
if desired_x < char_center_x {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.char_count_excluding_newline()
|
|
|
|
}
|
2020-11-14 23:38:04 +00:00
|
|
|
|
|
|
|
pub fn x_offset(&self, column: usize) -> f32 {
|
|
|
|
self.x_offsets[column.min(self.x_offsets.len() - 1)]
|
|
|
|
}
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Galley {
|
|
|
|
pub fn sanity_check(&self) {
|
|
|
|
let mut char_count = 0;
|
2020-11-13 00:23:36 +00:00
|
|
|
for row in &self.rows {
|
|
|
|
row.sanity_check();
|
|
|
|
char_count += row.char_count_including_newline();
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
assert_eq!(char_count, self.text.chars().count());
|
2020-11-13 00:23:36 +00:00
|
|
|
if let Some(last_row) = self.rows.last() {
|
2020-11-09 20:55:56 +00:00
|
|
|
debug_assert!(
|
2020-11-13 00:23:36 +00:00
|
|
|
!last_row.ends_with_newline,
|
|
|
|
"If the text ends with '\\n', there would be an empty row last.\n\
|
2020-11-09 20:55:56 +00:00
|
|
|
Galley: {:#?}",
|
|
|
|
self
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// ## Physical positions
|
|
|
|
impl Galley {
|
2020-11-14 23:38:04 +00:00
|
|
|
fn end_pos(&self) -> Rect {
|
2020-11-14 22:07:16 +00:00
|
|
|
if let Some(row) = self.rows.last() {
|
|
|
|
let x = row.max_x();
|
2020-11-15 13:21:21 +00:00
|
|
|
Rect::from_min_max(pos2(x, row.y_min), pos2(x, row.y_max))
|
2020-11-09 20:55:56 +00:00
|
|
|
} else {
|
2020-11-14 22:07:16 +00:00
|
|
|
// Empty galley
|
|
|
|
Rect::from_min_max(pos2(0.0, 0.0), pos2(0.0, 0.0))
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-14 22:07:16 +00:00
|
|
|
/// Returns a 0-width Rect.
|
|
|
|
pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Rect {
|
2020-11-09 20:55:56 +00:00
|
|
|
let mut it = PCursor::default();
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
for row in &self.rows {
|
2020-11-09 20:55:56 +00:00
|
|
|
if it.paragraph == pcursor.paragraph {
|
2020-11-13 00:23:36 +00:00
|
|
|
// Right paragraph, but is it the right row in the paragraph?
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
if it.offset <= pcursor.offset
|
2020-11-13 22:22:22 +00:00
|
|
|
&& (pcursor.offset <= it.offset + row.char_count_excluding_newline()
|
|
|
|
|| row.ends_with_newline)
|
2020-11-09 20:55:56 +00:00
|
|
|
{
|
|
|
|
let column = pcursor.offset - it.offset;
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
let select_next_row_instead = pcursor.prefer_next_row
|
2020-11-13 00:23:36 +00:00
|
|
|
&& !row.ends_with_newline
|
2020-11-13 22:22:22 +00:00
|
|
|
&& column >= row.char_count_excluding_newline();
|
|
|
|
if !select_next_row_instead {
|
2020-11-14 23:38:04 +00:00
|
|
|
let x = row.x_offset(column);
|
2020-11-14 22:07:16 +00:00
|
|
|
return Rect::from_min_max(pos2(x, row.y_min), pos2(x, row.y_max));
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
if row.ends_with_newline {
|
2020-11-09 20:55:56 +00:00
|
|
|
it.paragraph += 1;
|
|
|
|
it.offset = 0;
|
|
|
|
} else {
|
2020-11-13 00:23:36 +00:00
|
|
|
it.offset += row.char_count_including_newline();
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-14 23:38:04 +00:00
|
|
|
self.end_pos()
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
|
2020-11-14 22:07:16 +00:00
|
|
|
/// Returns a 0-width Rect.
|
|
|
|
pub fn pos_from_cursor(&self, cursor: &Cursor) -> Rect {
|
2020-11-09 20:55:56 +00:00
|
|
|
self.pos_from_pcursor(cursor.pcursor) // The one TextEdit stores
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Cursor at the given position within the galley
|
2020-11-13 22:22:22 +00:00
|
|
|
pub fn cursor_from_pos(&self, pos: Vec2) -> Cursor {
|
2020-11-09 20:55:56 +00:00
|
|
|
let mut best_y_dist = f32::INFINITY;
|
|
|
|
let mut cursor = Cursor::default();
|
|
|
|
|
|
|
|
let mut ccursor_index = 0;
|
|
|
|
let mut pcursor_it = PCursor::default();
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
for (row_nr, row) in self.rows.iter().enumerate() {
|
2020-11-13 00:23:36 +00:00
|
|
|
let y_dist = (row.y_min - pos.y).abs().min((row.y_max - pos.y).abs());
|
2020-11-09 20:55:56 +00:00
|
|
|
if y_dist < best_y_dist {
|
|
|
|
best_y_dist = y_dist;
|
2020-11-13 00:23:36 +00:00
|
|
|
let column = row.char_at(pos.x);
|
2020-11-13 22:22:22 +00:00
|
|
|
let prefer_next_row = column < row.char_count_excluding_newline();
|
2020-11-09 20:55:56 +00:00
|
|
|
cursor = Cursor {
|
|
|
|
ccursor: CCursor {
|
|
|
|
index: ccursor_index + column,
|
2020-11-13 22:22:22 +00:00
|
|
|
prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
},
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor {
|
2020-11-13 22:22:22 +00:00
|
|
|
row: row_nr,
|
2020-11-09 20:55:56 +00:00
|
|
|
column,
|
|
|
|
},
|
|
|
|
pcursor: PCursor {
|
|
|
|
paragraph: pcursor_it.paragraph,
|
|
|
|
offset: pcursor_it.offset + column,
|
2020-11-13 22:22:22 +00:00
|
|
|
prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2020-11-13 00:23:36 +00:00
|
|
|
ccursor_index += row.char_count_including_newline();
|
|
|
|
if row.ends_with_newline {
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor_it.paragraph += 1;
|
|
|
|
pcursor_it.offset = 0;
|
|
|
|
} else {
|
2020-11-13 00:23:36 +00:00
|
|
|
pcursor_it.offset += row.char_count_including_newline();
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
cursor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// ## Cursor positions
|
|
|
|
impl Galley {
|
|
|
|
/// Cursor to one-past last character.
|
|
|
|
pub fn end(&self) -> Cursor {
|
2020-11-13 00:23:36 +00:00
|
|
|
if self.rows.is_empty() {
|
2020-11-09 20:55:56 +00:00
|
|
|
return Default::default();
|
|
|
|
}
|
|
|
|
let mut ccursor = CCursor {
|
|
|
|
index: 0,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: true,
|
2020-11-09 20:55:56 +00:00
|
|
|
};
|
|
|
|
let mut pcursor = PCursor {
|
|
|
|
paragraph: 0,
|
|
|
|
offset: 0,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: true,
|
2020-11-09 20:55:56 +00:00
|
|
|
};
|
2020-11-13 00:23:36 +00:00
|
|
|
for row in &self.rows {
|
2020-11-13 22:22:22 +00:00
|
|
|
let row_char_count = row.char_count_including_newline();
|
|
|
|
ccursor.index += row_char_count;
|
2020-11-13 00:23:36 +00:00
|
|
|
if row.ends_with_newline {
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor.paragraph += 1;
|
|
|
|
pcursor.offset = 0;
|
|
|
|
} else {
|
2020-11-13 22:22:22 +00:00
|
|
|
pcursor.offset += row_char_count;
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Cursor {
|
|
|
|
ccursor,
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: self.end_rcursor(),
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
pub fn end_rcursor(&self) -> RCursor {
|
|
|
|
if let Some(last_row) = self.rows.last() {
|
|
|
|
debug_assert!(!last_row.ends_with_newline);
|
|
|
|
RCursor {
|
|
|
|
row: self.rows.len() - 1,
|
|
|
|
column: last_row.char_count_excluding_newline(),
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// ## Cursor conversions
|
|
|
|
impl Galley {
|
2020-11-13 22:22:22 +00:00
|
|
|
// The returned cursor is clamped.
|
2020-11-09 20:55:56 +00:00
|
|
|
pub fn from_ccursor(&self, ccursor: CCursor) -> Cursor {
|
2020-11-13 22:22:22 +00:00
|
|
|
let prefer_next_row = ccursor.prefer_next_row;
|
2020-11-09 20:55:56 +00:00
|
|
|
let mut ccursor_it = CCursor {
|
|
|
|
index: 0,
|
2020-11-13 22:22:22 +00:00
|
|
|
prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
};
|
|
|
|
let mut pcursor_it = PCursor {
|
|
|
|
paragraph: 0,
|
|
|
|
offset: 0,
|
2020-11-13 22:22:22 +00:00
|
|
|
prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
};
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
for (row_nr, row) in self.rows.iter().enumerate() {
|
|
|
|
let row_char_count = row.char_count_excluding_newline();
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
if ccursor_it.index <= ccursor.index
|
2020-11-13 22:22:22 +00:00
|
|
|
&& ccursor.index <= ccursor_it.index + row_char_count
|
2020-11-09 20:55:56 +00:00
|
|
|
{
|
|
|
|
let column = ccursor.index - ccursor_it.index;
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
let select_next_row_instead = prefer_next_row
|
2020-11-13 00:23:36 +00:00
|
|
|
&& !row.ends_with_newline
|
2020-11-13 22:22:22 +00:00
|
|
|
&& column >= row.char_count_excluding_newline();
|
|
|
|
if !select_next_row_instead {
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor_it.offset += column;
|
|
|
|
return Cursor {
|
|
|
|
ccursor,
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor {
|
2020-11-13 22:22:22 +00:00
|
|
|
row: row_nr,
|
2020-11-09 20:55:56 +00:00
|
|
|
column,
|
|
|
|
},
|
|
|
|
pcursor: pcursor_it,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2020-11-13 00:23:36 +00:00
|
|
|
ccursor_it.index += row.char_count_including_newline();
|
|
|
|
if row.ends_with_newline {
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor_it.paragraph += 1;
|
|
|
|
pcursor_it.offset = 0;
|
|
|
|
} else {
|
2020-11-13 00:23:36 +00:00
|
|
|
pcursor_it.offset += row.char_count_including_newline();
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
debug_assert_eq!(ccursor_it, self.end().ccursor);
|
|
|
|
Cursor {
|
|
|
|
ccursor: ccursor_it, // clamp
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: self.end_rcursor(),
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor: pcursor_it,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
pub fn from_rcursor(&self, rcursor: RCursor) -> Cursor {
|
|
|
|
if rcursor.row >= self.rows.len() {
|
2020-11-09 20:55:56 +00:00
|
|
|
return self.end();
|
|
|
|
}
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
let prefer_next_row =
|
|
|
|
rcursor.column < self.rows[rcursor.row].char_count_excluding_newline();
|
2020-11-09 20:55:56 +00:00
|
|
|
let mut ccursor_it = CCursor {
|
|
|
|
index: 0,
|
2020-11-13 22:22:22 +00:00
|
|
|
prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
};
|
|
|
|
let mut pcursor_it = PCursor {
|
|
|
|
paragraph: 0,
|
|
|
|
offset: 0,
|
2020-11-13 22:22:22 +00:00
|
|
|
prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
};
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
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());
|
2020-11-09 20:55:56 +00:00
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
if row.ends_with_newline {
|
|
|
|
// Allow offset to go beyond the end of the paragraph
|
|
|
|
pcursor_it.offset += rcursor.column;
|
|
|
|
} else {
|
|
|
|
pcursor_it.offset += rcursor.column.at_most(row.char_count_excluding_newline());
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
2020-11-13 22:22:22 +00:00
|
|
|
return Cursor {
|
|
|
|
ccursor: ccursor_it,
|
|
|
|
rcursor,
|
|
|
|
pcursor: pcursor_it,
|
|
|
|
};
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
2020-11-13 00:23:36 +00:00
|
|
|
ccursor_it.index += row.char_count_including_newline();
|
|
|
|
if row.ends_with_newline {
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor_it.paragraph += 1;
|
|
|
|
pcursor_it.offset = 0;
|
|
|
|
} else {
|
2020-11-13 00:23:36 +00:00
|
|
|
pcursor_it.offset += row.char_count_including_newline();
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Cursor {
|
|
|
|
ccursor: ccursor_it,
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: self.end_rcursor(),
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor: pcursor_it,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: return identical cursor, or clamp?
|
|
|
|
pub fn from_pcursor(&self, pcursor: PCursor) -> Cursor {
|
2020-11-13 22:22:22 +00:00
|
|
|
let prefer_next_row = pcursor.prefer_next_row;
|
2020-11-09 20:55:56 +00:00
|
|
|
let mut ccursor_it = CCursor {
|
|
|
|
index: 0,
|
2020-11-13 22:22:22 +00:00
|
|
|
prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
};
|
|
|
|
let mut pcursor_it = PCursor {
|
|
|
|
paragraph: 0,
|
|
|
|
offset: 0,
|
2020-11-13 22:22:22 +00:00
|
|
|
prefer_next_row,
|
2020-11-09 20:55:56 +00:00
|
|
|
};
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
for (row_nr, row) in self.rows.iter().enumerate() {
|
2020-11-09 20:55:56 +00:00
|
|
|
if pcursor_it.paragraph == pcursor.paragraph {
|
2020-11-13 00:23:36 +00:00
|
|
|
// Right paragraph, but is it the right row in the paragraph?
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
if pcursor_it.offset <= pcursor.offset
|
2020-11-13 22:22:22 +00:00
|
|
|
&& (pcursor.offset <= pcursor_it.offset + row.char_count_excluding_newline()
|
|
|
|
|| row.ends_with_newline)
|
2020-11-09 20:55:56 +00:00
|
|
|
{
|
|
|
|
let column = pcursor.offset - pcursor_it.offset;
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
let select_next_row_instead = pcursor.prefer_next_row
|
2020-11-13 00:23:36 +00:00
|
|
|
&& !row.ends_with_newline
|
2020-11-13 22:22:22 +00:00
|
|
|
&& column >= row.char_count_excluding_newline();
|
|
|
|
|
|
|
|
if !select_next_row_instead {
|
|
|
|
ccursor_it.index += column.at_most(row.char_count_excluding_newline());
|
|
|
|
|
2020-11-09 20:55:56 +00:00
|
|
|
return Cursor {
|
|
|
|
ccursor: ccursor_it,
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor {
|
2020-11-13 22:22:22 +00:00
|
|
|
row: row_nr,
|
2020-11-09 20:55:56 +00:00
|
|
|
column,
|
|
|
|
},
|
|
|
|
pcursor,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
ccursor_it.index += row.char_count_including_newline();
|
|
|
|
if row.ends_with_newline {
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor_it.paragraph += 1;
|
|
|
|
pcursor_it.offset = 0;
|
|
|
|
} else {
|
2020-11-13 00:23:36 +00:00
|
|
|
pcursor_it.offset += row.char_count_including_newline();
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Cursor {
|
|
|
|
ccursor: ccursor_it,
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: self.end_rcursor(),
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// ## Cursor positions
|
|
|
|
impl Galley {
|
|
|
|
pub fn cursor_left_one_character(&self, cursor: &Cursor) -> Cursor {
|
|
|
|
if cursor.ccursor.index == 0 {
|
|
|
|
Default::default()
|
|
|
|
} else {
|
2020-11-13 22:22:22 +00:00
|
|
|
let ccursor = CCursor {
|
|
|
|
index: cursor.ccursor.index,
|
|
|
|
prefer_next_row: true, // default to this when navigating. It is more often useful to put cursor at the begging of a row than at the end.
|
|
|
|
};
|
|
|
|
self.from_ccursor(ccursor - 1)
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn cursor_right_one_character(&self, cursor: &Cursor) -> Cursor {
|
2020-11-13 22:22:22 +00:00
|
|
|
let ccursor = CCursor {
|
|
|
|
index: cursor.ccursor.index,
|
|
|
|
prefer_next_row: true, // default to this when navigating. It is more often useful to put cursor at the begging of a row than at the end.
|
|
|
|
};
|
|
|
|
self.from_ccursor(ccursor + 1)
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
pub fn cursor_up_one_row(&self, cursor: &Cursor) -> Cursor {
|
2020-11-13 00:23:36 +00:00
|
|
|
if cursor.rcursor.row == 0 {
|
2020-11-09 20:55:56 +00:00
|
|
|
Cursor::default()
|
|
|
|
} else {
|
2020-11-13 22:22:22 +00:00
|
|
|
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();
|
|
|
|
|
|
|
|
let new_rcursor = if cursor_is_beyond_end_of_current_row {
|
|
|
|
// keep same column
|
|
|
|
RCursor {
|
|
|
|
row: new_row,
|
|
|
|
column: cursor.rcursor.column,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// keep same X coord
|
2020-11-14 22:07:16 +00:00
|
|
|
let x = self.pos_from_cursor(cursor).center().x;
|
2020-11-13 22:22:22 +00:00
|
|
|
let column = if x > self.rows[new_row].max_x() {
|
|
|
|
// beyond the end of this row - keep same colum
|
|
|
|
cursor.rcursor.column
|
|
|
|
} else {
|
|
|
|
self.rows[new_row].char_at(x)
|
|
|
|
};
|
|
|
|
RCursor {
|
|
|
|
row: new_row,
|
|
|
|
column,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
self.from_rcursor(new_rcursor)
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
pub fn cursor_down_one_row(&self, cursor: &Cursor) -> Cursor {
|
2020-11-13 00:23:36 +00:00
|
|
|
if cursor.rcursor.row + 1 < self.rows.len() {
|
2020-11-13 22:22:22 +00:00
|
|
|
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();
|
|
|
|
|
|
|
|
let new_rcursor = if cursor_is_beyond_end_of_current_row {
|
|
|
|
// keep same column
|
|
|
|
RCursor {
|
|
|
|
row: new_row,
|
|
|
|
column: cursor.rcursor.column,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// keep same X coord
|
2020-11-14 22:07:16 +00:00
|
|
|
let x = self.pos_from_cursor(cursor).center().x;
|
2020-11-13 22:22:22 +00:00
|
|
|
let column = if x > self.rows[new_row].max_x() {
|
|
|
|
// beyond the end of the next row - keep same column
|
|
|
|
cursor.rcursor.column
|
|
|
|
} else {
|
|
|
|
self.rows[new_row].char_at(x)
|
|
|
|
};
|
|
|
|
RCursor {
|
|
|
|
row: new_row,
|
|
|
|
column,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.from_rcursor(new_rcursor)
|
2020-11-09 20:55:56 +00:00
|
|
|
} else {
|
|
|
|
self.end()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
pub fn cursor_begin_of_row(&self, cursor: &Cursor) -> Cursor {
|
2020-11-13 00:23:36 +00:00
|
|
|
self.from_rcursor(RCursor {
|
|
|
|
row: cursor.rcursor.row,
|
2020-11-09 20:55:56 +00:00
|
|
|
column: 0,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
pub fn cursor_end_of_row(&self, cursor: &Cursor) -> Cursor {
|
2020-11-13 00:23:36 +00:00
|
|
|
self.from_rcursor(RCursor {
|
|
|
|
row: cursor.rcursor.row,
|
|
|
|
column: self.rows[cursor.rcursor.row].char_count_excluding_newline(),
|
2020-11-09 20:55:56 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_text_layout() {
|
2020-11-15 13:21:21 +00:00
|
|
|
impl PartialEq for Cursor {
|
|
|
|
fn eq(&self, other: &Cursor) -> bool {
|
|
|
|
(self.ccursor, self.rcursor, self.pcursor)
|
|
|
|
== (other.ccursor, other.rcursor, other.pcursor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-09 20:55:56 +00:00
|
|
|
use crate::mutex::Mutex;
|
|
|
|
use crate::paint::{font::Font, *};
|
|
|
|
|
|
|
|
let pixels_per_point = 1.0;
|
|
|
|
let typeface_data = include_bytes!("../../fonts/ProggyClean.ttf");
|
|
|
|
let atlas = TextureAtlas::new(512, 16);
|
|
|
|
let atlas = std::sync::Arc::new(Mutex::new(atlas));
|
|
|
|
let font = Font::new(atlas, typeface_data, 13.0, pixels_per_point);
|
|
|
|
|
|
|
|
let galley = font.layout_multiline("".to_owned(), 1024.0);
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(galley.rows.len(), 1);
|
|
|
|
assert_eq!(galley.rows[0].ends_with_newline, false);
|
|
|
|
assert_eq!(galley.rows[0].x_offsets, vec![0.0]);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
let galley = font.layout_multiline("\n".to_owned(), 1024.0);
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(galley.rows.len(), 2);
|
|
|
|
assert_eq!(galley.rows[0].ends_with_newline, true);
|
|
|
|
assert_eq!(galley.rows[1].ends_with_newline, false);
|
|
|
|
assert_eq!(galley.rows[1].x_offsets, vec![0.0]);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
let galley = font.layout_multiline("\n\n".to_owned(), 1024.0);
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(galley.rows.len(), 3);
|
|
|
|
assert_eq!(galley.rows[0].ends_with_newline, true);
|
|
|
|
assert_eq!(galley.rows[1].ends_with_newline, true);
|
|
|
|
assert_eq!(galley.rows[2].ends_with_newline, false);
|
|
|
|
assert_eq!(galley.rows[2].x_offsets, vec![0.0]);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
let galley = font.layout_multiline(" ".to_owned(), 1024.0);
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(galley.rows.len(), 1);
|
|
|
|
assert_eq!(galley.rows[0].ends_with_newline, false);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
let galley = font.layout_multiline("One row!".to_owned(), 1024.0);
|
|
|
|
assert_eq!(galley.rows.len(), 1);
|
|
|
|
assert_eq!(galley.rows[0].ends_with_newline, false);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
let galley = font.layout_multiline("First row!\n".to_owned(), 1024.0);
|
|
|
|
assert_eq!(galley.rows.len(), 2);
|
|
|
|
assert_eq!(galley.rows[0].ends_with_newline, true);
|
|
|
|
assert_eq!(galley.rows[1].ends_with_newline, false);
|
|
|
|
assert_eq!(galley.rows[1].x_offsets, vec![0.0]);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
let galley = font.layout_multiline("line\nbreak".to_owned(), 10.0);
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(galley.rows.len(), 2);
|
|
|
|
assert_eq!(galley.rows[0].ends_with_newline, true);
|
|
|
|
assert_eq!(galley.rows[1].ends_with_newline, false);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
// Test wrapping:
|
2020-11-13 00:23:36 +00:00
|
|
|
let galley = font.layout_multiline("word wrap".to_owned(), 10.0);
|
|
|
|
assert_eq!(galley.rows.len(), 2);
|
|
|
|
assert_eq!(galley.rows[0].ends_with_newline, false);
|
|
|
|
assert_eq!(galley.rows[1].ends_with_newline, false);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
// Test wrapping:
|
2020-11-13 00:23:36 +00:00
|
|
|
let galley = font.layout_multiline("word wrap.\nNew paragraph.".to_owned(), 10.0);
|
|
|
|
assert_eq!(galley.rows.len(), 4);
|
|
|
|
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());
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(
|
2020-11-13 00:23:36 +00:00
|
|
|
galley.rows[1].char_count_including_newline(),
|
2020-11-09 20:55:56 +00:00
|
|
|
"wrap.\n".len()
|
|
|
|
);
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(galley.rows[2].ends_with_newline, false);
|
|
|
|
assert_eq!(galley.rows[3].ends_with_newline, false);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
let cursor = Cursor::default();
|
|
|
|
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
|
|
|
|
|
|
|
|
let cursor = galley.end();
|
|
|
|
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
|
|
|
|
assert_eq!(
|
|
|
|
cursor,
|
|
|
|
Cursor {
|
|
|
|
ccursor: CCursor::new(25),
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor { row: 3, column: 10 },
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor: PCursor {
|
|
|
|
paragraph: 1,
|
|
|
|
offset: 14,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
let cursor = galley.from_ccursor(CCursor::new(1));
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor.rcursor, RCursor { row: 0, column: 1 });
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(
|
|
|
|
cursor.pcursor,
|
|
|
|
PCursor {
|
|
|
|
paragraph: 0,
|
|
|
|
offset: 1,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
|
|
|
|
|
|
|
|
let cursor = galley.from_pcursor(PCursor {
|
|
|
|
paragraph: 1,
|
|
|
|
offset: 2,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
});
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor.rcursor, RCursor { row: 2, column: 2 });
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
|
|
|
|
|
|
|
|
let cursor = galley.from_pcursor(PCursor {
|
|
|
|
paragraph: 1,
|
|
|
|
offset: 6,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
});
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor.rcursor, RCursor { row: 3, column: 2 });
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(cursor, galley.from_ccursor(cursor.ccursor));
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(cursor, galley.from_pcursor(cursor.pcursor));
|
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
// On the border between two rows within the same paragraph:
|
|
|
|
let cursor = galley.from_rcursor(RCursor { row: 0, column: 5 });
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(
|
|
|
|
cursor,
|
|
|
|
Cursor {
|
|
|
|
ccursor: CCursor::new(5),
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor { row: 0, column: 5 },
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor: PCursor {
|
|
|
|
paragraph: 0,
|
|
|
|
offset: 5,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
|
2020-11-09 20:55:56 +00:00
|
|
|
|
2020-11-13 00:23:36 +00:00
|
|
|
let cursor = galley.from_rcursor(RCursor { row: 1, column: 0 });
|
2020-11-09 20:55:56 +00:00
|
|
|
assert_eq!(
|
|
|
|
cursor,
|
|
|
|
Cursor {
|
|
|
|
ccursor: CCursor::new(5),
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor { row: 1, column: 0 },
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor: PCursor {
|
|
|
|
paragraph: 0,
|
|
|
|
offset: 5,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2020-11-13 00:23:36 +00:00
|
|
|
assert_eq!(cursor, galley.from_rcursor(cursor.rcursor));
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
// Test cursor movement:
|
2020-11-13 00:23:36 +00:00
|
|
|
let galley = font.layout_multiline("word wrap.\nNew paragraph.".to_owned(), 10.0);
|
|
|
|
assert_eq!(galley.rows.len(), 4);
|
|
|
|
assert_eq!(galley.rows[0].ends_with_newline, false);
|
|
|
|
assert_eq!(galley.rows[1].ends_with_newline, true);
|
|
|
|
assert_eq!(galley.rows[2].ends_with_newline, false);
|
|
|
|
assert_eq!(galley.rows[3].ends_with_newline, false);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
let cursor = Cursor::default();
|
|
|
|
|
2020-11-13 22:22:22 +00:00
|
|
|
assert_eq!(galley.cursor_up_one_row(&cursor), cursor);
|
|
|
|
assert_eq!(galley.cursor_begin_of_row(&cursor), cursor);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
assert_eq!(
|
2020-11-13 22:22:22 +00:00
|
|
|
galley.cursor_end_of_row(&cursor),
|
2020-11-09 20:55:56 +00:00
|
|
|
Cursor {
|
|
|
|
ccursor: CCursor::new(5),
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor { row: 0, column: 5 },
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor: PCursor {
|
|
|
|
paragraph: 0,
|
|
|
|
offset: 5,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
2020-11-13 22:22:22 +00:00
|
|
|
galley.cursor_down_one_row(&cursor),
|
2020-11-09 20:55:56 +00:00
|
|
|
Cursor {
|
|
|
|
ccursor: CCursor::new(5),
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor { row: 1, column: 0 },
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor: PCursor {
|
|
|
|
paragraph: 0,
|
|
|
|
offset: 5,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
let cursor = Cursor::default();
|
|
|
|
assert_eq!(
|
2020-11-13 22:22:22 +00:00
|
|
|
galley.cursor_down_one_row(&galley.cursor_down_one_row(&cursor)),
|
2020-11-09 20:55:56 +00:00
|
|
|
Cursor {
|
|
|
|
ccursor: CCursor::new(11),
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor { row: 2, column: 0 },
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor: PCursor {
|
|
|
|
paragraph: 1,
|
|
|
|
offset: 0,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
let cursor = galley.end();
|
2020-11-13 22:22:22 +00:00
|
|
|
assert_eq!(galley.cursor_down_one_row(&cursor), cursor);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
let cursor = galley.end();
|
2020-11-13 22:22:22 +00:00
|
|
|
assert!(galley.cursor_up_one_row(&galley.end()) != cursor);
|
2020-11-09 20:55:56 +00:00
|
|
|
|
|
|
|
assert_eq!(
|
2020-11-13 22:22:22 +00:00
|
|
|
galley.cursor_up_one_row(&galley.end()),
|
2020-11-09 20:55:56 +00:00
|
|
|
Cursor {
|
|
|
|
ccursor: CCursor::new(15),
|
2020-11-13 00:23:36 +00:00
|
|
|
rcursor: RCursor { row: 2, column: 10 },
|
2020-11-09 20:55:56 +00:00
|
|
|
pcursor: PCursor {
|
|
|
|
paragraph: 1,
|
|
|
|
offset: 4,
|
2020-11-13 00:23:36 +00:00
|
|
|
prefer_next_row: false,
|
2020-11-09 20:55:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|