Move the text_input_byte_offset_for_position from the FontMetrics to the Window

In preparation of having mutiple-lines TextInput, we will need to give more
data to this function so it no longer belong in FontMetrics

Also remove the unused FontMetric::line_height()
This commit is contained in:
Olivier Goffart 2021-08-31 11:55:32 +02:00 committed by Olivier Goffart
parent e1be599bc0
commit c1fc242a9a
7 changed files with 77 additions and 64 deletions

View file

@ -156,13 +156,6 @@ pub trait FontMetrics {
/// Returns the size of the given string in logical pixels. /// Returns the size of the given string in logical pixels.
/// When set, `max_width` means that one need to wrap the text so it does not go further than that /// When set, `max_width` means that one need to wrap the text so it does not go further than that
fn text_size(&self, text: &str, max_width: Option<f32>) -> Size; fn text_size(&self, text: &str, max_width: Option<f32>) -> Size;
/// Returns the height of a line of text.
fn line_height(&self) -> f32;
/// Returns the (UTF-8) byte offset in the given text that refers to the character that contributed to
/// the glyph cluster that's visually nearest to the given x coordinate. This is used for hit-testing,
/// for example when receiving a mouse click into a text field. Then this function returns the "cursor"
/// position.
fn text_offset_for_x_position(&self, text: &str, x: f32) -> usize;
} }
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]

View file

@ -302,16 +302,9 @@ impl Item for TextInput {
if !self.enabled() { if !self.enabled() {
return InputEventResult::EventIgnored; return InputEventResult::EventIgnored;
} }
let text = self.text();
let font_metrics = window.font_metrics(
&self.cached_rendering_data,
&|| self.unresolved_font_request(),
Self::FIELD_OFFSETS.text.apply_pin(self),
);
match event { match event {
MouseEvent::MousePressed { pos } => { MouseEvent::MousePressed { pos } => {
let clicked_offset = font_metrics.text_offset_for_x_position(&text, pos.x) as i32; let clicked_offset = window.text_input_byte_offset_for_position(self, pos) as i32;
self.as_ref().pressed.set(true); self.as_ref().pressed.set(true);
self.as_ref().anchor_position.set(clicked_offset); self.as_ref().anchor_position.set(clicked_offset);
self.as_ref().cursor_position.set(clicked_offset); self.as_ref().cursor_position.set(clicked_offset);
@ -325,7 +318,7 @@ impl Item for TextInput {
MouseEvent::MouseMoved { pos } => { MouseEvent::MouseMoved { pos } => {
if self.as_ref().pressed.get() { if self.as_ref().pressed.get() {
let clicked_offset = let clicked_offset =
font_metrics.text_offset_for_x_position(&text, pos.x) as i32; window.text_input_byte_offset_for_position(self, pos) as i32;
self.as_ref().cursor_position.set(clicked_offset); self.as_ref().cursor_position.set(clicked_offset);
} }
} }

View file

@ -66,6 +66,16 @@ pub trait PlatformWindow {
reference_text: Pin<&crate::properties::Property<SharedString>>, reference_text: Pin<&crate::properties::Property<SharedString>>,
) -> Box<dyn crate::graphics::FontMetrics>; ) -> Box<dyn crate::graphics::FontMetrics>;
/// Returns the (UTF-8) byte offset in the text property that refers to the character that contributed to
/// the glyph cluster that's visually nearest to the given coordinate. This is used for hit-testing,
/// for example when receiving a mouse click into a text field. Then this function returns the "cursor"
/// position.
fn text_input_byte_offset_for_position(
&self,
text_input: Pin<&crate::items::TextInput>,
pos: Point,
) -> usize;
/// Return self as any so the backend can upcast /// Return self as any so the backend can upcast
fn as_any(&self) -> &dyn core::any::Any; fn as_any(&self) -> &dyn core::any::Any;
} }

View file

@ -348,23 +348,6 @@ impl FontMetricsTrait for FontMetrics {
max_width.map(|x| x * self.scale_factor), max_width.map(|x| x * self.scale_factor),
) / self.scale_factor ) / self.scale_factor
} }
fn line_height(&self) -> f32 {
self.font.height()
}
fn text_offset_for_x_position(&self, text: &str, x: f32) -> usize {
let x = x * self.scale_factor;
let metrics = self.font.measure(self.letter_spacing.unwrap_or_default(), text);
let mut current_x = 0.;
for glyph in metrics.glyphs {
if current_x + glyph.advance_x / 2. >= x {
return glyph.byte_index;
}
current_x += glyph.advance_x;
}
text.len()
}
} }
pub struct FontCache { pub struct FontCache {

View file

@ -556,6 +556,40 @@ impl PlatformWindow for GraphicsWindow {
)) ))
} }
fn text_input_byte_offset_for_position(
&self,
text_input: Pin<&sixtyfps_corelib::items::TextInput>,
pos: Point,
) -> usize {
let scale_factor = self.scale_factor();
let cache_data = text_input
.cached_rendering_data
.get_or_update(&self.graphics_cache, || {
Some(crate::ItemGraphicsCacheEntry::Font(crate::fonts::FONT_CACHE.with(|cache| {
cache.borrow_mut().font(
text_input
.unresolved_font_request()
.merge(&self.default_font_properties.as_ref().get()),
scale_factor,
&text_input.text(),
)
})))
})
.unwrap();
let font = cache_data.as_font();
let x = pos.x * scale_factor;
let text = text_input.text();
let metrics = font.measure(text_input.letter_spacing(), &text);
let mut current_x = 0.;
for glyph in metrics.glyphs {
if current_x + glyph.advance_x / 2. >= x {
return glyph.byte_index;
}
current_x += glyph.advance_x;
}
text.len()
}
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
} }

View file

@ -1316,6 +1316,28 @@ impl PlatformWindow for QtWindow {
Box::new(get_font(unresolved_font_request_getter().merge(&self.default_font_properties()))) Box::new(get_font(unresolved_font_request_getter().merge(&self.default_font_properties())))
} }
fn text_input_byte_offset_for_position(
&self,
text_input: Pin<&sixtyfps_corelib::items::TextInput>,
pos: Point,
) -> usize {
let font: QFont =
get_font(text_input.unresolved_font_request().merge(&self.default_font_properties()));
let string = qttypes::QString::from(text_input.text().as_str());
let x: f32 = pos.x;
cpp! { unsafe [font as "QFont", string as "QString", x as "float"] -> usize as "long long" {
QTextLayout layout(string, font);
layout.beginLayout();
layout.createLine();
layout.endLayout();
if (layout.lineCount() == 0)
return 0;
auto cur = layout.lineAt(0).xToCursor(x);
// convert to an utf8 pos;
return QStringView(string).left(cur).toUtf8().size();
}}
}
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
} }
@ -1369,28 +1391,6 @@ impl sixtyfps_corelib::graphics::FontMetrics for QFont {
}}; }};
sixtyfps_corelib::graphics::Size::new(size.width as _, size.height as _) sixtyfps_corelib::graphics::Size::new(size.width as _, size.height as _)
} }
fn line_height(&self) -> f32 {
cpp! { unsafe [self as "const QFont*"]
-> f32 as "float"{
return QFontMetricsF(*self).height();
}}
}
fn text_offset_for_x_position(&self, text: &str, x: f32) -> usize {
let string = qttypes::QString::from(text);
cpp! { unsafe [self as "const QFont*", string as "QString", x as "float"] -> usize as "long long" {
QTextLayout layout(string, *self);
layout.beginLayout();
layout.createLine();
layout.endLayout();
if (layout.lineCount() == 0)
return 0;
auto cur = layout.lineAt(0).xToCursor(x);
// convert to an utf8 pos;
return QStringView(string).left(cur).toUtf8().size();
}}
}
} }
thread_local! { thread_local! {

View file

@ -18,7 +18,7 @@ You should use the `sixtyfps` crate instead.
use image::GenericImageView; use image::GenericImageView;
use sixtyfps_corelib::component::ComponentRc; use sixtyfps_corelib::component::ComponentRc;
use sixtyfps_corelib::graphics::{FontMetrics, Image, Size}; use sixtyfps_corelib::graphics::{FontMetrics, Image, Point, Size};
use sixtyfps_corelib::slice::Slice; use sixtyfps_corelib::slice::Slice;
use sixtyfps_corelib::window::{PlatformWindow, Window}; use sixtyfps_corelib::window::{PlatformWindow, Window};
use sixtyfps_corelib::{ImageInner, Property}; use sixtyfps_corelib::{ImageInner, Property};
@ -149,6 +149,14 @@ impl PlatformWindow for TestingWindow {
Box::new(TestingFontMetrics::default()) Box::new(TestingFontMetrics::default())
} }
fn text_input_byte_offset_for_position(
&self,
_text_input: Pin<&sixtyfps_corelib::items::TextInput>,
_pos: Point,
) -> usize {
0
}
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
} }
@ -161,14 +169,6 @@ impl FontMetrics for TestingFontMetrics {
fn text_size(&self, text: &str, _max_width: Option<f32>) -> Size { fn text_size(&self, text: &str, _max_width: Option<f32>) -> Size {
Size::new(text.len() as f32 * 10., 10.) Size::new(text.len() as f32 * 10., 10.)
} }
fn line_height(&self) -> f32 {
10.
}
fn text_offset_for_x_position(&self, _text: &str, _x: f32) -> usize {
0
}
} }
/// Initialize the testing backend. /// Initialize the testing backend.