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:
parent
9ab00b8e50
commit
c84431e473
7 changed files with 299 additions and 319 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue