Implement textual hit testing
This commit is contained in:
parent
8333b8f88c
commit
aa63841e2c
13 changed files with 341 additions and 75 deletions
28
core/src/hit_test.rs
Normal file
28
core/src/hit_test.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use crate::Vector;
|
||||||
|
|
||||||
|
/// The result of hit testing on text.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum HitTestResult {
|
||||||
|
/// The point was within the bounds of the returned character index.
|
||||||
|
CharOffset(usize),
|
||||||
|
/// The provided point was not within the bounds of a glyph. The index
|
||||||
|
/// of the character with the closest centeroid position is returned,
|
||||||
|
/// as well as its delta.
|
||||||
|
NearestCharOffset(usize, Vector),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HitTestResult {
|
||||||
|
/// Computes the cursor position corresponding to this [`HitTestResult`] .
|
||||||
|
pub fn cursor(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
HitTestResult::CharOffset(i) => *i,
|
||||||
|
HitTestResult::NearestCharOffset(i, delta) => {
|
||||||
|
if delta.x > f32::EPSILON {
|
||||||
|
i + 1
|
||||||
|
} else {
|
||||||
|
*i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ mod align;
|
||||||
mod background;
|
mod background;
|
||||||
mod color;
|
mod color;
|
||||||
mod font;
|
mod font;
|
||||||
|
mod hit_test;
|
||||||
mod length;
|
mod length;
|
||||||
mod padding;
|
mod padding;
|
||||||
mod point;
|
mod point;
|
||||||
|
|
@ -33,6 +34,7 @@ pub use align::{Align, HorizontalAlignment, VerticalAlignment};
|
||||||
pub use background::Background;
|
pub use background::Background;
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
pub use font::Font;
|
pub use font::Font;
|
||||||
|
pub use hit_test::HitTestResult;
|
||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
pub use padding::Padding;
|
pub use padding::Padding;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ use iced_graphics::font;
|
||||||
use iced_graphics::Layer;
|
use iced_graphics::Layer;
|
||||||
use iced_graphics::Primitive;
|
use iced_graphics::Primitive;
|
||||||
use iced_native::mouse;
|
use iced_native::mouse;
|
||||||
use iced_native::{Font, HorizontalAlignment, Size, VerticalAlignment};
|
use iced_native::{
|
||||||
|
Font, HitTestResult, HorizontalAlignment, Size, VerticalAlignment,
|
||||||
|
};
|
||||||
|
|
||||||
/// A [`glow`] graphics backend for [`iced`].
|
/// A [`glow`] graphics backend for [`iced`].
|
||||||
///
|
///
|
||||||
|
|
@ -211,6 +213,25 @@ impl backend::Text for Backend {
|
||||||
) -> (f32, f32) {
|
) -> (f32, f32) {
|
||||||
self.text_pipeline.measure(contents, size, font, bounds)
|
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")]
|
#[cfg(feature = "image")]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::Transformation;
|
use crate::Transformation;
|
||||||
use glow_glyph::ab_glyph;
|
use glow_glyph::ab_glyph;
|
||||||
use iced_graphics::font;
|
use iced_graphics::font;
|
||||||
|
use iced_native::HitTestResult;
|
||||||
use std::{cell::RefCell, collections::HashMap};
|
use std::{cell::RefCell, collections::HashMap};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -109,6 +110,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 glow_glyph::GlyphCruncher;
|
||||||
|
|
||||||
|
let glow_glyph::FontId(font_id) = self.find_font(font);
|
||||||
|
|
||||||
|
let section = glow_glyph::Section {
|
||||||
|
bounds: (bounds.width, bounds.height),
|
||||||
|
text: vec![glow_glyph::Text {
|
||||||
|
text: content,
|
||||||
|
scale: size.into(),
|
||||||
|
font_id: glow_glyph::FontId(font_id),
|
||||||
|
extra: glow_glyph::Extra::default(),
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut mb = self.measure_brush.borrow_mut();
|
||||||
|
|
||||||
|
// The underlying type is FontArc, so clones are cheap.
|
||||||
|
use 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(
|
||||||
|
|glow_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) {
|
pub fn trim_measurement_cache(&mut self) {
|
||||||
// TODO: We should probably use a `GlyphCalculator` for this. However,
|
// TODO: We should probably use a `GlyphCalculator` for this. However,
|
||||||
// it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
|
// it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Write a graphics backend.
|
//! Write a graphics backend.
|
||||||
use iced_native::image;
|
use iced_native::image;
|
||||||
use iced_native::svg;
|
use iced_native::svg;
|
||||||
use iced_native::{Font, Size};
|
use iced_native::{Font, HitTestResult, Point, Size};
|
||||||
|
|
||||||
/// The graphics backend of a [`Renderer`].
|
/// The graphics backend of a [`Renderer`].
|
||||||
///
|
///
|
||||||
|
|
@ -43,6 +43,23 @@ pub trait Text {
|
||||||
font: Font,
|
font: Font,
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
) -> (f32, f32);
|
) -> (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: Font,
|
||||||
|
bounds: Size,
|
||||||
|
point: Point,
|
||||||
|
nearest_only: bool,
|
||||||
|
) -> HitTestResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A graphics backend that supports image rendering.
|
/// A graphics backend that supports image rendering.
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,6 @@ pub use transformation::Transformation;
|
||||||
pub use viewport::Viewport;
|
pub use viewport::Viewport;
|
||||||
|
|
||||||
pub use iced_native::{
|
pub use iced_native::{
|
||||||
Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size,
|
Background, Color, Font, HitTestResult, HorizontalAlignment, Point,
|
||||||
Vector, VerticalAlignment,
|
Rectangle, Size, Vector, VerticalAlignment,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ use crate::{Primitive, Renderer};
|
||||||
use iced_native::mouse;
|
use iced_native::mouse;
|
||||||
use iced_native::text;
|
use iced_native::text;
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
Color, Font, HorizontalAlignment, Rectangle, Size, VerticalAlignment,
|
Color, Font, HitTestResult, HorizontalAlignment, Point, Rectangle, Size,
|
||||||
|
VerticalAlignment,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A paragraph of text.
|
/// A paragraph of text.
|
||||||
|
|
@ -35,6 +36,25 @@ where
|
||||||
.measure(content, f32::from(size), font, bounds)
|
.measure(content, f32::from(size), font, bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hit_test(
|
||||||
|
&self,
|
||||||
|
content: &str,
|
||||||
|
size: f32,
|
||||||
|
font: Font,
|
||||||
|
bounds: Size,
|
||||||
|
point: Point,
|
||||||
|
nearest_only: bool,
|
||||||
|
) -> HitTestResult {
|
||||||
|
self.backend().hit_test(
|
||||||
|
content,
|
||||||
|
size,
|
||||||
|
font,
|
||||||
|
bounds,
|
||||||
|
point,
|
||||||
|
nearest_only,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&mut self,
|
&mut self,
|
||||||
defaults: &Self::Defaults,
|
defaults: &Self::Defaults,
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ mod debug;
|
||||||
mod debug;
|
mod debug;
|
||||||
|
|
||||||
pub use iced_core::{
|
pub use iced_core::{
|
||||||
menu, Align, Background, Color, Font, HorizontalAlignment, Length, Menu,
|
menu, Align, Background, Color, Font, HitTestResult, HorizontalAlignment,
|
||||||
Padding, Point, Rectangle, Size, Vector, VerticalAlignment,
|
Length, Menu, Padding, Point, Rectangle, Size, Vector, VerticalAlignment,
|
||||||
};
|
};
|
||||||
pub use iced_futures::{executor, futures, Command};
|
pub use iced_futures::{executor, futures, Command};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
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,
|
HitTestResult, HorizontalAlignment, Layout, Padding, Point, Rectangle,
|
||||||
VerticalAlignment,
|
Renderer, Size, Vector, VerticalAlignment,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A renderer that does nothing.
|
/// A renderer that does nothing.
|
||||||
|
|
@ -67,6 +67,18 @@ impl text::Renderer for Null {
|
||||||
(0.0, 20.0)
|
(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(
|
fn draw(
|
||||||
&mut self,
|
&mut self,
|
||||||
_defaults: &Self::Defaults,
|
_defaults: &Self::Defaults,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Write some text for your users to read.
|
//! Write some text for your users to read.
|
||||||
use crate::{
|
use crate::{
|
||||||
layout, Color, Element, Hasher, HorizontalAlignment, Layout, Length, Point,
|
layout, Color, Element, Hasher, HitTestResult, HorizontalAlignment, Layout,
|
||||||
Rectangle, Size, VerticalAlignment, Widget,
|
Length, Point, Rectangle, Size, VerticalAlignment, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
@ -179,6 +179,23 @@ pub trait Renderer: crate::Renderer {
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
) -> (f32, f32);
|
) -> (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.
|
/// Draws a [`Text`] fragment.
|
||||||
///
|
///
|
||||||
/// It receives:
|
/// It receives:
|
||||||
|
|
|
||||||
|
|
@ -707,15 +707,15 @@ pub trait Renderer: text::Renderer + Sized {
|
||||||
|
|
||||||
let offset = self.offset(text_bounds, font, size, &value, &state);
|
let offset = self.offset(text_bounds, font, size, &value, &state);
|
||||||
|
|
||||||
find_cursor_position(
|
self.hit_test(
|
||||||
self,
|
&value.to_string(),
|
||||||
&value,
|
size.into(),
|
||||||
font,
|
font,
|
||||||
size,
|
Size::INFINITY,
|
||||||
x + offset,
|
Point::new(x + offset, text_bounds.height / 2.0),
|
||||||
0,
|
true,
|
||||||
value.len(),
|
|
||||||
)
|
)
|
||||||
|
.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 {
|
mod platform {
|
||||||
use crate::keyboard;
|
use crate::keyboard;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ use iced_graphics::font;
|
||||||
use iced_graphics::layer::Layer;
|
use iced_graphics::layer::Layer;
|
||||||
use iced_graphics::{Primitive, Viewport};
|
use iced_graphics::{Primitive, Viewport};
|
||||||
use iced_native::mouse;
|
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"))]
|
#[cfg(any(feature = "image_rs", feature = "svg"))]
|
||||||
use crate::image;
|
use crate::image;
|
||||||
|
|
@ -274,6 +276,25 @@ impl backend::Text for Backend {
|
||||||
) -> (f32, f32) {
|
) -> (f32, f32) {
|
||||||
self.text_pipeline.measure(contents, size, font, bounds)
|
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")]
|
#[cfg(feature = "image_rs")]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::Transformation;
|
use crate::Transformation;
|
||||||
use iced_graphics::font;
|
use iced_graphics::font;
|
||||||
|
use iced_native::HitTestResult;
|
||||||
use std::{cell::RefCell, collections::HashMap};
|
use std::{cell::RefCell, collections::HashMap};
|
||||||
use wgpu_glyph::ab_glyph;
|
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) {
|
pub fn trim_measurement_cache(&mut self) {
|
||||||
// TODO: We should probably use a `GlyphCalculator` for this. However,
|
// TODO: We should probably use a `GlyphCalculator` for this. However,
|
||||||
// it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
|
// it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue