Add cursor affinity
This commit is contained in:
parent
e00109d77f
commit
da842ec10d
2 changed files with 66 additions and 33 deletions
|
|
@ -19,12 +19,24 @@ pub struct Cursor {
|
||||||
pub line: usize,
|
pub line: usize,
|
||||||
/// First-byte-index of glyph at cursor (will insert behind this glyph)
|
/// First-byte-index of glyph at cursor (will insert behind this glyph)
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
|
/// Whether to associate the cursor with the run before it (false) or the run after it (true)
|
||||||
|
/// if placed at the boundary between two runs
|
||||||
|
pub affinity: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cursor {
|
impl Cursor {
|
||||||
/// Create a new cursor
|
/// Create a new cursor
|
||||||
pub const fn new(line: usize, index: usize) -> Self {
|
pub const fn new(line: usize, index: usize) -> Self {
|
||||||
Self { line, index }
|
Self::new_affinity(line, index, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new cursor, specifying the affinity
|
||||||
|
pub const fn new_affinity(line: usize, index: usize, affinity: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
line,
|
||||||
|
index,
|
||||||
|
affinity,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +73,7 @@ pub struct LayoutRun<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LayoutRun<'a> {
|
impl<'a> LayoutRun<'a> {
|
||||||
/// Return the pixel span Some((x_start, x_width)) of the highlighted area between cursor_start
|
/// Return the pixel span Some((x_left, x_width)) of the highlighted area between cursor_start
|
||||||
/// and cursor_end within this run, or None if the cursor range does not intersect this run.
|
/// and cursor_end within this run, or None if the cursor range does not intersect this run.
|
||||||
/// This may return widths of zero if cursor_start == cursor_end, if the run is empty, or if the
|
/// This may return widths of zero if cursor_start == cursor_end, if the run is empty, or if the
|
||||||
/// region's left start boundary is the same as the cursor's end boundary or vice versa.
|
/// region's left start boundary is the same as the cursor's end boundary or vice versa.
|
||||||
|
|
@ -71,28 +83,20 @@ impl<'a> LayoutRun<'a> {
|
||||||
let rtl_factor = if self.rtl { 1. } else { 0. };
|
let rtl_factor = if self.rtl { 1. } else { 0. };
|
||||||
let ltr_factor = 1. - rtl_factor;
|
let ltr_factor = 1. - rtl_factor;
|
||||||
for glyph in self.glyphs.iter() {
|
for glyph in self.glyphs.iter() {
|
||||||
let cursor = Cursor::new(self.line_i, glyph.start);
|
let cursor = self.cursor_from_glyph_left(glyph);
|
||||||
if cursor >= cursor_start && cursor <= cursor_end {
|
if cursor >= cursor_start && cursor <= cursor_end {
|
||||||
if x_start.is_none() {
|
if x_start.is_none() {
|
||||||
x_start = Some(glyph.x + glyph.w * rtl_factor);
|
x_start = Some(glyph.x + glyph.w * rtl_factor);
|
||||||
}
|
}
|
||||||
x_end = Some(glyph.x + glyph.w * rtl_factor);
|
x_end = Some(glyph.x + glyph.w * rtl_factor);
|
||||||
}
|
}
|
||||||
}
|
let cursor = self.cursor_from_glyph_right(glyph);
|
||||||
let cursor = Cursor::new(self.line_i, self.glyphs.last().map_or(0, |glyph| glyph.end));
|
if cursor >= cursor_start && cursor <= cursor_end {
|
||||||
if cursor >= cursor_start && cursor <= cursor_end {
|
if x_start.is_none() {
|
||||||
if x_start.is_none() {
|
x_start = Some(glyph.x + glyph.w * ltr_factor);
|
||||||
x_start = Some(
|
}
|
||||||
self.glyphs
|
x_end = Some(glyph.x + glyph.w * ltr_factor);
|
||||||
.last()
|
|
||||||
.map_or(0., |glyph| glyph.x + glyph.w * ltr_factor),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
x_end = Some(
|
|
||||||
self.glyphs
|
|
||||||
.last()
|
|
||||||
.map_or(0., |glyph| glyph.x + glyph.w * ltr_factor),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if let Some(x_start) = x_start {
|
if let Some(x_start) = x_start {
|
||||||
let x_end = x_end.expect("end of cursor not found");
|
let x_end = x_end.expect("end of cursor not found");
|
||||||
|
|
@ -106,6 +110,22 @@ impl<'a> LayoutRun<'a> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cursor_from_glyph_left(&self, glyph: &LayoutGlyph) -> Cursor {
|
||||||
|
if self.rtl {
|
||||||
|
Cursor::new_affinity(self.line_i, glyph.end, false)
|
||||||
|
} else {
|
||||||
|
Cursor::new_affinity(self.line_i, glyph.start, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_from_glyph_right(&self, glyph: &LayoutGlyph) -> Cursor {
|
||||||
|
if self.rtl {
|
||||||
|
Cursor::new_affinity(self.line_i, glyph.start, true)
|
||||||
|
} else {
|
||||||
|
Cursor::new_affinity(self.line_i, glyph.end, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator of visible text lines, see [`LayoutRun`]
|
/// An iterator of visible text lines, see [`LayoutRun`]
|
||||||
|
|
@ -378,18 +398,18 @@ impl<'a> Buffer<'a> {
|
||||||
let layout = line.layout_opt().as_ref().expect("layout not found");
|
let layout = line.layout_opt().as_ref().expect("layout not found");
|
||||||
for (layout_i, layout_line) in layout.iter().enumerate() {
|
for (layout_i, layout_line) in layout.iter().enumerate() {
|
||||||
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
|
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
|
||||||
if cursor.index == glyph.start {
|
let cursor_end = Cursor::new_affinity(cursor.line, glyph.end, false);
|
||||||
|
let cursor_start = Cursor::new_affinity(cursor.line, glyph.start, true);
|
||||||
|
let (cursor_left, cursor_right) = if glyph.level.is_ltr() {
|
||||||
|
(cursor_start, cursor_end)
|
||||||
|
} else {
|
||||||
|
(cursor_end, cursor_start)
|
||||||
|
};
|
||||||
|
if *cursor == cursor_left {
|
||||||
return LayoutCursor::new(cursor.line, layout_i, glyph_i);
|
return LayoutCursor::new(cursor.line, layout_i, glyph_i);
|
||||||
}
|
}
|
||||||
}
|
if *cursor == cursor_right {
|
||||||
match layout_line.glyphs.last() {
|
return LayoutCursor::new(cursor.line, layout_i, glyph_i + 1);
|
||||||
Some(glyph) => {
|
|
||||||
if cursor.index == glyph.end {
|
|
||||||
return LayoutCursor::new(cursor.line, layout_i, layout_line.glyphs.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return LayoutCursor::new(cursor.line, layout_i, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -537,6 +557,7 @@ impl<'a> Buffer<'a> {
|
||||||
} else if y >= line_y - font_size && y < line_y - font_size + line_height {
|
} else if y >= line_y - font_size && y < line_y - font_size + line_height {
|
||||||
let mut new_cursor_glyph = run.glyphs.len();
|
let mut new_cursor_glyph = run.glyphs.len();
|
||||||
let mut new_cursor_char = 0;
|
let mut new_cursor_char = 0;
|
||||||
|
let mut new_cursor_affinity = true;
|
||||||
|
|
||||||
let mut first_glyph = true;
|
let mut first_glyph = true;
|
||||||
|
|
||||||
|
|
@ -563,6 +584,7 @@ impl<'a> Buffer<'a> {
|
||||||
if right_half != glyph.level.is_rtl() {
|
if right_half != glyph.level.is_rtl() {
|
||||||
// If clicking on last half of glyph, move cursor past glyph
|
// If clicking on last half of glyph, move cursor past glyph
|
||||||
new_cursor_char += egc.len();
|
new_cursor_char += egc.len();
|
||||||
|
new_cursor_affinity = false;
|
||||||
}
|
}
|
||||||
break 'hit;
|
break 'hit;
|
||||||
}
|
}
|
||||||
|
|
@ -573,6 +595,7 @@ impl<'a> Buffer<'a> {
|
||||||
if right_half != glyph.level.is_rtl() {
|
if right_half != glyph.level.is_rtl() {
|
||||||
// If clicking on last half of glyph, move cursor past glyph
|
// If clicking on last half of glyph, move cursor past glyph
|
||||||
new_cursor_char = cluster.len();
|
new_cursor_char = cluster.len();
|
||||||
|
new_cursor_affinity = false;
|
||||||
}
|
}
|
||||||
break 'hit;
|
break 'hit;
|
||||||
}
|
}
|
||||||
|
|
@ -584,11 +607,13 @@ impl<'a> Buffer<'a> {
|
||||||
Some(glyph) => {
|
Some(glyph) => {
|
||||||
// Position at glyph
|
// Position at glyph
|
||||||
new_cursor.index = glyph.start + new_cursor_char;
|
new_cursor.index = glyph.start + new_cursor_char;
|
||||||
|
new_cursor.affinity = new_cursor_affinity;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
if let Some(glyph) = run.glyphs.last() {
|
if let Some(glyph) = run.glyphs.last() {
|
||||||
// Position at end of line
|
// Position at end of line
|
||||||
new_cursor.index = glyph.end;
|
new_cursor.index = glyph.end;
|
||||||
|
new_cursor.affinity = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -599,7 +624,7 @@ impl<'a> Buffer<'a> {
|
||||||
} else if runs.peek().is_none() && y > run.line_y {
|
} else if runs.peek().is_none() && y > run.line_y {
|
||||||
let mut new_cursor = Cursor::new(run.line_i, 0);
|
let mut new_cursor = Cursor::new(run.line_i, 0);
|
||||||
if let Some(glyph) = run.glyphs.last() {
|
if let Some(glyph) = run.glyphs.last() {
|
||||||
new_cursor.index = glyph.end;
|
new_cursor = run.cursor_from_glyph_right(glyph);
|
||||||
}
|
}
|
||||||
new_cursor_opt = Some(new_cursor);
|
new_cursor_opt = Some(new_cursor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,18 +44,22 @@ impl<'a> Editor<'a> {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_index = match layout_line.glyphs.get(cursor.glyph) {
|
let (new_index, new_affinity) = match layout_line.glyphs.get(cursor.glyph) {
|
||||||
Some(glyph) => glyph.start,
|
Some(glyph) => (glyph.start, true),
|
||||||
None => match layout_line.glyphs.last() {
|
None => match layout_line.glyphs.last() {
|
||||||
Some(glyph) => glyph.end,
|
Some(glyph) => (glyph.end, false),
|
||||||
//TODO: is this correct?
|
//TODO: is this correct?
|
||||||
None => 0,
|
None => (0, true),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.cursor.line != cursor.line || self.cursor.index != new_index {
|
if self.cursor.line != cursor.line
|
||||||
|
|| self.cursor.index != new_index
|
||||||
|
|| self.cursor.affinity != new_affinity
|
||||||
|
{
|
||||||
self.cursor.line = cursor.line;
|
self.cursor.line = cursor.line;
|
||||||
self.cursor.index = new_index;
|
self.cursor.index = new_index;
|
||||||
|
self.cursor.affinity = new_affinity;
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -290,10 +294,12 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cursor.index = prev_index;
|
self.cursor.index = prev_index;
|
||||||
|
self.cursor.affinity = true;
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
} else if self.cursor.line > 0 {
|
} else if self.cursor.line > 0 {
|
||||||
self.cursor.line -= 1;
|
self.cursor.line -= 1;
|
||||||
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
|
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
|
||||||
|
self.cursor.affinity = true;
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
self.cursor_x_opt = None;
|
self.cursor_x_opt = None;
|
||||||
|
|
@ -304,6 +310,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
for (i, c) in line.text().grapheme_indices(true) {
|
for (i, c) in line.text().grapheme_indices(true) {
|
||||||
if i == self.cursor.index {
|
if i == self.cursor.index {
|
||||||
self.cursor.index += c.len();
|
self.cursor.index += c.len();
|
||||||
|
self.cursor.affinity = false;
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -311,6 +318,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
} else if self.cursor.line + 1 < self.buffer.lines.len() {
|
} else if self.cursor.line + 1 < self.buffer.lines.len() {
|
||||||
self.cursor.line += 1;
|
self.cursor.line += 1;
|
||||||
self.cursor.index = 0;
|
self.cursor.index = 0;
|
||||||
|
self.cursor.affinity = false;
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
self.cursor_x_opt = None;
|
self.cursor_x_opt = None;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue