mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 21:00:03 -04:00
Merge branch 'master' into cache_galley_lines
This commit is contained in:
@@ -26,13 +26,6 @@ impl CCursor {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cursor> for CCursor {
|
||||
#[inline]
|
||||
fn from(c: Cursor) -> Self {
|
||||
c.ccursor
|
||||
}
|
||||
}
|
||||
|
||||
/// Two `CCursor`s are considered equal if they refer to the same character boundary,
|
||||
/// even if one prefers the start of the next row.
|
||||
impl PartialEq for CCursor {
|
||||
@@ -76,10 +69,12 @@ impl std::ops::SubAssign<usize> for CCursor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Row Cursor
|
||||
/// Row/column cursor.
|
||||
///
|
||||
/// This refers to rows and columns in layout terms--text wrapping creates multiple rows.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct RCursor {
|
||||
pub struct LayoutCursor {
|
||||
/// 0 is first row, and so on.
|
||||
/// Note that a single paragraph can span multiple rows.
|
||||
/// (a paragraph is text separated by `\n`).
|
||||
@@ -90,48 +85,3 @@ pub struct RCursor {
|
||||
/// When moving up/down it may again be within the next row.
|
||||
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.
|
||||
/// Note that a single paragraph can span multiple rows.
|
||||
/// (a paragraph is text separated by `\n`).
|
||||
pub paragraph: usize,
|
||||
|
||||
/// Character based (NOT bytes).
|
||||
/// 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.
|
||||
pub offset: usize,
|
||||
|
||||
/// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
|
||||
/// do we prefer the next row?
|
||||
/// This is *almost* always what you want, *except* for when
|
||||
/// explicitly clicking the end of a row or pressing the end key.
|
||||
pub prefer_next_row: bool,
|
||||
}
|
||||
|
||||
/// Two `PCursor`s are considered equal if they refer to the same character boundary,
|
||||
/// even if one prefers the start of the next row.
|
||||
impl PartialEq for PCursor {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> 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.
|
||||
/// 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.
|
||||
///
|
||||
/// The default cursor is the zero-cursor, to the first character.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Cursor {
|
||||
pub ccursor: CCursor,
|
||||
pub rcursor: RCursor,
|
||||
pub pcursor: PCursor,
|
||||
}
|
||||
|
||||
@@ -91,8 +91,14 @@ impl FontImpl {
|
||||
scale_in_pixels: f32,
|
||||
tweak: FontTweak,
|
||||
) -> Self {
|
||||
assert!(scale_in_pixels > 0.0);
|
||||
assert!(pixels_per_point > 0.0);
|
||||
assert!(
|
||||
scale_in_pixels > 0.0,
|
||||
"scale_in_pixels is smaller than 0, got: {scale_in_pixels:?}"
|
||||
);
|
||||
assert!(
|
||||
pixels_per_point > 0.0,
|
||||
"pixels_per_point must be greater than 0, got: {pixels_per_point:?}"
|
||||
);
|
||||
|
||||
use ab_glyph::{Font, ScaleFont};
|
||||
let scaled = ab_glyph_font.as_scaled(scale_in_pixels);
|
||||
@@ -264,7 +270,7 @@ impl FontImpl {
|
||||
}
|
||||
|
||||
fn allocate_glyph(&self, glyph_id: ab_glyph::GlyphId) -> GlyphInfo {
|
||||
assert!(glyph_id.0 != 0);
|
||||
assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0");
|
||||
use ab_glyph::{Font as _, ScaleFont};
|
||||
|
||||
let glyph = glyph_id.with_scale_and_position(
|
||||
|
||||
@@ -144,6 +144,12 @@ impl FontData {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for FontData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.font.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Extra scale and vertical tweak to apply to all text of a certain font.
|
||||
|
||||
@@ -543,7 +543,7 @@ fn halign_and_justify_row(
|
||||
(num_leading_spaces, row.glyphs.len() - num_trailing_spaces)
|
||||
};
|
||||
let num_glyphs_in_range = glyph_range.1 - glyph_range.0;
|
||||
assert!(num_glyphs_in_range > 0);
|
||||
assert!(num_glyphs_in_range > 0, "Should have at least one glyph");
|
||||
|
||||
let original_min_x = row.glyphs[glyph_range.0].logical_rect().min.x;
|
||||
let original_max_x = row.glyphs[glyph_range.1 - 1].logical_rect().max.x;
|
||||
@@ -777,10 +777,10 @@ fn add_row_backgrounds(job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut end_run = |start: Option<(Color32, Rect)>, stop_x: f32| {
|
||||
if let Some((color, start_rect)) = start {
|
||||
let mut end_run = |start: Option<(Color32, Rect, f32)>, stop_x: f32| {
|
||||
if let Some((color, start_rect, expand)) = start {
|
||||
let rect = Rect::from_min_max(start_rect.left_top(), pos2(stop_x, start_rect.bottom()));
|
||||
let rect = rect.expand(1.0); // looks better
|
||||
let rect = rect.expand(expand);
|
||||
mesh.add_colored_rect(rect, color);
|
||||
}
|
||||
};
|
||||
@@ -795,18 +795,19 @@ fn add_row_backgrounds(job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
|
||||
|
||||
if color == Color32::TRANSPARENT {
|
||||
end_run(run_start.take(), last_rect.right());
|
||||
} else if let Some((existing_color, start)) = run_start {
|
||||
} else if let Some((existing_color, start, expand)) = run_start {
|
||||
if existing_color == color
|
||||
&& start.top() == rect.top()
|
||||
&& start.bottom() == rect.bottom()
|
||||
&& format.expand_bg == expand
|
||||
{
|
||||
// continue the same background rectangle
|
||||
} else {
|
||||
end_run(run_start.take(), last_rect.right());
|
||||
run_start = Some((color, rect));
|
||||
run_start = Some((color, rect, format.expand_bg));
|
||||
}
|
||||
} else {
|
||||
run_start = Some((color, rect));
|
||||
run_start = Some((color, rect, format.expand_bg));
|
||||
}
|
||||
|
||||
last_rect = rect;
|
||||
@@ -916,7 +917,10 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke,
|
||||
} else {
|
||||
// Thin lines often lost, so this is a bad idea
|
||||
|
||||
assert_eq!(start.y, stop.y);
|
||||
assert_eq!(
|
||||
start.y, stop.y,
|
||||
"Horizontal line must be horizontal, but got: {start:?} -> {stop:?}"
|
||||
);
|
||||
|
||||
let min_y = point_scale.round_to_pixel(start.y - 0.5 * stroke.width);
|
||||
let max_y = point_scale.round_to_pixel(min_y + stroke.width);
|
||||
@@ -1012,22 +1016,22 @@ impl RowBreakCandidates {
|
||||
punctuation,
|
||||
any,
|
||||
} = self;
|
||||
if space.map_or(false, |s| s < index) {
|
||||
if space.is_some_and(|s| s < index) {
|
||||
*space = None;
|
||||
}
|
||||
if cjk.map_or(false, |s| s < index) {
|
||||
if cjk.is_some_and(|s| s < index) {
|
||||
*cjk = None;
|
||||
}
|
||||
if pre_cjk.map_or(false, |s| s < index) {
|
||||
if pre_cjk.is_some_and(|s| s < index) {
|
||||
*pre_cjk = None;
|
||||
}
|
||||
if dash.map_or(false, |s| s < index) {
|
||||
if dash.is_some_and(|s| s < index) {
|
||||
*dash = None;
|
||||
}
|
||||
if punctuation.map_or(false, |s| s < index) {
|
||||
if punctuation.is_some_and(|s| s < index) {
|
||||
*punctuation = None;
|
||||
}
|
||||
if any.map_or(false, |s| s < index) {
|
||||
if any.is_some_and(|s| s < index) {
|
||||
*any = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{
|
||||
cursor::{CCursor, Cursor, PCursor, RCursor},
|
||||
cursor::{CCursor, LayoutCursor},
|
||||
font::UvRect,
|
||||
};
|
||||
use crate::{Color32, FontId, Mesh, Stroke};
|
||||
@@ -272,6 +272,11 @@ pub struct TextFormat {
|
||||
|
||||
pub background: Color32,
|
||||
|
||||
/// Amount to expand background fill by.
|
||||
///
|
||||
/// Default: 1.0
|
||||
pub expand_bg: f32,
|
||||
|
||||
pub italics: bool,
|
||||
|
||||
pub underline: Stroke,
|
||||
@@ -299,6 +304,7 @@ impl Default for TextFormat {
|
||||
line_height: None,
|
||||
color: Color32::GRAY,
|
||||
background: Color32::TRANSPARENT,
|
||||
expand_bg: 1.0,
|
||||
italics: false,
|
||||
underline: Stroke::NONE,
|
||||
strikethrough: Stroke::NONE,
|
||||
@@ -316,6 +322,7 @@ impl std::hash::Hash for TextFormat {
|
||||
line_height,
|
||||
color,
|
||||
background,
|
||||
expand_bg,
|
||||
italics,
|
||||
underline,
|
||||
strikethrough,
|
||||
@@ -328,6 +335,7 @@ impl std::hash::Hash for TextFormat {
|
||||
}
|
||||
color.hash(state);
|
||||
background.hash(state);
|
||||
emath::OrderedFloat(*expand_bg).hash(state);
|
||||
italics.hash(state);
|
||||
underline.hash(state);
|
||||
strikethrough.hash(state);
|
||||
@@ -793,53 +801,18 @@ impl Galley {
|
||||
}
|
||||
|
||||
/// Returns a 0-width Rect.
|
||||
pub fn pos_from_cursor(&self, cursor: &Cursor) -> Rect {
|
||||
self.pos_from_pcursor(cursor.pcursor) // pcursor is what TextEdit stores
|
||||
fn pos_from_layout_cursor(&self, layout_cursor: &LayoutCursor) -> Rect {
|
||||
let Some(row) = self.rows.get(layout_cursor.row) else {
|
||||
return self.end_pos();
|
||||
};
|
||||
|
||||
let x = row.x_offset(layout_cursor.column);
|
||||
Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
|
||||
}
|
||||
|
||||
/// Returns a 0-width Rect.
|
||||
pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Rect {
|
||||
let mut it = PCursor::default();
|
||||
|
||||
for row in &self.rows {
|
||||
if it.paragraph == pcursor.paragraph {
|
||||
// Right paragraph, but is it the right row in the paragraph?
|
||||
|
||||
if it.offset <= pcursor.offset
|
||||
&& (pcursor.offset <= it.offset + row.char_count_excluding_newline()
|
||||
|| row.ends_with_newline)
|
||||
{
|
||||
let column = pcursor.offset - it.offset;
|
||||
|
||||
let select_next_row_instead = pcursor.prefer_next_row
|
||||
&& !row.ends_with_newline
|
||||
&& column >= row.char_count_excluding_newline();
|
||||
if !select_next_row_instead {
|
||||
let x = row.x_offset(column);
|
||||
return Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if row.ends_with_newline {
|
||||
it.paragraph += 1;
|
||||
it.offset = 0;
|
||||
} else {
|
||||
it.offset += row.char_count_including_newline();
|
||||
}
|
||||
}
|
||||
|
||||
self.end_pos()
|
||||
}
|
||||
|
||||
/// Returns a 0-width Rect.
|
||||
pub fn pos_from_ccursor(&self, ccursor: CCursor) -> Rect {
|
||||
self.pos_from_cursor(&self.from_ccursor(ccursor))
|
||||
}
|
||||
|
||||
/// Returns a 0-width Rect.
|
||||
pub fn pos_from_rcursor(&self, rcursor: RCursor) -> Rect {
|
||||
self.pos_from_cursor(&self.from_rcursor(rcursor))
|
||||
pub fn pos_from_cursor(&self, cursor: CCursor) -> Rect {
|
||||
self.pos_from_layout_cursor(&self.layout_from_cursor(cursor))
|
||||
}
|
||||
|
||||
/// Cursor at the given position within the galley.
|
||||
@@ -849,7 +822,7 @@ impl Galley {
|
||||
/// and a cursor below the galley is considered
|
||||
/// same as a cursor at the end.
|
||||
/// This allows implementing text-selection by dragging above/below the galley.
|
||||
pub fn cursor_from_pos(&self, pos: Vec2) -> Cursor {
|
||||
pub fn cursor_from_pos(&self, pos: Vec2) -> CCursor {
|
||||
if let Some(first_row) = self.rows.first() {
|
||||
if pos.y < first_row.min_y() {
|
||||
return self.begin();
|
||||
@@ -862,12 +835,11 @@ impl Galley {
|
||||
}
|
||||
|
||||
let mut best_y_dist = f32::INFINITY;
|
||||
let mut cursor = Cursor::default();
|
||||
let mut cursor = CCursor::default();
|
||||
|
||||
let mut ccursor_index = 0;
|
||||
let mut pcursor_it = PCursor::default();
|
||||
|
||||
for (row_nr, row) in self.rows.iter().enumerate() {
|
||||
for row in &self.rows {
|
||||
let min_y = row.min_y();
|
||||
let max_y = row.max_y();
|
||||
|
||||
@@ -878,20 +850,9 @@ impl Galley {
|
||||
// char_at is `Row` not `PlacedRow` relative which means we have to subtract the pos.
|
||||
let column = row.char_at(pos.x - row.pos.x);
|
||||
let prefer_next_row = column < row.char_count_excluding_newline();
|
||||
cursor = Cursor {
|
||||
ccursor: CCursor {
|
||||
index: ccursor_index + column,
|
||||
prefer_next_row,
|
||||
},
|
||||
rcursor: RCursor {
|
||||
row: row_nr,
|
||||
column,
|
||||
},
|
||||
pcursor: PCursor {
|
||||
paragraph: pcursor_it.paragraph,
|
||||
offset: pcursor_it.offset + column,
|
||||
prefer_next_row,
|
||||
},
|
||||
cursor = CCursor {
|
||||
index: ccursor_index + column,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
if is_pos_within_row {
|
||||
@@ -899,12 +860,6 @@ impl Galley {
|
||||
}
|
||||
}
|
||||
ccursor_index += row.char_count_including_newline();
|
||||
if row.ends_with_newline {
|
||||
pcursor_it.paragraph += 1;
|
||||
pcursor_it.offset = 0;
|
||||
} else {
|
||||
pcursor_it.offset += row.char_count_including_newline();
|
||||
}
|
||||
}
|
||||
|
||||
cursor
|
||||
@@ -915,15 +870,15 @@ impl Galley {
|
||||
impl Galley {
|
||||
/// Cursor to the first character.
|
||||
///
|
||||
/// This is the same as [`Cursor::default`].
|
||||
/// This is the same as [`CCursor::default`].
|
||||
#[inline]
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn begin(&self) -> Cursor {
|
||||
Cursor::default()
|
||||
pub fn begin(&self) -> CCursor {
|
||||
CCursor::default()
|
||||
}
|
||||
|
||||
/// Cursor to one-past last character.
|
||||
pub fn end(&self) -> Cursor {
|
||||
pub fn end(&self) -> CCursor {
|
||||
if self.rows.is_empty() {
|
||||
return Default::default();
|
||||
}
|
||||
@@ -931,31 +886,47 @@ impl Galley {
|
||||
index: 0,
|
||||
prefer_next_row: true,
|
||||
};
|
||||
let mut pcursor = PCursor {
|
||||
paragraph: 0,
|
||||
offset: 0,
|
||||
prefer_next_row: true,
|
||||
};
|
||||
for row in &self.rows {
|
||||
let row_char_count = row.char_count_including_newline();
|
||||
ccursor.index += row_char_count;
|
||||
if row.ends_with_newline {
|
||||
pcursor.paragraph += 1;
|
||||
pcursor.offset = 0;
|
||||
} else {
|
||||
pcursor.offset += row_char_count;
|
||||
}
|
||||
}
|
||||
Cursor {
|
||||
ccursor,
|
||||
rcursor: self.end_rcursor(),
|
||||
pcursor,
|
||||
}
|
||||
ccursor
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Cursor conversions
|
||||
impl Galley {
|
||||
// The returned cursor is clamped.
|
||||
pub fn layout_from_cursor(&self, cursor: CCursor) -> LayoutCursor {
|
||||
let prefer_next_row = cursor.prefer_next_row;
|
||||
let mut ccursor_it = CCursor {
|
||||
index: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
for (row_nr, row) in self.rows.iter().enumerate() {
|
||||
let row_char_count = row.char_count_excluding_newline();
|
||||
|
||||
if ccursor_it.index <= cursor.index && cursor.index <= ccursor_it.index + row_char_count
|
||||
{
|
||||
let column = cursor.index - ccursor_it.index;
|
||||
|
||||
let select_next_row_instead = prefer_next_row
|
||||
&& !row.ends_with_newline
|
||||
&& column >= row.char_count_excluding_newline();
|
||||
if !select_next_row_instead {
|
||||
return LayoutCursor {
|
||||
row: row_nr,
|
||||
column,
|
||||
};
|
||||
}
|
||||
}
|
||||
ccursor_it.index += row.char_count_including_newline();
|
||||
}
|
||||
debug_assert!(ccursor_it == self.end(), "Cursor out of bounds");
|
||||
|
||||
pub fn end_rcursor(&self) -> RCursor {
|
||||
if let Some(last_row) = self.rows.last() {
|
||||
RCursor {
|
||||
LayoutCursor {
|
||||
row: self.rows.len() - 1,
|
||||
column: last_row.char_count_including_newline(),
|
||||
}
|
||||
@@ -963,270 +934,156 @@ impl Galley {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Cursor conversions
|
||||
impl Galley {
|
||||
// The returned cursor is clamped.
|
||||
pub fn from_ccursor(&self, ccursor: CCursor) -> Cursor {
|
||||
let prefer_next_row = ccursor.prefer_next_row;
|
||||
let mut ccursor_it = CCursor {
|
||||
index: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
let mut pcursor_it = PCursor {
|
||||
paragraph: 0,
|
||||
offset: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
for (row_nr, row) in self.rows.iter().enumerate() {
|
||||
let row_char_count = row.char_count_excluding_newline();
|
||||
|
||||
if ccursor_it.index <= ccursor.index
|
||||
&& ccursor.index <= ccursor_it.index + row_char_count
|
||||
{
|
||||
let column = ccursor.index - ccursor_it.index;
|
||||
|
||||
let select_next_row_instead = prefer_next_row
|
||||
&& !row.ends_with_newline
|
||||
&& column >= row.char_count_excluding_newline();
|
||||
if !select_next_row_instead {
|
||||
pcursor_it.offset += column;
|
||||
return Cursor {
|
||||
ccursor,
|
||||
rcursor: RCursor {
|
||||
row: row_nr,
|
||||
column,
|
||||
},
|
||||
pcursor: pcursor_it,
|
||||
};
|
||||
}
|
||||
}
|
||||
ccursor_it.index += row.char_count_including_newline();
|
||||
if row.ends_with_newline {
|
||||
pcursor_it.paragraph += 1;
|
||||
pcursor_it.offset = 0;
|
||||
} else {
|
||||
pcursor_it.offset += row.char_count_including_newline();
|
||||
}
|
||||
}
|
||||
debug_assert!(ccursor_it == self.end().ccursor);
|
||||
Cursor {
|
||||
ccursor: ccursor_it, // clamp
|
||||
rcursor: self.end_rcursor(),
|
||||
pcursor: pcursor_it,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_rcursor(&self, rcursor: RCursor) -> Cursor {
|
||||
if rcursor.row >= self.rows.len() {
|
||||
fn cursor_from_layout(&self, layout_cursor: LayoutCursor) -> CCursor {
|
||||
if layout_cursor.row >= self.rows.len() {
|
||||
return self.end();
|
||||
}
|
||||
|
||||
let prefer_next_row =
|
||||
rcursor.column < self.rows[rcursor.row].char_count_excluding_newline();
|
||||
let mut ccursor_it = CCursor {
|
||||
layout_cursor.column < self.rows[layout_cursor.row].char_count_excluding_newline();
|
||||
let mut cursor_it = CCursor {
|
||||
index: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
let mut pcursor_it = PCursor {
|
||||
paragraph: 0,
|
||||
offset: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
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());
|
||||
if row_nr == layout_cursor.row {
|
||||
cursor_it.index += layout_cursor
|
||||
.column
|
||||
.at_most(row.char_count_excluding_newline());
|
||||
|
||||
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());
|
||||
}
|
||||
return Cursor {
|
||||
ccursor: ccursor_it,
|
||||
rcursor,
|
||||
pcursor: pcursor_it,
|
||||
};
|
||||
}
|
||||
ccursor_it.index += row.char_count_including_newline();
|
||||
if row.ends_with_newline {
|
||||
pcursor_it.paragraph += 1;
|
||||
pcursor_it.offset = 0;
|
||||
} else {
|
||||
pcursor_it.offset += row.char_count_including_newline();
|
||||
return cursor_it;
|
||||
}
|
||||
cursor_it.index += row.char_count_including_newline();
|
||||
}
|
||||
Cursor {
|
||||
ccursor: ccursor_it,
|
||||
rcursor: self.end_rcursor(),
|
||||
pcursor: pcursor_it,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(emilk): return identical cursor, or clamp?
|
||||
pub fn from_pcursor(&self, pcursor: PCursor) -> Cursor {
|
||||
let prefer_next_row = pcursor.prefer_next_row;
|
||||
let mut ccursor_it = CCursor {
|
||||
index: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
let mut pcursor_it = PCursor {
|
||||
paragraph: 0,
|
||||
offset: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
for (row_nr, row) in self.rows.iter().enumerate() {
|
||||
if pcursor_it.paragraph == pcursor.paragraph {
|
||||
// Right paragraph, but is it the right row in the paragraph?
|
||||
|
||||
if pcursor_it.offset <= pcursor.offset
|
||||
&& (pcursor.offset <= pcursor_it.offset + row.char_count_excluding_newline()
|
||||
|| row.ends_with_newline)
|
||||
{
|
||||
let column = pcursor.offset - pcursor_it.offset;
|
||||
|
||||
let select_next_row_instead = pcursor.prefer_next_row
|
||||
&& !row.ends_with_newline
|
||||
&& column >= row.char_count_excluding_newline();
|
||||
|
||||
if !select_next_row_instead {
|
||||
ccursor_it.index += column.at_most(row.char_count_excluding_newline());
|
||||
|
||||
return Cursor {
|
||||
ccursor: ccursor_it,
|
||||
rcursor: RCursor {
|
||||
row: row_nr,
|
||||
column,
|
||||
},
|
||||
pcursor,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ccursor_it.index += row.char_count_including_newline();
|
||||
if row.ends_with_newline {
|
||||
pcursor_it.paragraph += 1;
|
||||
pcursor_it.offset = 0;
|
||||
} else {
|
||||
pcursor_it.offset += row.char_count_including_newline();
|
||||
}
|
||||
}
|
||||
Cursor {
|
||||
ccursor: ccursor_it,
|
||||
rcursor: self.end_rcursor(),
|
||||
pcursor,
|
||||
}
|
||||
cursor_it
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Cursor positions
|
||||
impl Galley {
|
||||
pub fn cursor_left_one_character(&self, cursor: &Cursor) -> Cursor {
|
||||
if cursor.ccursor.index == 0 {
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn cursor_left_one_character(&self, cursor: &CCursor) -> CCursor {
|
||||
if cursor.index == 0 {
|
||||
Default::default()
|
||||
} else {
|
||||
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)
|
||||
CCursor {
|
||||
index: cursor.index - 1,
|
||||
prefer_next_row: true, // default to this when navigating. It is more often useful to put cursor at the beginning of a row than at the end.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_right_one_character(&self, cursor: &Cursor) -> Cursor {
|
||||
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)
|
||||
pub fn cursor_right_one_character(&self, cursor: &CCursor) -> CCursor {
|
||||
CCursor {
|
||||
index: (cursor.index + 1).min(self.end().index),
|
||||
prefer_next_row: true, // default to this when navigating. It is more often useful to put cursor at the beginning of a row than at the end.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_up_one_row(&self, cursor: &Cursor) -> Cursor {
|
||||
if cursor.rcursor.row == 0 {
|
||||
Cursor::default()
|
||||
pub fn cursor_up_one_row(
|
||||
&self,
|
||||
cursor: &CCursor,
|
||||
h_pos: Option<f32>,
|
||||
) -> (CCursor, Option<f32>) {
|
||||
let layout_cursor = self.layout_from_cursor(*cursor);
|
||||
let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
|
||||
if layout_cursor.row == 0 {
|
||||
(CCursor::default(), None)
|
||||
} else {
|
||||
let new_row = cursor.rcursor.row - 1;
|
||||
let new_row = layout_cursor.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 {
|
||||
let new_layout_cursor = {
|
||||
// keep same X coord
|
||||
let x = self.pos_from_cursor(cursor).center().x;
|
||||
let row = &self.rows[new_row];
|
||||
let column = if x > row.rect().right() {
|
||||
// beyond the end of this row - keep same column
|
||||
cursor.rcursor.column
|
||||
} else {
|
||||
row.char_at(x)
|
||||
};
|
||||
RCursor {
|
||||
let column = self.rows[new_row].char_at(h_pos);
|
||||
LayoutCursor {
|
||||
row: new_row,
|
||||
column,
|
||||
}
|
||||
};
|
||||
self.from_rcursor(new_rcursor)
|
||||
(self.cursor_from_layout(new_layout_cursor), Some(h_pos))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_down_one_row(&self, cursor: &Cursor) -> Cursor {
|
||||
if cursor.rcursor.row + 1 < self.rows.len() {
|
||||
let new_row = cursor.rcursor.row + 1;
|
||||
pub fn cursor_down_one_row(
|
||||
&self,
|
||||
cursor: &CCursor,
|
||||
h_pos: Option<f32>,
|
||||
) -> (CCursor, Option<f32>) {
|
||||
let layout_cursor = self.layout_from_cursor(*cursor);
|
||||
let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
|
||||
if layout_cursor.row + 1 < self.rows.len() {
|
||||
let new_row = layout_cursor.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 {
|
||||
let new_layout_cursor = {
|
||||
// keep same X coord
|
||||
let x = self.pos_from_cursor(cursor).center().x;
|
||||
let row = &self.rows[new_row];
|
||||
let column = if x > row.rect().right() {
|
||||
// beyond the end of the next row - keep same column
|
||||
cursor.rcursor.column
|
||||
} else {
|
||||
row.char_at(x)
|
||||
};
|
||||
RCursor {
|
||||
let column = self.rows[new_row].char_at(h_pos);
|
||||
LayoutCursor {
|
||||
row: new_row,
|
||||
column,
|
||||
}
|
||||
};
|
||||
|
||||
self.from_rcursor(new_rcursor)
|
||||
(self.cursor_from_layout(new_layout_cursor), Some(h_pos))
|
||||
} else {
|
||||
self.end()
|
||||
(self.end(), None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_begin_of_row(&self, cursor: &Cursor) -> Cursor {
|
||||
self.from_rcursor(RCursor {
|
||||
row: cursor.rcursor.row,
|
||||
pub fn cursor_begin_of_row(&self, cursor: &CCursor) -> CCursor {
|
||||
let layout_cursor = self.layout_from_cursor(*cursor);
|
||||
self.cursor_from_layout(LayoutCursor {
|
||||
row: layout_cursor.row,
|
||||
column: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cursor_end_of_row(&self, cursor: &Cursor) -> Cursor {
|
||||
self.from_rcursor(RCursor {
|
||||
row: cursor.rcursor.row,
|
||||
column: self.rows[cursor.rcursor.row].char_count_excluding_newline(),
|
||||
pub fn cursor_end_of_row(&self, cursor: &CCursor) -> CCursor {
|
||||
let layout_cursor = self.layout_from_cursor(*cursor);
|
||||
self.cursor_from_layout(LayoutCursor {
|
||||
row: layout_cursor.row,
|
||||
column: self.rows[layout_cursor.row].char_count_excluding_newline(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cursor_begin_of_paragraph(&self, cursor: &CCursor) -> CCursor {
|
||||
let mut layout_cursor = self.layout_from_cursor(*cursor);
|
||||
layout_cursor.column = 0;
|
||||
|
||||
loop {
|
||||
let prev_row = layout_cursor
|
||||
.row
|
||||
.checked_sub(1)
|
||||
.and_then(|row| self.rows.get(row));
|
||||
|
||||
let Some(prev_row) = prev_row else {
|
||||
// This is the first row
|
||||
break;
|
||||
};
|
||||
|
||||
if prev_row.ends_with_newline {
|
||||
break;
|
||||
}
|
||||
|
||||
layout_cursor.row -= 1;
|
||||
}
|
||||
|
||||
self.cursor_from_layout(layout_cursor)
|
||||
}
|
||||
|
||||
pub fn cursor_end_of_paragraph(&self, cursor: &CCursor) -> CCursor {
|
||||
let mut layout_cursor = self.layout_from_cursor(*cursor);
|
||||
loop {
|
||||
let row = &self.rows[layout_cursor.row];
|
||||
if row.ends_with_newline || layout_cursor.row == self.rows.len() - 1 {
|
||||
layout_cursor.column = row.char_count_excluding_newline();
|
||||
break;
|
||||
}
|
||||
|
||||
layout_cursor.row += 1;
|
||||
}
|
||||
|
||||
self.cursor_from_layout(layout_cursor)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user