TextEdit: click to move cursor

This commit is contained in:
Emil Ernerfeldt 2020-05-16 20:54:01 +02:00
parent ce0e7f4e09
commit 01568acef2
2 changed files with 53 additions and 1 deletions

View file

@ -39,6 +39,9 @@ pub struct Line {
/// Bottom of the line, offset within the Galley. /// Bottom of the line, offset within the Galley.
/// Unit: points. /// Unit: points.
pub y_max: f32, pub y_max: f32,
/// If true, the last char on this line is '\n'
pub ends_with_newline: bool,
} }
impl Galley { impl Galley {
@ -71,6 +74,31 @@ impl Galley {
vec2(0.0, 0.0) vec2(0.0, 0.0)
} }
} }
/// Character offset at the given position within the galley
pub fn char_at(&self, pos: Vec2) -> usize {
let mut best_y_dist = f32::INFINITY;
let mut char_idx = 0;
let mut char_count = 0;
for line in &self.lines {
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 {
// 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;
}
}
char_count += line.char_count();
}
// eprintln!("char_at {:?}: {} (text: {:?})", pos, char_idx, self.text);
char_idx
}
} }
impl Line { impl Line {
@ -90,6 +118,17 @@ impl Line {
pub fn max_x(&self) -> f32 { pub fn max_x(&self) -> f32 {
*self.x_offsets.last().unwrap() *self.x_offsets.last().unwrap()
} }
/// Closest char at the desired x coordinate. return [0, char_count()]
pub fn char_at(&self, desired_x: f32) -> usize {
for (i, char_x_bounds) in self.x_offsets.windows(2).enumerate() {
let char_center_x = 0.5 * (char_x_bounds[0] + char_x_bounds[1]);
if desired_x < char_center_x {
return i;
}
}
self.char_count()
}
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -262,6 +301,7 @@ impl Font {
x_offsets, x_offsets,
y_min: 0.0, y_min: 0.0,
y_max: self.height(), y_max: self.height(),
ends_with_newline: false,
}; };
let width = line.max_x(); let width = line.max_x();
let size = vec2(width, self.height()); let size = vec2(width, self.height());
@ -311,6 +351,7 @@ impl Font {
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,
ends_with_newline: text.ends_with('\n'),
}); });
} }
@ -387,6 +428,7 @@ impl Font {
.collect(), .collect(),
y_min: cursor_y, y_min: cursor_y,
y_max: cursor_y + self.height(), y_max: cursor_y + self.height(),
ends_with_newline: false, // we'll fix this later
} }
} else { } else {
Line { Line {
@ -396,6 +438,7 @@ impl Font {
.collect(), .collect(),
y_min: cursor_y, y_min: cursor_y,
y_max: cursor_y + self.height(), y_max: cursor_y + self.height(),
ends_with_newline: false, // we'll fix this later
} }
}; };
line.sanity_check(); line.sanity_check();
@ -423,11 +466,16 @@ impl Font {
.collect(), .collect(),
y_min: cursor_y, y_min: cursor_y,
y_max: cursor_y + self.height(), y_max: cursor_y + self.height(),
ends_with_newline: false, // we'll fix this later
}; };
line.sanity_check(); line.sanity_check();
out_lines.push(line); out_lines.push(line);
} }
if text.ends_with('\n') {
out_lines.last_mut().unwrap().ends_with_newline = true;
}
out_lines out_lines
} }
} }

View file

@ -2,7 +2,8 @@ use crate::*;
#[derive(Clone, Copy, Debug, Default, serde_derive::Deserialize, serde_derive::Serialize)] #[derive(Clone, Copy, Debug, Default, serde_derive::Deserialize, serde_derive::Serialize)]
pub(crate) struct State { pub(crate) struct State {
/// Charctaer based, NOT bytes /// Charctaer based, NOT bytes.
/// TODO: store as line + row
pub cursor: Option<usize>, pub cursor: Option<usize>,
} }
@ -62,6 +63,9 @@ impl<'t> Widget for TextEdit<'t> {
if interact.clicked { if interact.clicked {
ui.request_kb_focus(id); 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));
}
} }
if interact.hovered { if interact.hovered {
ui.output().cursor_icon = CursorIcon::Text; ui.output().cursor_icon = CursorIcon::Text;