summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/keyboard.rs2
-rw-r--r--core/src/lib.rs1
-rw-r--r--core/src/mouse.rs2
-rw-r--r--core/src/text.rs29
-rw-r--r--glow/src/backend.rs20
-rw-r--r--glow/src/text.rs94
-rw-r--r--graphics/src/backend.rs20
-rw-r--r--graphics/src/widget/text.rs21
-rw-r--r--native/src/renderer/null.rs14
-rw-r--r--native/src/widget/text.rs19
-rw-r--r--native/src/widget/text_input.rs70
-rw-r--r--wgpu/src/backend.rs20
-rw-r--r--wgpu/src/text.rs92
13 files changed, 335 insertions, 69 deletions
diff --git a/core/src/keyboard.rs b/core/src/keyboard.rs
index cb64701a..6827a4db 100644
--- a/core/src/keyboard.rs
+++ b/core/src/keyboard.rs
@@ -1,4 +1,4 @@
-//! Reuse basic keyboard types.
+//! Listen to keyboard events.
mod event;
mod hotkey;
mod key_code;
diff --git a/core/src/lib.rs b/core/src/lib.rs
index c4288158..a0decdab 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -17,6 +17,7 @@
pub mod keyboard;
pub mod menu;
pub mod mouse;
+pub mod text;
mod align;
mod background;
diff --git a/core/src/mouse.rs b/core/src/mouse.rs
index 25ce6ac3..48214f65 100644
--- a/core/src/mouse.rs
+++ b/core/src/mouse.rs
@@ -1,4 +1,4 @@
-//! Reuse basic mouse types.
+//! Handle mouse events.
mod button;
mod event;
mod interaction;
diff --git a/core/src/text.rs b/core/src/text.rs
new file mode 100644
index 00000000..ded22eef
--- /dev/null
+++ b/core/src/text.rs
@@ -0,0 +1,29 @@
+//! Draw and interact with text.
+use crate::Vector;
+
+/// The result of hit testing on text.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Hit {
+ /// 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 Hit {
+ /// Computes the cursor position corresponding to this [`HitTestResult`] .
+ pub fn cursor(&self) -> usize {
+ match self {
+ Self::CharOffset(i) => *i,
+ Self::NearestCharOffset(i, delta) => {
+ if delta.x > f32::EPSILON {
+ i + 1
+ } else {
+ *i
+ }
+ }
+ }
+ }
+}
diff --git a/glow/src/backend.rs b/glow/src/backend.rs
index 1680fc00..37c0ac9d 100644
--- a/glow/src/backend.rs
+++ b/glow/src/backend.rs
@@ -2,6 +2,7 @@ use crate::quad;
use crate::text;
use crate::triangle;
use crate::{Settings, Transformation, Viewport};
+
use iced_graphics::backend;
use iced_graphics::font;
use iced_graphics::Layer;
@@ -211,6 +212,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,
+ ) -> text::Hit {
+ self.text_pipeline.hit_test(
+ contents,
+ size,
+ font,
+ bounds,
+ point,
+ nearest_only,
+ )
+ }
}
#[cfg(feature = "image")]
diff --git a/glow/src/text.rs b/glow/src/text.rs
index a4c39dfe..d6915d92 100644
--- a/glow/src/text.rs
+++ b/glow/src/text.rs
@@ -1,8 +1,12 @@
use crate::Transformation;
-use glow_glyph::ab_glyph;
+
use iced_graphics::font;
+
+use glow_glyph::ab_glyph;
use std::{cell::RefCell, collections::HashMap};
+pub use iced_native::text::Hit;
+
#[derive(Debug)]
pub struct Pipeline {
draw_brush: RefCell<glow_glyph::GlyphBrush>,
@@ -109,6 +113,94 @@ 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,
+ ) -> Hit {
+ 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 Hit::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
+ }
+ },
+ );
+
+ Hit::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.
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index ed1b9e08..656949c5 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -1,7 +1,8 @@
//! Write a graphics backend.
use iced_native::image;
use iced_native::svg;
-use iced_native::{Font, Size};
+use iced_native::text;
+use iced_native::{Font, Point, Size};
/// The graphics backend of a [`Renderer`].
///
@@ -43,6 +44,23 @@ pub trait Text {
font: Font,
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: Font,
+ bounds: Size,
+ point: Point,
+ nearest_only: bool,
+ ) -> text::Hit;
}
/// A graphics backend that supports image rendering.
diff --git a/graphics/src/widget/text.rs b/graphics/src/widget/text.rs
index 7e22e680..c235f254 100644
--- a/graphics/src/widget/text.rs
+++ b/graphics/src/widget/text.rs
@@ -4,7 +4,7 @@ use crate::{Primitive, Renderer};
use iced_native::mouse;
use iced_native::text;
use iced_native::{
- Color, Font, HorizontalAlignment, Rectangle, Size, VerticalAlignment,
+ Color, Font, HorizontalAlignment, Point, Rectangle, Size, VerticalAlignment,
};
/// A paragraph of text.
@@ -35,6 +35,25 @@ where
.measure(content, f32::from(size), font, bounds)
}
+ fn hit_test(
+ &self,
+ content: &str,
+ size: f32,
+ font: Font,
+ bounds: Size,
+ point: Point,
+ nearest_only: bool,
+ ) -> text::Hit {
+ self.backend().hit_test(
+ content,
+ size,
+ font,
+ bounds,
+ point,
+ nearest_only,
+ )
+ }
+
fn draw(
&mut self,
defaults: &Self::Defaults,
diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index bb57c163..b1a26c41 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -2,7 +2,7 @@ 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,
+ 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,
+ ) -> text::Hit {
+ text::Hit::NearestCharOffset(0, Vector::new(0., 0.))
+ }
+
fn draw(
&mut self,
_defaults: &Self::Defaults,
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index 6cc18e6c..adf6a74f 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -4,6 +4,8 @@ use crate::{
Rectangle, Size, VerticalAlignment, Widget,
};
+pub use iced_core::text::Hit;
+
use std::hash::Hash;
/// A paragraph of text.
@@ -179,6 +181,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,
+ ) -> Hit;
+
/// Draws a [`Text`] fragment.
///
/// It receives:
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index bb64d5b7..f1a7a1a0 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -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;
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 4f34045b..b31bf92c 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -2,6 +2,7 @@ use crate::quad;
use crate::text;
use crate::triangle;
use crate::{Settings, Transformation};
+
use iced_graphics::backend;
use iced_graphics::font;
use iced_graphics::layer::Layer;
@@ -274,6 +275,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,
+ ) -> text::Hit {
+ self.text_pipeline.hit_test(
+ contents,
+ size,
+ font,
+ bounds,
+ point,
+ nearest_only,
+ )
+ }
}
#[cfg(feature = "image_rs")]
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 2b5b94c9..ee49ee4b 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -1,8 +1,12 @@
use crate::Transformation;
+
use iced_graphics::font;
+
use std::{cell::RefCell, collections::HashMap};
use wgpu_glyph::ab_glyph;
+pub use iced_native::text::Hit;
+
#[derive(Debug)]
pub struct Pipeline {
draw_brush: RefCell<wgpu_glyph::GlyphBrush<()>>,
@@ -117,6 +121,94 @@ 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,
+ ) -> Hit {
+ 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 Hit::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
+ }
+ },
+ );
+
+ Hit::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.