Move cursor motions to new Motion enum, move handling to Buffer
This commit is contained in:
parent
6528e9f804
commit
018a2e9d2a
10 changed files with 567 additions and 505 deletions
393
src/buffer.rs
393
src/buffer.rs
|
|
@ -6,102 +6,11 @@ use core::{cmp, fmt};
|
|||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{
|
||||
Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color, FontSystem,
|
||||
LayoutGlyph, LayoutLine, ShapeBuffer, ShapeLine, Shaping, Wrap,
|
||||
Affinity, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color, Cursor,
|
||||
FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, Motion, ShapeBuffer, ShapeLine, Shaping,
|
||||
Wrap,
|
||||
};
|
||||
|
||||
/// Current cursor location
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct Cursor {
|
||||
/// Text line the cursor is on
|
||||
pub line: usize,
|
||||
/// First-byte-index of glyph at cursor (will insert behind this glyph)
|
||||
pub index: usize,
|
||||
/// Whether to associate the cursor with the run before it or the run after it if placed at the
|
||||
/// boundary between two runs
|
||||
pub affinity: Affinity,
|
||||
/// Cursor color
|
||||
pub color: Option<Color>,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
/// Create a new cursor
|
||||
pub const fn new(line: usize, index: usize) -> Self {
|
||||
Self::new_with_affinity(line, index, Affinity::Before)
|
||||
}
|
||||
|
||||
/// Create a new cursor, specifying the affinity
|
||||
pub const fn new_with_affinity(line: usize, index: usize, affinity: Affinity) -> Self {
|
||||
Self {
|
||||
line,
|
||||
index,
|
||||
affinity,
|
||||
color: None,
|
||||
}
|
||||
}
|
||||
/// Create a new cursor, specifying the color
|
||||
pub const fn new_with_color(line: usize, index: usize, color: Color) -> Self {
|
||||
Self {
|
||||
line,
|
||||
index,
|
||||
affinity: Affinity::Before,
|
||||
color: Some(color),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to associate cursors placed at a boundary between runs with the run before or after it.
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum Affinity {
|
||||
#[default]
|
||||
Before,
|
||||
After,
|
||||
}
|
||||
|
||||
impl Affinity {
|
||||
pub fn before(&self) -> bool {
|
||||
*self == Self::Before
|
||||
}
|
||||
|
||||
pub fn after(&self) -> bool {
|
||||
*self == Self::After
|
||||
}
|
||||
|
||||
pub fn from_before(before: bool) -> Self {
|
||||
if before {
|
||||
Self::Before
|
||||
} else {
|
||||
Self::After
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_after(after: bool) -> Self {
|
||||
if after {
|
||||
Self::After
|
||||
} else {
|
||||
Self::Before
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The position of a cursor within a [`Buffer`].
|
||||
#[derive(Debug)]
|
||||
pub struct LayoutCursor {
|
||||
pub line: usize,
|
||||
pub layout: usize,
|
||||
pub glyph: usize,
|
||||
}
|
||||
|
||||
impl LayoutCursor {
|
||||
pub fn new(line: usize, layout: usize, glyph: usize) -> Self {
|
||||
Self {
|
||||
line,
|
||||
layout,
|
||||
glyph,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A line of visible text for rendering
|
||||
#[derive(Debug)]
|
||||
pub struct LayoutRun<'a> {
|
||||
|
|
@ -848,6 +757,297 @@ impl Buffer {
|
|||
new_cursor_opt
|
||||
}
|
||||
|
||||
/// Apply a [`Motion`] to a [`Cursor`]
|
||||
pub fn cursor_motion(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
mut cursor: Cursor,
|
||||
motion: Motion,
|
||||
) -> Option<Cursor> {
|
||||
match motion {
|
||||
Motion::LayoutCursor(layout_cursor) => {
|
||||
let layout = self.line_layout(font_system, layout_cursor.line)?;
|
||||
|
||||
let layout_line = match layout.get(layout_cursor.layout) {
|
||||
Some(some) => some,
|
||||
None => match layout.last() {
|
||||
Some(some) => some,
|
||||
None => return None,
|
||||
},
|
||||
};
|
||||
|
||||
let (new_index, new_affinity) = match layout_line.glyphs.get(layout_cursor.glyph) {
|
||||
Some(glyph) => (glyph.start, Affinity::After),
|
||||
None => match layout_line.glyphs.last() {
|
||||
Some(glyph) => (glyph.end, Affinity::Before),
|
||||
//TODO: is this correct?
|
||||
None => (0, Affinity::After),
|
||||
},
|
||||
};
|
||||
|
||||
if cursor.line != layout_cursor.line
|
||||
|| cursor.index != new_index
|
||||
|| cursor.affinity != new_affinity
|
||||
{
|
||||
cursor.line = layout_cursor.line;
|
||||
cursor.index = new_index;
|
||||
cursor.affinity = new_affinity;
|
||||
}
|
||||
}
|
||||
Motion::Previous => {
|
||||
let line = self.lines.get(cursor.line)?;
|
||||
if cursor.index > 0 {
|
||||
// Find previous character index
|
||||
let mut prev_index = 0;
|
||||
for (i, _) in line.text().grapheme_indices(true) {
|
||||
if i < cursor.index {
|
||||
prev_index = i;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cursor.index = prev_index;
|
||||
cursor.affinity = Affinity::After;
|
||||
} else if cursor.line > 0 {
|
||||
cursor.line -= 1;
|
||||
cursor.index = self.lines.get(cursor.line)?.text().len();
|
||||
cursor.affinity = Affinity::After;
|
||||
}
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::Next => {
|
||||
let line = self.lines.get(cursor.line)?;
|
||||
if cursor.index < line.text().len() {
|
||||
for (i, c) in line.text().grapheme_indices(true) {
|
||||
if i == cursor.index {
|
||||
cursor.index += c.len();
|
||||
cursor.affinity = Affinity::Before;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if cursor.line + 1 < self.lines.len() {
|
||||
cursor.line += 1;
|
||||
cursor.index = 0;
|
||||
cursor.affinity = Affinity::Before;
|
||||
}
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::Left => {
|
||||
let rtl_opt = self
|
||||
.line_shape(font_system, cursor.line)
|
||||
.map(|shape| shape.rtl);
|
||||
if let Some(rtl) = rtl_opt {
|
||||
if rtl {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::Next)?;
|
||||
} else {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::Previous)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Motion::Right => {
|
||||
let rtl_opt = self
|
||||
.line_shape(font_system, cursor.line)
|
||||
.map(|shape| shape.rtl);
|
||||
if let Some(rtl) = rtl_opt {
|
||||
if rtl {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::Previous)?;
|
||||
} else {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::Next)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Motion::Up => {
|
||||
//TODO: make this preserve X as best as possible!
|
||||
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||
|
||||
if cursor.x_opt.is_none() {
|
||||
cursor.x_opt = Some(
|
||||
layout_cursor.glyph as i32, //TODO: glyph x position
|
||||
);
|
||||
}
|
||||
|
||||
if layout_cursor.layout > 0 {
|
||||
layout_cursor.layout -= 1;
|
||||
} else if layout_cursor.line > 0 {
|
||||
layout_cursor.line -= 1;
|
||||
layout_cursor.layout = usize::max_value();
|
||||
}
|
||||
|
||||
if let Some(cursor_x) = cursor.x_opt {
|
||||
layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
|
||||
}
|
||||
|
||||
cursor =
|
||||
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||
}
|
||||
Motion::Down => {
|
||||
//TODO: make this preserve X as best as possible!
|
||||
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||
|
||||
let layout_len = self
|
||||
.line_layout(font_system, layout_cursor.line)
|
||||
.expect("layout not found")
|
||||
.len();
|
||||
|
||||
if cursor.x_opt.is_none() {
|
||||
cursor.x_opt = Some(
|
||||
layout_cursor.glyph as i32, //TODO: glyph x position
|
||||
);
|
||||
}
|
||||
|
||||
if layout_cursor.layout + 1 < layout_len {
|
||||
layout_cursor.layout += 1;
|
||||
} else if layout_cursor.line + 1 < self.lines.len() {
|
||||
layout_cursor.line += 1;
|
||||
layout_cursor.layout = 0;
|
||||
}
|
||||
|
||||
if let Some(cursor_x) = cursor.x_opt {
|
||||
layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
|
||||
}
|
||||
|
||||
cursor =
|
||||
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||
}
|
||||
Motion::Home => {
|
||||
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||
layout_cursor.glyph = 0;
|
||||
cursor =
|
||||
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::SoftHome => {
|
||||
let line = self.lines.get(cursor.line)?;
|
||||
cursor.index = line
|
||||
.text()
|
||||
.char_indices()
|
||||
.filter_map(|(i, c)| if c.is_whitespace() { None } else { Some(i) })
|
||||
.next()
|
||||
.unwrap_or(0);
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::End => {
|
||||
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||
layout_cursor.glyph = usize::max_value();
|
||||
cursor =
|
||||
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::ParagraphStart => {
|
||||
cursor.index = 0;
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::ParagraphEnd => {
|
||||
cursor.index = self.lines.get(cursor.line)?.text().len();
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::PageUp => {
|
||||
cursor = self.cursor_motion(
|
||||
font_system,
|
||||
cursor,
|
||||
Motion::Vertical(-self.size().1 as i32),
|
||||
)?;
|
||||
}
|
||||
Motion::PageDown => {
|
||||
cursor = self.cursor_motion(
|
||||
font_system,
|
||||
cursor,
|
||||
Motion::Vertical(self.size().1 as i32),
|
||||
)?;
|
||||
}
|
||||
Motion::Vertical(px) => {
|
||||
// TODO more efficient
|
||||
let lines = px / self.metrics().line_height as i32;
|
||||
match lines.cmp(&0) {
|
||||
cmp::Ordering::Less => {
|
||||
for _ in 0..-lines {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::Up)?;
|
||||
}
|
||||
}
|
||||
cmp::Ordering::Greater => {
|
||||
for _ in 0..lines {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::Down)?;
|
||||
}
|
||||
}
|
||||
cmp::Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
Motion::PreviousWord => {
|
||||
let line = self.lines.get(cursor.line)?;
|
||||
if cursor.index > 0 {
|
||||
cursor.index = line
|
||||
.text()
|
||||
.unicode_word_indices()
|
||||
.rev()
|
||||
.map(|(i, _)| i)
|
||||
.find(|&i| i < cursor.index)
|
||||
.unwrap_or(0);
|
||||
} else if cursor.line > 0 {
|
||||
cursor.line -= 1;
|
||||
cursor.index = self.lines.get(cursor.line)?.text().len();
|
||||
}
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::NextWord => {
|
||||
let line = self.lines.get(cursor.line)?;
|
||||
if cursor.index < line.text().len() {
|
||||
cursor.index = line
|
||||
.text()
|
||||
.unicode_word_indices()
|
||||
.map(|(i, word)| i + word.len())
|
||||
.find(|&i| i > cursor.index)
|
||||
.unwrap_or(line.text().len());
|
||||
} else if cursor.line + 1 < self.lines.len() {
|
||||
cursor.line += 1;
|
||||
cursor.index = 0;
|
||||
}
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::LeftWord => {
|
||||
let rtl_opt = self
|
||||
.line_shape(font_system, cursor.line)
|
||||
.map(|shape| shape.rtl);
|
||||
if let Some(rtl) = rtl_opt {
|
||||
if rtl {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::NextWord)?;
|
||||
} else {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::PreviousWord)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Motion::RightWord => {
|
||||
let rtl_opt = self
|
||||
.line_shape(font_system, cursor.line)
|
||||
.map(|shape| shape.rtl);
|
||||
if let Some(rtl) = rtl_opt {
|
||||
if rtl {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::PreviousWord)?;
|
||||
} else {
|
||||
cursor = self.cursor_motion(font_system, cursor, Motion::NextWord)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Motion::BufferStart => {
|
||||
cursor.line = 0;
|
||||
cursor.index = 0;
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::BufferEnd => {
|
||||
cursor.line = self.lines.len() - 1;
|
||||
cursor.index = self.lines.get(cursor.line)?.text().len();
|
||||
cursor.x_opt = None;
|
||||
}
|
||||
Motion::GotoLine(line) => {
|
||||
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||
layout_cursor.line = line;
|
||||
cursor =
|
||||
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||
}
|
||||
}
|
||||
Some(cursor)
|
||||
}
|
||||
|
||||
/// Draw the buffer
|
||||
#[cfg(feature = "swash")]
|
||||
pub fn draw<F>(
|
||||
|
|
@ -972,6 +1172,11 @@ impl<'a> BorrowedWithFontSystem<'a, Buffer> {
|
|||
.set_rich_text(self.font_system, spans, default_attrs, shaping);
|
||||
}
|
||||
|
||||
/// Apply a [`Motion`] to a [`Cursor`]
|
||||
pub fn cursor_motion(&mut self, cursor: Cursor, motion: Motion) -> Option<Cursor> {
|
||||
self.inner.cursor_motion(self.font_system, cursor, motion)
|
||||
}
|
||||
|
||||
/// Draw the buffer
|
||||
#[cfg(feature = "swash")]
|
||||
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue