Use Option to encode empty text case in hit test methods

This commit is contained in:
Héctor Ramón Jiménez 2021-09-15 14:49:13 +07:00
parent 93fec8d273
commit 643500bbdf
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
10 changed files with 59 additions and 52 deletions

View file

@ -14,14 +14,14 @@ pub enum Hit {
impl Hit { impl Hit {
/// Computes the cursor position corresponding to this [`HitTestResult`] . /// Computes the cursor position corresponding to this [`HitTestResult`] .
pub fn cursor(&self) -> usize { pub fn cursor(self) -> usize {
match self { match self {
Self::CharOffset(i) => *i, Self::CharOffset(i) => i,
Self::NearestCharOffset(i, delta) => { Self::NearestCharOffset(i, delta) => {
if delta.x > f32::EPSILON { if delta.x > f32::EPSILON {
i + 1 i + 1
} else { } else {
*i i
} }
} }
} }

View file

@ -221,7 +221,7 @@ impl backend::Text for Backend {
bounds: Size, bounds: Size,
point: iced_native::Point, point: iced_native::Point,
nearest_only: bool, nearest_only: bool,
) -> text::Hit { ) -> Option<text::Hit> {
self.text_pipeline.hit_test( self.text_pipeline.hit_test(
contents, contents,
size, size,

View file

@ -121,7 +121,7 @@ impl Pipeline {
bounds: iced_native::Size, bounds: iced_native::Size,
point: iced_native::Point, point: iced_native::Point,
nearest_only: bool, nearest_only: bool,
) -> Hit { ) -> Option<Hit> {
use glow_glyph::GlyphCruncher; use glow_glyph::GlyphCruncher;
let glow_glyph::FontId(font_id) = self.find_font(font); let glow_glyph::FontId(font_id) = self.find_font(font);
@ -182,23 +182,25 @@ impl Pipeline {
if !nearest_only { if !nearest_only {
for (idx, bounds) in bounds.clone() { for (idx, bounds) in bounds.clone() {
if bounds.contains(point) { if bounds.contains(point) {
return Hit::CharOffset(char_index(idx)); return Some(Hit::CharOffset(char_index(idx)));
} }
} }
} }
let (idx, nearest) = bounds.fold( let (idx, nearest) = bounds.fold(
(0usize, iced_native::Point::ORIGIN), (None, iced_native::Point::ORIGIN),
|acc: (usize, iced_native::Point), (idx, bounds)| { |best, (idx, bounds)| {
if bounds.center().distance(point) < acc.1.distance(point) { let center = bounds.center();
(idx, bounds.center())
if center.distance(point) < best.1.distance(point) {
(Some(idx), center)
} else { } else {
acc best
} }
}, },
); );
Hit::NearestCharOffset(char_index(idx), (point - nearest).into()) idx.map(|idx| Hit::NearestCharOffset(char_index(idx), point - nearest))
} }
pub fn trim_measurement_cache(&mut self) { pub fn trim_measurement_cache(&mut self) {

View file

@ -60,7 +60,7 @@ pub trait Text {
bounds: Size, bounds: Size,
point: Point, point: Point,
nearest_only: bool, nearest_only: bool,
) -> text::Hit; ) -> Option<text::Hit>;
} }
/// A graphics backend that supports image rendering. /// A graphics backend that supports image rendering.

View file

@ -43,7 +43,7 @@ where
bounds: Size, bounds: Size,
point: Point, point: Point,
nearest_only: bool, nearest_only: bool,
) -> text::Hit { ) -> Option<text::Hit> {
self.backend().hit_test( self.backend().hit_test(
content, content,
size, size,

View file

@ -2,7 +2,7 @@ use crate::{
button, checkbox, column, container, pane_grid, progress_bar, radio, row, button, checkbox, column, container, pane_grid, progress_bar, radio, row,
scrollable, slider, text, text_input, toggler, Color, Element, Font, scrollable, slider, text, text_input, toggler, Color, Element, Font,
HorizontalAlignment, Layout, Padding, Point, Rectangle, Renderer, Size, HorizontalAlignment, Layout, Padding, Point, Rectangle, Renderer, Size,
Vector, VerticalAlignment, VerticalAlignment,
}; };
/// A renderer that does nothing. /// A renderer that does nothing.
@ -75,8 +75,8 @@ impl text::Renderer for Null {
_bounds: Size, _bounds: Size,
_point: Point, _point: Point,
_nearest_only: bool, _nearest_only: bool,
) -> text::Hit { ) -> Option<text::Hit> {
text::Hit::NearestCharOffset(0, Vector::new(0., 0.)) None
} }
fn draw( fn draw(

View file

@ -196,7 +196,7 @@ pub trait Renderer: crate::Renderer {
bounds: Size, bounds: Size,
point: Point, point: Point,
nearest_only: bool, nearest_only: bool,
) -> Hit; ) -> Option<Hit>;
/// Draws a [`Text`] fragment. /// Draws a [`Text`] fragment.
/// ///

View file

@ -268,41 +268,42 @@ where
match click.kind() { match click.kind() {
click::Kind::Single => { click::Kind::Single => {
if target > 0.0 { let position = if target > 0.0 {
let value = if self.is_secure { let value = if self.is_secure {
self.value.secure() self.value.secure()
} else { } else {
self.value.clone() self.value.clone()
}; };
let position = renderer.find_cursor_position( renderer.find_cursor_position(
text_layout.bounds(), text_layout.bounds(),
self.font, self.font,
self.size, self.size,
&value, &value,
&self.state, &self.state,
target, target,
); )
self.state.cursor.move_to(position);
} else { } else {
self.state.cursor.move_to(0); None
} };
self.state.cursor.move_to(position.unwrap_or(0));
self.state.is_dragging = true; self.state.is_dragging = true;
} }
click::Kind::Double => { click::Kind::Double => {
if self.is_secure { if self.is_secure {
self.state.cursor.select_all(&self.value); self.state.cursor.select_all(&self.value);
} else { } else {
let position = renderer.find_cursor_position( let position = renderer
text_layout.bounds(), .find_cursor_position(
self.font, text_layout.bounds(),
self.size, self.font,
&self.value, self.size,
&self.state, &self.value,
target, &self.state,
); target,
)
.unwrap_or(0);
self.state.cursor.select_range( self.state.cursor.select_range(
self.value.previous_start_of_word(position), self.value.previous_start_of_word(position),
@ -341,14 +342,16 @@ where
self.value.clone() self.value.clone()
}; };
let position = renderer.find_cursor_position( let position = renderer
text_layout.bounds(), .find_cursor_position(
self.font, text_layout.bounds(),
self.size, self.font,
&value, self.size,
&self.state, &value,
target, &self.state,
); target,
)
.unwrap_or(0);
self.state.cursor.select_range( self.state.cursor.select_range(
self.state.cursor.start(&value), self.state.cursor.start(&value),
@ -702,7 +705,7 @@ pub trait Renderer: text::Renderer + Sized {
value: &Value, value: &Value,
state: &State, state: &State,
x: f32, x: f32,
) -> usize { ) -> Option<usize> {
let size = size.unwrap_or(self.default_size()); let size = size.unwrap_or(self.default_size());
let offset = self.offset(text_bounds, font, size, &value, &state); let offset = self.offset(text_bounds, font, size, &value, &state);
@ -715,7 +718,7 @@ pub trait Renderer: text::Renderer + Sized {
Point::new(x + offset, text_bounds.height / 2.0), Point::new(x + offset, text_bounds.height / 2.0),
true, true,
) )
.cursor() .map(text::Hit::cursor)
} }
} }

View file

@ -284,7 +284,7 @@ impl backend::Text for Backend {
bounds: Size, bounds: Size,
point: iced_native::Point, point: iced_native::Point,
nearest_only: bool, nearest_only: bool,
) -> text::Hit { ) -> Option<text::Hit> {
self.text_pipeline.hit_test( self.text_pipeline.hit_test(
contents, contents,
size, size,

View file

@ -129,7 +129,7 @@ impl Pipeline {
bounds: iced_native::Size, bounds: iced_native::Size,
point: iced_native::Point, point: iced_native::Point,
nearest_only: bool, nearest_only: bool,
) -> Hit { ) -> Option<Hit> {
use wgpu_glyph::GlyphCruncher; use wgpu_glyph::GlyphCruncher;
let wgpu_glyph::FontId(font_id) = self.find_font(font); let wgpu_glyph::FontId(font_id) = self.find_font(font);
@ -190,23 +190,25 @@ impl Pipeline {
if !nearest_only { if !nearest_only {
for (idx, bounds) in bounds.clone() { for (idx, bounds) in bounds.clone() {
if bounds.contains(point) { if bounds.contains(point) {
return Hit::CharOffset(char_index(idx)); return Some(Hit::CharOffset(char_index(idx)));
} }
} }
} }
let (idx, nearest) = bounds.fold( let (idx, nearest) = bounds.fold(
(0usize, iced_native::Point::ORIGIN), (None, iced_native::Point::ORIGIN),
|acc: (usize, iced_native::Point), (idx, bounds)| { |best, (idx, bounds)| {
if bounds.center().distance(point) < acc.1.distance(point) { let center = bounds.center();
(idx, bounds.center())
if center.distance(point) < best.1.distance(point) {
(Some(idx), center)
} else { } else {
acc best
} }
}, },
); );
Hit::NearestCharOffset(char_index(idx), (point - nearest).into()) idx.map(|idx| Hit::NearestCharOffset(char_index(idx), point - nearest))
} }
pub fn trim_measurement_cache(&mut self) { pub fn trim_measurement_cache(&mut self) {