From 01568acef2be24d7a7a5e8062307f30daa9a5014 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 16 May 2020 20:54:01 +0200 Subject: [PATCH] TextEdit: click to move cursor --- emigui/src/font.rs | 48 +++++++++++++++++++++++++++++++++ emigui/src/widgets/text_edit.rs | 6 ++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/emigui/src/font.rs b/emigui/src/font.rs index 8999e8ad..4d8756be 100644 --- a/emigui/src/font.rs +++ b/emigui/src/font.rs @@ -39,6 +39,9 @@ pub struct Line { /// Bottom of the line, offset within the Galley. /// Unit: points. pub y_max: f32, + + /// If true, the last char on this line is '\n' + pub ends_with_newline: bool, } impl Galley { @@ -71,6 +74,31 @@ impl Galley { 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 { @@ -90,6 +118,17 @@ impl Line { pub fn max_x(&self) -> f32 { *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, y_min: 0.0, y_max: self.height(), + ends_with_newline: false, }; let width = line.max_x(); let size = vec2(width, self.height()); @@ -311,6 +351,7 @@ impl Font { x_offsets: vec![0.0], y_min: cursor_y, y_max: cursor_y + line_spacing, + ends_with_newline: text.ends_with('\n'), }); } @@ -387,6 +428,7 @@ impl Font { .collect(), y_min: cursor_y, y_max: cursor_y + self.height(), + ends_with_newline: false, // we'll fix this later } } else { Line { @@ -396,6 +438,7 @@ impl Font { .collect(), y_min: cursor_y, y_max: cursor_y + self.height(), + ends_with_newline: false, // we'll fix this later } }; line.sanity_check(); @@ -423,11 +466,16 @@ impl Font { .collect(), y_min: cursor_y, y_max: cursor_y + self.height(), + ends_with_newline: false, // we'll fix this later }; line.sanity_check(); out_lines.push(line); } + if text.ends_with('\n') { + out_lines.last_mut().unwrap().ends_with_newline = true; + } + out_lines } } diff --git a/emigui/src/widgets/text_edit.rs b/emigui/src/widgets/text_edit.rs index 31e4feb5..0aaca9b6 100644 --- a/emigui/src/widgets/text_edit.rs +++ b/emigui/src/widgets/text_edit.rs @@ -2,7 +2,8 @@ use crate::*; #[derive(Clone, Copy, Debug, Default, serde_derive::Deserialize, serde_derive::Serialize)] pub(crate) struct State { - /// Charctaer based, NOT bytes + /// Charctaer based, NOT bytes. + /// TODO: store as line + row pub cursor: Option, } @@ -62,6 +63,9 @@ 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)); + } } if interact.hovered { ui.output().cursor_icon = CursorIcon::Text;