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

@ -7,7 +7,9 @@ use iced_graphics::font;
use iced_graphics::layer::Layer;
use iced_graphics::{Primitive, Viewport};
use iced_native::mouse;
use iced_native::{Font, HorizontalAlignment, Size, VerticalAlignment};
use iced_native::{
Font, HitTestResult, HorizontalAlignment, Size, VerticalAlignment,
};
#[cfg(any(feature = "image_rs", feature = "svg"))]
use crate::image;
@ -274,6 +276,25 @@ impl backend::Text for Backend {
) -> (f32, f32) {
self.text_pipeline.measure(contents, size, font, bounds)
}
fn hit_test(
&self,
contents: &str,
size: f32,
font: Font,
bounds: Size,
point: iced_native::Point,
nearest_only: bool,
) -> HitTestResult {
self.text_pipeline.hit_test(
contents,
size,
font,
bounds,
point,
nearest_only,
)
}
}
#[cfg(feature = "image_rs")]

View file

@ -1,5 +1,6 @@
use crate::Transformation;
use iced_graphics::font;
use iced_native::HitTestResult;
use std::{cell::RefCell, collections::HashMap};
use wgpu_glyph::ab_glyph;
@ -117,6 +118,97 @@ impl Pipeline {
}
}
pub fn hit_test(
&self,
content: &str,
size: f32,
font: iced_native::Font,
bounds: iced_native::Size,
point: iced_native::Point,
nearest_only: bool,
) -> HitTestResult {
use wgpu_glyph::GlyphCruncher;
let wgpu_glyph::FontId(font_id) = self.find_font(font);
let section = wgpu_glyph::Section {
bounds: (bounds.width, bounds.height),
text: vec![wgpu_glyph::Text {
text: content,
scale: size.into(),
font_id: wgpu_glyph::FontId(font_id),
extra: wgpu_glyph::Extra::default(),
}],
..Default::default()
};
let mut mb = self.measure_brush.borrow_mut();
// The underlying type is FontArc, so clones are cheap.
use wgpu_glyph::ab_glyph::{Font, ScaleFont};
let font = mb.fonts()[font_id].clone().into_scaled(size);
// Implements an iterator over the glyph bounding boxes.
let bounds = mb.glyphs(section).map(
|wgpu_glyph::SectionGlyph {
byte_index, glyph, ..
}| {
(
*byte_index,
iced_native::Rectangle::new(
iced_native::Point::new(
glyph.position.x - font.h_side_bearing(glyph.id),
glyph.position.y - font.ascent(),
),
iced_native::Size::new(
font.h_advance(glyph.id),
font.ascent() - font.descent(),
),
),
)
},
);
// Implements computation of the character index based on the byte index
// within the input string.
let char_index = |byte_index| {
let mut b_count = 0;
for (i, utf8_len) in
content.chars().map(|c| c.len_utf8()).enumerate()
{
if byte_index < (b_count + utf8_len) {
return i;
}
b_count += utf8_len;
}
return byte_index;
};
if !nearest_only {
for (idx, bounds) in bounds.clone() {
if bounds.contains(point) {
return HitTestResult::CharOffset(char_index(idx));
}
}
}
let (idx, nearest) = bounds.fold(
(0usize, iced_native::Point::ORIGIN),
|acc: (usize, iced_native::Point), (idx, bounds)| {
if bounds.center().distance(point) < acc.1.distance(point) {
(idx, bounds.center())
} else {
acc
}
},
);
HitTestResult::NearestCharOffset(
char_index(idx),
(point - nearest).into(),
)
}
pub fn trim_measurement_cache(&mut self) {
// TODO: We should probably use a `GlyphCalculator` for this. However,
// it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.