mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-11 22:50:08 +00:00
experimental ability to insert inline elements in text
This commit is contained in:
@@ -26,8 +26,11 @@ use crate::{
|
||||
},
|
||||
font::{Font, FontGlyph, FontHeight, FontResource, BUILT_IN_FONT},
|
||||
style::{resource::StyleResource, StyledProperty},
|
||||
HorizontalAlignment, Thickness, VerticalAlignment,
|
||||
HorizontalAlignment, Thickness, UiNode, UserInterface, VerticalAlignment,
|
||||
};
|
||||
use fxhash::FxHashMap;
|
||||
use fyrox_core::pool::Handle;
|
||||
use fyrox_graph::SceneGraph;
|
||||
use fyrox_resource::state::{LoadError, ResourceState};
|
||||
pub use run::*;
|
||||
use std::{
|
||||
@@ -308,6 +311,11 @@ pub struct FormattedText {
|
||||
pub trim_text: InheritableVariable<bool>,
|
||||
}
|
||||
|
||||
pub struct InlineContext<'a> {
|
||||
pub children: &'a [Handle<UiNode>],
|
||||
pub ui: &'a UserInterface,
|
||||
}
|
||||
|
||||
impl FormattedText {
|
||||
pub fn font_at(&self, index: usize) -> FontResource {
|
||||
self.runs.font_at(index).unwrap_or_else(|| self.get_font())
|
||||
@@ -884,12 +892,51 @@ impl FormattedText {
|
||||
}
|
||||
|
||||
pub fn measure_and_arrange(&mut self) -> Vector2<f32> {
|
||||
let size = self.measure();
|
||||
self.arrange(self.constraint);
|
||||
let size = self.measure(None);
|
||||
self.arrange(self.constraint, None);
|
||||
size
|
||||
}
|
||||
|
||||
pub fn measure(&mut self) -> Vector2<f32> {
|
||||
fn wrap(
|
||||
&self,
|
||||
mut wrapper: impl TextWrapper,
|
||||
inline_bounds: &FxHashMap<usize, Vector2<f32>>,
|
||||
text: &[char],
|
||||
) {
|
||||
for (i, c) in text.iter().enumerate() {
|
||||
// Try wrap an inline first.
|
||||
if let Some(inline_bounds) = inline_bounds.get(&i) {
|
||||
wrapper.push(None, inline_bounds.x);
|
||||
}
|
||||
|
||||
// Then wrap the character.
|
||||
let advance = GlyphMetrics {
|
||||
font: &mut self.font_at(i).data_ref(),
|
||||
size: self.font_size_at(i),
|
||||
}
|
||||
.advance(*c);
|
||||
|
||||
wrapper.push(Some(*c), advance);
|
||||
}
|
||||
wrapper.finish();
|
||||
}
|
||||
|
||||
pub fn measure(&mut self, inline_ctx: Option<InlineContext>) -> Vector2<f32> {
|
||||
let mut inline_bounds = FxHashMap::default();
|
||||
if let Some(ref inline_ctx) = inline_ctx {
|
||||
for &inline in inline_ctx.children {
|
||||
let inline_ref = inline_ctx.ui.node(inline);
|
||||
inline_ctx
|
||||
.ui
|
||||
.measure_node(inline, Vector2::new(f32::INFINITY, f32::INFINITY));
|
||||
let desired_size = inline_ref.desired_size();
|
||||
let measured_inline = inline_bounds
|
||||
.entry(*inline_ref.column)
|
||||
.or_insert_with(|| desired_size);
|
||||
*measured_inline = measured_inline.sup(&desired_size);
|
||||
}
|
||||
}
|
||||
|
||||
let mut lines = std::mem::take(&mut self.lines);
|
||||
lines.clear();
|
||||
// Fail early if any font is not available.
|
||||
@@ -929,18 +976,10 @@ impl FormattedText {
|
||||
WrapMode::Word => wrap_mask(WordWrap::new(sink), self.text.len(), mask, advance),
|
||||
}
|
||||
} else {
|
||||
let source = self.text.iter().enumerate().map(|(i, c)| {
|
||||
let a = GlyphMetrics {
|
||||
font: &mut self.font_at(i).data_ref(),
|
||||
size: self.font_size_at(i),
|
||||
}
|
||||
.advance(*c);
|
||||
(*c, a)
|
||||
});
|
||||
match *self.wrap {
|
||||
WrapMode::NoWrap => wrap(NoWrap::new(sink), source),
|
||||
WrapMode::Letter => wrap(LetterWrap::new(sink), source),
|
||||
WrapMode::Word => wrap(WordWrap::new(sink), source),
|
||||
WrapMode::NoWrap => self.wrap(NoWrap::new(sink), &inline_bounds, &self.text),
|
||||
WrapMode::Letter => self.wrap(LetterWrap::new(sink), &inline_bounds, &self.text),
|
||||
WrapMode::Word => self.wrap(WordWrap::new(sink), &inline_bounds, &self.text),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -962,6 +1001,9 @@ impl FormattedText {
|
||||
}
|
||||
.ascender();
|
||||
line.height = line.height.max(h);
|
||||
if let Some(inline) = inline_bounds.get(&i) {
|
||||
line.height = line.height.max(inline.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.total_height += line.height + self.line_space();
|
||||
@@ -1010,7 +1052,18 @@ impl FormattedText {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn arrange(&mut self, constraint: Vector2<f32>) {
|
||||
pub fn arrange(&mut self, constraint: Vector2<f32>, inline_ctx: Option<InlineContext>) {
|
||||
let mut inlines = FxHashMap::default();
|
||||
if let Some(ref inline_ctx) = inline_ctx {
|
||||
for &inline in inline_ctx.children {
|
||||
let inline_ref = inline_ctx.ui.node(inline);
|
||||
inlines
|
||||
.entry(*inline_ref.column)
|
||||
.or_insert_with(Vec::default)
|
||||
.push(inline);
|
||||
}
|
||||
}
|
||||
|
||||
self.constraint = constraint;
|
||||
let constraint = Vector2::new(
|
||||
(self.constraint.x - (self.padding.left + self.padding.right)).max(0.0),
|
||||
@@ -1094,6 +1147,27 @@ impl FormattedText {
|
||||
x += metrics.newline_advance();
|
||||
}
|
||||
_ => {
|
||||
// Put inlines first (if any).
|
||||
if let Some(ref inline_ctx) = inline_ctx {
|
||||
if let Some(inlines) = inlines.get(&i) {
|
||||
let mut bounds = Vector2::zeros();
|
||||
for &inline in inlines {
|
||||
let inline_ref = inline_ctx.ui.node(inline);
|
||||
let desired_size = inline_ref.desired_size();
|
||||
inline_ctx.ui.arrange_node(
|
||||
inline,
|
||||
&Rect {
|
||||
position: Vector2::new(x, y),
|
||||
size: desired_size,
|
||||
},
|
||||
);
|
||||
bounds = bounds.sup(&desired_size);
|
||||
}
|
||||
x += bounds.x;
|
||||
}
|
||||
}
|
||||
|
||||
// Then the glyph.
|
||||
let y1 = y + line.height - metrics.ascender();
|
||||
let scale = self.super_sampling_scale;
|
||||
let (glyph, advance) =
|
||||
@@ -1126,20 +1200,9 @@ impl FormattedText {
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap<W, I>(mut wrapper: W, source: I)
|
||||
where
|
||||
W: TextWrapper,
|
||||
I: Iterator<Item = (char, f32)>,
|
||||
{
|
||||
for (character, advance) in source {
|
||||
wrapper.push(character, advance);
|
||||
}
|
||||
wrapper.finish();
|
||||
}
|
||||
|
||||
fn wrap_mask<W: TextWrapper>(mut wrapper: W, length: usize, mask_char: char, advance: f32) {
|
||||
for _ in 0..length {
|
||||
wrapper.push(mask_char, advance);
|
||||
wrapper.push(Some(mask_char), advance);
|
||||
}
|
||||
wrapper.finish();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
use std::ops::Range;
|
||||
|
||||
pub trait TextWrapper {
|
||||
fn push(&mut self, c: char, advance: f32);
|
||||
fn push(&mut self, c: Option<char>, advance: f32);
|
||||
fn finish(&mut self);
|
||||
}
|
||||
|
||||
@@ -53,16 +53,18 @@ impl<S> NoWrap<S> {
|
||||
}
|
||||
|
||||
impl<S: LineSink> TextWrapper for NoWrap<S> {
|
||||
fn push(&mut self, c: char, advance: f32) {
|
||||
if is_newline(c) {
|
||||
self.sink.push_line(self.start..self.position, self.width);
|
||||
self.start = self.position + 1; // Next like starts after the newline, so skip ahead by one.
|
||||
self.position += 1;
|
||||
self.width = 0.0;
|
||||
} else {
|
||||
self.position += 1;
|
||||
self.width += advance;
|
||||
fn push(&mut self, c: Option<char>, advance: f32) {
|
||||
if let Some(c) = c {
|
||||
if is_newline(c) {
|
||||
self.sink.push_line(self.start..self.position, self.width);
|
||||
self.start = self.position + 1; // Next like starts after the newline, so skip ahead by one.
|
||||
self.position += 1;
|
||||
self.width = 0.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.position += 1;
|
||||
self.width += advance;
|
||||
}
|
||||
fn finish(&mut self) {
|
||||
self.sink.push_line(self.start..self.position, self.width);
|
||||
@@ -88,7 +90,7 @@ impl<S> LetterWrap<S> {
|
||||
}
|
||||
|
||||
impl<S: LineSink> TextWrapper for LetterWrap<S> {
|
||||
fn push(&mut self, c: char, advance: f32) {
|
||||
fn push(&mut self, c: Option<char>, advance: f32) {
|
||||
if self.position != self.start && self.width + advance > self.sink.max_width() {
|
||||
self.sink.push_line(self.start..self.position, self.width);
|
||||
self.start = self.position;
|
||||
@@ -96,10 +98,12 @@ impl<S: LineSink> TextWrapper for LetterWrap<S> {
|
||||
}
|
||||
self.position += 1;
|
||||
self.width += advance;
|
||||
if is_newline(c) {
|
||||
self.sink.push_line(self.start..self.position, self.width);
|
||||
self.start = self.position; // Next line starts after the newline
|
||||
self.width = 0.0;
|
||||
if let Some(c) = c {
|
||||
if is_newline(c) {
|
||||
self.sink.push_line(self.start..self.position, self.width);
|
||||
self.start = self.position; // Next line starts after the newline
|
||||
self.width = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +142,7 @@ impl<S> WordWrap<S> {
|
||||
}
|
||||
|
||||
impl<S: LineSink> TextWrapper for WordWrap<S> {
|
||||
fn push(&mut self, c: char, advance: f32) {
|
||||
fn push(&mut self, c: Option<char>, advance: f32) {
|
||||
if self.position != self.start && self.width + advance > self.sink.max_width() {
|
||||
if self.start < self.word_start {
|
||||
self.sink
|
||||
@@ -156,20 +160,22 @@ impl<S: LineSink> TextWrapper for WordWrap<S> {
|
||||
}
|
||||
self.position += 1;
|
||||
self.width += advance;
|
||||
if is_newline(c) {
|
||||
self.sink.push_line(self.start..self.position, self.width);
|
||||
self.start = self.position;
|
||||
self.width = 0.0;
|
||||
// newline is not part of a word, so move word_start ahead.
|
||||
self.word_start = self.position;
|
||||
self.word_width = 0.0;
|
||||
} else if c.is_whitespace() {
|
||||
// We are not in a word, so move word_start ahead.
|
||||
self.word_start = self.position;
|
||||
self.word_width = 0.0;
|
||||
} else {
|
||||
// We are in a word, so leave word_start alone and increase word_width.
|
||||
self.word_width += advance;
|
||||
if let Some(c) = c {
|
||||
if is_newline(c) {
|
||||
self.sink.push_line(self.start..self.position, self.width);
|
||||
self.start = self.position;
|
||||
self.width = 0.0;
|
||||
// newline is not part of a word, so move word_start ahead.
|
||||
self.word_start = self.position;
|
||||
self.word_width = 0.0;
|
||||
} else if c.is_whitespace() {
|
||||
// We are not in a word, so move word_start ahead.
|
||||
self.word_start = self.position;
|
||||
self.word_width = 0.0;
|
||||
} else {
|
||||
// We are in a word, so leave word_start alone and increase word_width.
|
||||
self.word_width += advance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +211,7 @@ mod tests {
|
||||
|
||||
fn wrap<W: TextWrapper>(wrap: &mut W, text: &str) {
|
||||
for c in text.chars() {
|
||||
wrap.push(c, 1.0);
|
||||
wrap.push(Some(c), 1.0);
|
||||
}
|
||||
wrap.finish();
|
||||
}
|
||||
|
||||
+13
-3
@@ -23,6 +23,7 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use crate::formatted_text::InlineContext;
|
||||
use crate::{
|
||||
brush::Brush,
|
||||
core::{
|
||||
@@ -359,16 +360,25 @@ impl ConstructorProvider<UiNode, UserInterface> for Text {
|
||||
crate::define_widget_deref!(Text);
|
||||
|
||||
impl Control for Text {
|
||||
fn measure_override(&self, _: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
|
||||
self.formatted_text
|
||||
.borrow_mut()
|
||||
.set_super_sampling_scale(self.visual_max_scaling())
|
||||
.set_constraint(available_size)
|
||||
.measure()
|
||||
.measure(Some(InlineContext {
|
||||
children: &self.children,
|
||||
ui,
|
||||
}))
|
||||
}
|
||||
|
||||
fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
|
||||
self.formatted_text.borrow_mut().arrange(final_size);
|
||||
self.formatted_text.borrow_mut().arrange(
|
||||
final_size,
|
||||
Some(InlineContext {
|
||||
children: &self.children,
|
||||
ui,
|
||||
}),
|
||||
);
|
||||
self.widget.arrange_override(ui, final_size)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use crate::formatted_text::InlineContext;
|
||||
use crate::{
|
||||
brush::Brush,
|
||||
core::{
|
||||
@@ -694,7 +695,10 @@ impl TextBox {
|
||||
.unwrap_or_default();
|
||||
let mut text = self.formatted_text.borrow_mut();
|
||||
text.insert_str(&str, position);
|
||||
text.measure();
|
||||
text.measure(Some(InlineContext {
|
||||
children: &self.children,
|
||||
ui,
|
||||
}));
|
||||
drop(text);
|
||||
self.set_caret_position(
|
||||
self.char_index_to_position(position + str.chars().count())
|
||||
@@ -954,13 +958,22 @@ impl Control for TextBox {
|
||||
.borrow_mut()
|
||||
.set_super_sampling_scale(self.visual_max_scaling())
|
||||
.set_constraint(available_size)
|
||||
.measure();
|
||||
.measure(Some(InlineContext {
|
||||
children: &self.children,
|
||||
ui,
|
||||
}));
|
||||
let children_size = self.widget.measure_override(ui, available_size);
|
||||
text_size.sup(&children_size)
|
||||
}
|
||||
|
||||
fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
|
||||
self.formatted_text.borrow_mut().arrange(final_size);
|
||||
self.formatted_text.borrow_mut().arrange(
|
||||
final_size,
|
||||
Some(InlineContext {
|
||||
children: &self.children,
|
||||
ui,
|
||||
}),
|
||||
);
|
||||
self.widget.arrange_override(ui, final_size)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user