Implement textual hit testing

This commit is contained in:
Tom 2021-08-21 10:31:26 -07:00
parent 8333b8f88c
commit aa63841e2c
13 changed files with 341 additions and 75 deletions

View file

@ -61,8 +61,8 @@ mod debug;
mod debug;
pub use iced_core::{
menu, Align, Background, Color, Font, HorizontalAlignment, Length, Menu,
Padding, Point, Rectangle, Size, Vector, VerticalAlignment,
menu, Align, Background, Color, Font, HitTestResult, HorizontalAlignment,
Length, Menu, Padding, Point, Rectangle, Size, Vector, VerticalAlignment,
};
pub use iced_futures::{executor, futures, Command};

View file

@ -1,8 +1,8 @@
use crate::{
button, checkbox, column, container, pane_grid, progress_bar, radio, row,
scrollable, slider, text, text_input, toggler, Color, Element, Font,
HorizontalAlignment, Layout, Padding, Point, Rectangle, Renderer, Size,
VerticalAlignment,
HitTestResult, HorizontalAlignment, Layout, Padding, Point, Rectangle,
Renderer, Size, Vector, VerticalAlignment,
};
/// A renderer that does nothing.
@ -67,6 +67,18 @@ impl text::Renderer for Null {
(0.0, 20.0)
}
fn hit_test(
&self,
_contents: &str,
_size: f32,
_font: Self::Font,
_bounds: Size,
_point: Point,
_nearest_only: bool,
) -> HitTestResult {
HitTestResult::NearestCharOffset(0, Vector::new(0., 0.))
}
fn draw(
&mut self,
_defaults: &Self::Defaults,

View file

@ -1,7 +1,7 @@
//! Write some text for your users to read.
use crate::{
layout, Color, Element, Hasher, HorizontalAlignment, Layout, Length, Point,
Rectangle, Size, VerticalAlignment, Widget,
layout, Color, Element, Hasher, HitTestResult, HorizontalAlignment, Layout,
Length, Point, Rectangle, Size, VerticalAlignment, Widget,
};
use std::hash::Hash;
@ -179,6 +179,23 @@ pub trait Renderer: crate::Renderer {
bounds: Size,
) -> (f32, f32);
/// Tests whether the provided point is within the boundaries of [`Text`]
/// laid out with the given parameters, returning information about
/// the nearest character.
///
/// If nearest_only is true, the hit test does not consider whether the
/// the point is interior to any glyph bounds, returning only the character
/// with the nearest centeroid.
fn hit_test(
&self,
contents: &str,
size: f32,
font: Self::Font,
bounds: Size,
point: Point,
nearest_only: bool,
) -> HitTestResult;
/// Draws a [`Text`] fragment.
///
/// It receives:

View file

@ -707,15 +707,15 @@ pub trait Renderer: text::Renderer + Sized {
let offset = self.offset(text_bounds, font, size, &value, &state);
find_cursor_position(
self,
&value,
self.hit_test(
&value.to_string(),
size.into(),
font,
size,
x + offset,
0,
value.len(),
Size::INFINITY,
Point::new(x + offset, text_bounds.height / 2.0),
true,
)
.cursor()
}
}
@ -803,62 +803,6 @@ impl State {
}
}
// TODO: Reduce allocations
fn find_cursor_position<Renderer: self::Renderer>(
renderer: &Renderer,
value: &Value,
font: Renderer::Font,
size: u16,
target: f32,
start: usize,
end: usize,
) -> usize {
if start >= end {
if start == 0 {
return 0;
}
let prev = value.until(start - 1);
let next = value.until(start);
let prev_width = renderer.measure_value(&prev.to_string(), size, font);
let next_width = renderer.measure_value(&next.to_string(), size, font);
if next_width - target > target - prev_width {
return start - 1;
} else {
return start;
}
}
let index = (end - start) / 2;
let subvalue = value.until(start + index);
let width = renderer.measure_value(&subvalue.to_string(), size, font);
if width > target {
find_cursor_position(
renderer,
value,
font,
size,
target,
start,
start + index,
)
} else {
find_cursor_position(
renderer,
value,
font,
size,
target,
start + index + 1,
end,
)
}
}
mod platform {
use crate::keyboard;