TextEdit: cursor movement with home/end/up/down
This commit is contained in:
parent
c9f07c1cff
commit
3ec552392f
2 changed files with 81 additions and 16 deletions
|
@ -8,6 +8,16 @@ use crate::{
|
|||
texture_atlas::TextureAtlas,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct GalleyCursor {
|
||||
/// character count in whole galley
|
||||
pub char_idx: usize,
|
||||
/// line number
|
||||
pub line: usize,
|
||||
/// character count on this line
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
/// A collection of text locked into place.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Galley {
|
||||
|
@ -76,28 +86,30 @@ impl Galley {
|
|||
}
|
||||
|
||||
/// Character offset at the given position within the galley
|
||||
pub fn char_at(&self, pos: Vec2) -> usize {
|
||||
pub fn char_at(&self, pos: Vec2) -> GalleyCursor {
|
||||
let mut best_y_dist = f32::INFINITY;
|
||||
let mut char_idx = 0;
|
||||
let mut cursor = GalleyCursor::default();
|
||||
|
||||
let mut char_count = 0;
|
||||
for line in &self.lines {
|
||||
for (line_nr, line) in self.lines.iter().enumerate() {
|
||||
let y_dist = (line.y_min - pos.y).abs().min((line.y_max - pos.y).abs());
|
||||
if y_dist < best_y_dist {
|
||||
best_y_dist = y_dist;
|
||||
let line_offset = line.char_at(pos.x);
|
||||
if line_offset == line.char_count() && line.ends_with_newline {
|
||||
let mut column = line.char_at(pos.x);
|
||||
if column == line.char_count() && line.ends_with_newline {
|
||||
// handle the case where line ends with a \n and we click after it.
|
||||
// We should return the position BEFORE the \n!
|
||||
char_idx = char_count + line_offset - 1;
|
||||
} else {
|
||||
char_idx = char_count + line_offset;
|
||||
column -= 1;
|
||||
}
|
||||
cursor = GalleyCursor {
|
||||
char_idx: char_count + column,
|
||||
line: line_nr,
|
||||
column,
|
||||
}
|
||||
}
|
||||
char_count += line.char_count();
|
||||
}
|
||||
// eprintln!("char_at {:?}: {} (text: {:?})", pos, char_idx, self.text);
|
||||
char_idx
|
||||
cursor
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ impl<'t> Widget for TextEdit<'t> {
|
|||
if interact.clicked {
|
||||
ui.request_kb_focus(id);
|
||||
if let Some(mouse_pos) = ui.input().mouse_pos {
|
||||
state.cursor = Some(galley.char_at(mouse_pos - interact.rect.min));
|
||||
state.cursor = Some(galley.char_at(mouse_pos - interact.rect.min).char_idx);
|
||||
}
|
||||
}
|
||||
if interact.hovered {
|
||||
|
@ -149,7 +149,7 @@ impl<'t> Widget for TextEdit<'t> {
|
|||
}
|
||||
|
||||
fn insert_text(cursor: &mut usize, text: &mut String, text_to_insert: &str) {
|
||||
// eprintln!("insert_text before: '{}', cursor at {}", text, cursor);
|
||||
eprintln!("insert_text {:?}", text_to_insert);
|
||||
|
||||
let mut char_it = text.chars();
|
||||
let mut new_text = String::with_capacity(text.capacity());
|
||||
|
@ -161,9 +161,8 @@ fn insert_text(cursor: &mut usize, text: &mut String, text_to_insert: &str) {
|
|||
new_text += text_to_insert;
|
||||
new_text.extend(char_it);
|
||||
*text = new_text;
|
||||
|
||||
// eprintln!("insert_text after: '{}', cursor at {}\n", text, cursor);
|
||||
}
|
||||
|
||||
fn on_key_press(cursor: &mut usize, text: &mut String, key: Key) {
|
||||
// eprintln!("on_key_press before: '{}', cursor at {}", text, cursor);
|
||||
|
||||
|
@ -189,10 +188,15 @@ fn on_key_press(cursor: &mut usize, text: &mut String, key: Key) {
|
|||
*text = new_text;
|
||||
}
|
||||
Key::Home => {
|
||||
*cursor = 0; // TODO: start of line
|
||||
// To start of paragraph:
|
||||
let pos = line_col_from_char_idx(text, *cursor);
|
||||
*cursor = char_idx_from_line_col(text, (pos.0, 0));
|
||||
}
|
||||
Key::End => {
|
||||
*cursor = text.chars().count(); // TODO: end of line
|
||||
// To end of paragraph:
|
||||
let pos = line_col_from_char_idx(text, *cursor);
|
||||
let line = line_from_number(text, pos.0);
|
||||
*cursor = char_idx_from_line_col(text, (pos.0, line.chars().count()));
|
||||
}
|
||||
Key::Left if *cursor > 0 => {
|
||||
*cursor -= 1;
|
||||
|
@ -200,8 +204,57 @@ fn on_key_press(cursor: &mut usize, text: &mut String, key: Key) {
|
|||
Key::Right => {
|
||||
*cursor = (*cursor + 1).min(text.chars().count());
|
||||
}
|
||||
Key::Up => {
|
||||
let mut pos = line_col_from_char_idx(text, *cursor);
|
||||
pos.0 = pos.0.saturating_sub(1);
|
||||
*cursor = char_idx_from_line_col(text, pos);
|
||||
}
|
||||
Key::Down => {
|
||||
let mut pos = line_col_from_char_idx(text, *cursor);
|
||||
pos.0 += 1;
|
||||
*cursor = char_idx_from_line_col(text, pos);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// eprintln!("on_key_press after: '{}', cursor at {}\n", text, cursor);
|
||||
}
|
||||
|
||||
fn line_col_from_char_idx(s: &str, char_idx: usize) -> (usize, usize) {
|
||||
let mut char_count = 0;
|
||||
|
||||
let mut last_line_nr = 0;
|
||||
let mut last_line = s;
|
||||
for (line_nr, line) in s.split('\n').enumerate() {
|
||||
let line_width = line.chars().count();
|
||||
if char_idx <= char_count + line_width {
|
||||
return (line_nr, char_idx - char_count);
|
||||
}
|
||||
char_count += line_width + 1;
|
||||
last_line_nr = line_nr;
|
||||
last_line = line;
|
||||
}
|
||||
|
||||
// safe fallback:
|
||||
(last_line_nr, last_line.chars().count())
|
||||
}
|
||||
|
||||
fn char_idx_from_line_col(s: &str, pos: (usize, usize)) -> usize {
|
||||
let mut char_count = 0;
|
||||
for (line_nr, line) in s.split('\n').enumerate() {
|
||||
if line_nr == pos.0 {
|
||||
return char_count + pos.1.min(line.chars().count());
|
||||
}
|
||||
char_count += line.chars().count() + 1;
|
||||
}
|
||||
char_count
|
||||
}
|
||||
|
||||
fn line_from_number(s: &str, desired_line_number: usize) -> &str {
|
||||
for (line_nr, line) in s.split('\n').enumerate() {
|
||||
if line_nr == desired_line_number {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue