Make hit detection public

This commit is contained in:
Jeremy Soller 2022-10-25 20:27:46 -06:00
parent e32cfedfcf
commit c031944b45

View file

@ -88,16 +88,16 @@ pub struct TextLayoutRun<'a> {
pub line_y: i32, pub line_y: i32,
} }
pub struct TextLayoutRunIter<'a> { pub struct TextLayoutRunIter<'a, 'b> {
buffer: &'a TextBuffer<'a>, buffer: &'b TextBuffer<'a>,
line_i: usize, line_i: usize,
layout_i: usize, layout_i: usize,
line_y: i32, line_y: i32,
total_layout: i32, total_layout: i32,
} }
impl<'a> TextLayoutRunIter<'a> { impl<'a, 'b> TextLayoutRunIter<'a, 'b> {
pub fn new(buffer: &'a TextBuffer<'a>) -> Self { pub fn new(buffer: &'b TextBuffer<'a>) -> Self {
Self { Self {
buffer, buffer,
line_i: 0, line_i: 0,
@ -108,8 +108,8 @@ impl<'a> TextLayoutRunIter<'a> {
} }
} }
impl<'a> Iterator for TextLayoutRunIter<'a> { impl<'a, 'b> Iterator for TextLayoutRunIter<'a, 'b> {
type Item = TextLayoutRun<'a>; type Item = TextLayoutRun<'b>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
while let Some(line) = self.buffer.lines.get(self.line_i) { while let Some(line) = self.buffer.lines.get(self.line_i) {
@ -759,7 +759,12 @@ impl<'a> TextBuffer<'a> {
TextAction::Click { x, y } => { TextAction::Click { x, y } => {
self.select_opt = None; self.select_opt = None;
self.click(x, y); if let Some(new_cursor) = self.hit(x, y) {
if new_cursor != self.cursor {
self.cursor = new_cursor;
self.redraw = true;
}
}
}, },
TextAction::Drag { x, y } => { TextAction::Drag { x, y } => {
if self.select_opt.is_none() { if self.select_opt.is_none() {
@ -767,7 +772,12 @@ impl<'a> TextBuffer<'a> {
self.redraw = true; self.redraw = true;
} }
self.click(x, y); if let Some(new_cursor) = self.hit(x, y) {
if new_cursor != self.cursor {
self.cursor = new_cursor;
self.redraw = true;
}
}
}, },
TextAction::Scroll { lines } => { TextAction::Scroll { lines } => {
self.scroll += lines; self.scroll += lines;
@ -778,25 +788,26 @@ impl<'a> TextBuffer<'a> {
} }
} }
fn click(&mut self, mouse_x: i32, mouse_y: i32) { /// Convert x, y position to TextCursor (hit detection)
pub fn hit(&self, x: i32, y: i32) -> Option<TextCursor> {
let instant = Instant::now(); let instant = Instant::now();
let font_size = self.metrics.font_size; let font_size = self.metrics.font_size;
let line_height = self.metrics.line_height; let line_height = self.metrics.line_height;
let mut new_cursor = self.cursor; let mut new_cursor_opt = None;
for run in self.layout_runs() { for run in self.layout_runs() {
let line_y = run.line_y; let line_y = run.line_y;
if mouse_y >= line_y - font_size if y >= line_y - font_size
&& mouse_y < line_y - font_size + line_height && 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;
'hit: for (glyph_i, glyph) in run.glyphs.iter().enumerate() { 'hit: for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
if mouse_x >= glyph.x as i32 if x >= glyph.x as i32
&& mouse_x <= (glyph.x + glyph.w) as i32 && x <= (glyph.x + glyph.w) as i32
{ {
new_cursor_glyph = glyph_i; new_cursor_glyph = glyph_i;
@ -805,12 +816,12 @@ impl<'a> TextBuffer<'a> {
let mut egc_x = glyph.x; let mut egc_x = glyph.x;
let egc_w = glyph.w / (total as f32); let egc_w = glyph.w / (total as f32);
for (egc_i, egc) in cluster.grapheme_indices(true) { for (egc_i, egc) in cluster.grapheme_indices(true) {
if mouse_x >= egc_x as i32 if x >= egc_x as i32
&& mouse_x <= (egc_x + egc_w) as i32 && x <= (egc_x + egc_w) as i32
{ {
new_cursor_char = egc_i; new_cursor_char = egc_i;
let right_half = mouse_x >= (egc_x + egc_w / 2.0) as i32; let right_half = x >= (egc_x + egc_w / 2.0) as i32;
if right_half != glyph.rtl { if right_half != glyph.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();
@ -820,7 +831,7 @@ impl<'a> TextBuffer<'a> {
egc_x += egc_w; egc_x += egc_w;
} }
let right_half = mouse_x >= (glyph.x + glyph.w / 2.0) as i32; let right_half = x >= (glyph.x + glyph.w / 2.0) as i32;
if right_half != glyph.rtl { if right_half != glyph.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();
@ -829,8 +840,7 @@ impl<'a> TextBuffer<'a> {
} }
} }
new_cursor.line = run.line_i; let mut new_cursor = TextCursor::new(run.line_i, 0);
new_cursor.index = 0;
match run.glyphs.get(new_cursor_glyph) { match run.glyphs.get(new_cursor_glyph) {
Some(glyph) => { Some(glyph) => {
@ -859,21 +869,20 @@ impl<'a> TextBuffer<'a> {
} }
} }
new_cursor_opt = Some(new_cursor);
break; break;
} }
} }
if new_cursor != self.cursor {
self.cursor = new_cursor;
self.redraw = true;
}
let duration = instant.elapsed(); let duration = instant.elapsed();
log::trace!("click({}, {}): {:?}", mouse_x, mouse_y, duration); log::trace!("click({}, {}): {:?}", x, y, duration);
new_cursor_opt
} }
/// Get the visible layout runs for rendering and other tasks /// Get the visible layout runs for rendering and other tasks
pub fn layout_runs(&self) -> TextLayoutRunIter { pub fn layout_runs<'b>(&'b self) -> TextLayoutRunIter<'a, 'b> {
TextLayoutRunIter::new(self) TextLayoutRunIter::new(self)
} }