mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
Introducing runs to FormattedText
This commit is contained in:
+55
-52
@@ -19,6 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
use crate::font::FontHeight;
|
||||
use crate::formatted_text::{DrawValueLayer, GlyphDrawValues};
|
||||
use crate::style::resource::StyleResource;
|
||||
use crate::{
|
||||
brush::Brush,
|
||||
@@ -949,48 +950,55 @@ impl DrawingContext {
|
||||
material: &MaterialResource,
|
||||
formatted_text: &FormattedText,
|
||||
) {
|
||||
let font = formatted_text.get_font();
|
||||
|
||||
#[inline(always)]
|
||||
fn draw(
|
||||
formatted_text: &FormattedText,
|
||||
layer: DrawValueLayer,
|
||||
ctx: &mut DrawingContext,
|
||||
clip_bounds: Rect<f32>,
|
||||
position: Vector2<f32>,
|
||||
dilation: f32,
|
||||
offset: Vector2<f32>,
|
||||
brush: Brush,
|
||||
font: &FontResource,
|
||||
material: &MaterialResource,
|
||||
) {
|
||||
let Some(mut current_page_index) = formatted_text
|
||||
.get_glyphs()
|
||||
.first()
|
||||
.map(|g| g.atlas_page_index)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut current_draw_values = None;
|
||||
for element in formatted_text.get_glyphs() {
|
||||
// If we've switched to another atlas page, commit the text and start a new batch.
|
||||
if current_page_index != element.atlas_page_index {
|
||||
ctx.commit(
|
||||
clip_bounds,
|
||||
brush.clone(),
|
||||
CommandTexture::Font {
|
||||
font: font.clone(),
|
||||
page_index: current_page_index,
|
||||
// Use font size scaled by super sampling scaling to pick correct atlas
|
||||
// page.
|
||||
height: FontHeight::from(formatted_text.super_sampled_font_size()),
|
||||
},
|
||||
material,
|
||||
None,
|
||||
);
|
||||
current_page_index = element.atlas_page_index;
|
||||
if let DrawValueLayer::Shadow = layer {
|
||||
if !formatted_text.shadow_at(element.source_char_index) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let draw_values = formatted_text.get_glyph_draw_values(layer, element);
|
||||
if current_draw_values.is_none() {
|
||||
current_draw_values = Some(draw_values)
|
||||
} else if current_draw_values.as_ref() != Some(&draw_values) {
|
||||
// If the drawing values have changed, commit the text and start a new batch.
|
||||
let GlyphDrawValues {
|
||||
atlas_page_index: page_index,
|
||||
font,
|
||||
brush,
|
||||
height,
|
||||
} = current_draw_values.unwrap();
|
||||
let texture = CommandTexture::Font {
|
||||
font,
|
||||
page_index,
|
||||
height,
|
||||
};
|
||||
ctx.commit(clip_bounds, brush, texture, material, None);
|
||||
current_draw_values = Some(draw_values);
|
||||
}
|
||||
|
||||
let bounds = element.bounds;
|
||||
let dilation = match layer {
|
||||
DrawValueLayer::Main => 0.0,
|
||||
DrawValueLayer::Shadow => {
|
||||
formatted_text.shadow_dilation_at(element.source_char_index)
|
||||
}
|
||||
};
|
||||
let offset = match layer {
|
||||
DrawValueLayer::Main => Vector2::default(),
|
||||
DrawValueLayer::Shadow => {
|
||||
formatted_text.shadow_offset_at(element.source_char_index)
|
||||
}
|
||||
};
|
||||
|
||||
let final_bounds = Rect::new(
|
||||
position.x + bounds.x() + offset.x,
|
||||
@@ -1003,46 +1011,41 @@ impl DrawingContext {
|
||||
ctx.push_rect_filled(&final_bounds, Some(&element.tex_coords));
|
||||
}
|
||||
|
||||
// Commit the rest.
|
||||
ctx.commit(
|
||||
clip_bounds,
|
||||
if let Some(GlyphDrawValues {
|
||||
atlas_page_index: page_index,
|
||||
font,
|
||||
brush,
|
||||
CommandTexture::Font {
|
||||
font: font.clone(),
|
||||
page_index: current_page_index,
|
||||
// Use font size scaled by super sampling scaling to pick correct atlas
|
||||
// page.
|
||||
height: FontHeight::from(formatted_text.super_sampled_font_size()),
|
||||
},
|
||||
material,
|
||||
None,
|
||||
);
|
||||
height,
|
||||
}) = current_draw_values
|
||||
{
|
||||
let texture = CommandTexture::Font {
|
||||
font,
|
||||
page_index,
|
||||
height,
|
||||
};
|
||||
// Commit the rest.
|
||||
ctx.commit(clip_bounds, brush, texture, material, None);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw shadow, if any.
|
||||
if *formatted_text.shadow {
|
||||
if *formatted_text.shadow || !formatted_text.runs.is_empty() {
|
||||
draw(
|
||||
formatted_text,
|
||||
DrawValueLayer::Shadow,
|
||||
self,
|
||||
clip_bounds,
|
||||
position,
|
||||
*formatted_text.shadow_dilation,
|
||||
*formatted_text.shadow_offset,
|
||||
(*formatted_text.shadow_brush).clone(),
|
||||
&font,
|
||||
material,
|
||||
);
|
||||
}
|
||||
|
||||
draw(
|
||||
formatted_text,
|
||||
DrawValueLayer::Main,
|
||||
self,
|
||||
clip_bounds,
|
||||
position,
|
||||
0.0,
|
||||
Default::default(),
|
||||
formatted_text.brush(),
|
||||
&font,
|
||||
material,
|
||||
);
|
||||
}
|
||||
|
||||
+184
-39
@@ -24,16 +24,18 @@ use crate::{
|
||||
algebra::Vector2, color::Color, math::Rect, reflect::prelude::*, uuid_provider,
|
||||
variable::InheritableVariable, visitor::prelude::*,
|
||||
},
|
||||
font::{Font, FontGlyph, FontResource},
|
||||
font::{Font, FontGlyph, FontHeight, FontResource},
|
||||
style::StyledProperty,
|
||||
HorizontalAlignment, VerticalAlignment,
|
||||
};
|
||||
pub use run::*;
|
||||
use std::ops::Range;
|
||||
use strum_macros::{AsRefStr, EnumString, VariantNames};
|
||||
|
||||
mod textwrapper;
|
||||
use textwrapper::*;
|
||||
|
||||
mod run;
|
||||
mod textwrapper;
|
||||
|
||||
/// Defines a position in the text. It is just a coordinates of a character in text.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, Visit, Reflect)]
|
||||
pub struct Position {
|
||||
@@ -49,6 +51,7 @@ pub struct TextGlyph {
|
||||
pub bounds: Rect<f32>,
|
||||
pub tex_coords: [Vector2<f32>; 4],
|
||||
pub atlas_page_index: usize,
|
||||
pub source_char_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
@@ -160,6 +163,7 @@ fn build_glyph(
|
||||
metrics: &mut GlyphMetrics,
|
||||
mut x: f32,
|
||||
mut y: f32,
|
||||
source_char_index: usize,
|
||||
character: char,
|
||||
prev_character: Option<char>,
|
||||
super_sampling_scale: f32,
|
||||
@@ -187,6 +191,7 @@ fn build_glyph(
|
||||
bounds: rect,
|
||||
tex_coords: glyph.tex_coords,
|
||||
atlas_page_index: glyph.page_index,
|
||||
source_char_index,
|
||||
};
|
||||
let advance = glyph.advance
|
||||
+ prev_character
|
||||
@@ -201,6 +206,7 @@ fn build_glyph(
|
||||
bounds: rect,
|
||||
tex_coords: [Vector2::default(); 4],
|
||||
atlas_page_index: 0,
|
||||
source_char_index,
|
||||
};
|
||||
(text_glyph, rect.w())
|
||||
}
|
||||
@@ -257,9 +263,40 @@ pub struct FormattedText {
|
||||
pub shadow_brush: InheritableVariable<Brush>,
|
||||
pub shadow_dilation: InheritableVariable<f32>,
|
||||
pub shadow_offset: InheritableVariable<Vector2<f32>>,
|
||||
#[visit(optional)]
|
||||
pub runs: RunSet,
|
||||
}
|
||||
|
||||
impl FormattedText {
|
||||
pub fn font_at(&self, index: usize) -> FontResource {
|
||||
self.runs.font_at(index).unwrap_or_else(|| self.get_font())
|
||||
}
|
||||
pub fn font_size_at(&self, index: usize) -> f32 {
|
||||
self.runs
|
||||
.font_size_at(index)
|
||||
.unwrap_or_else(|| self.font_size().property)
|
||||
}
|
||||
pub fn brush_at(&self, index: usize) -> Brush {
|
||||
self.runs.brush_at(index).unwrap_or_else(|| self.brush())
|
||||
}
|
||||
pub fn shadow_at(&self, index: usize) -> bool {
|
||||
self.runs.shadow_at(index).unwrap_or(*self.shadow)
|
||||
}
|
||||
pub fn shadow_brush_at(&self, index: usize) -> Brush {
|
||||
self.runs
|
||||
.shadow_brush_at(index)
|
||||
.unwrap_or_else(|| self.shadow_brush.clone_inner())
|
||||
}
|
||||
pub fn shadow_dilation_at(&self, index: usize) -> f32 {
|
||||
self.runs
|
||||
.shadow_dilation_at(index)
|
||||
.unwrap_or(*self.shadow_dilation)
|
||||
}
|
||||
pub fn shadow_offset_at(&self, index: usize) -> Vector2<f32> {
|
||||
self.runs
|
||||
.shadow_offset_at(index)
|
||||
.unwrap_or(*self.shadow_offset)
|
||||
}
|
||||
pub fn nearest_valid_position(&self, start: Position) -> Position {
|
||||
if self.lines.is_empty() {
|
||||
return Position::default();
|
||||
@@ -476,6 +513,31 @@ impl FormattedText {
|
||||
&self.glyphs
|
||||
}
|
||||
|
||||
pub fn get_glyph_draw_values(
|
||||
&self,
|
||||
layer: DrawValueLayer,
|
||||
glyph: &TextGlyph,
|
||||
) -> GlyphDrawValues {
|
||||
let atlas_page_index = glyph.atlas_page_index;
|
||||
let i = glyph.source_char_index;
|
||||
let font = self.font_at(i);
|
||||
let height = FontHeight::from(self.font_size_at(i) * self.super_sampling_scale);
|
||||
match layer {
|
||||
DrawValueLayer::Main => GlyphDrawValues {
|
||||
atlas_page_index,
|
||||
font,
|
||||
brush: self.brush_at(i),
|
||||
height,
|
||||
},
|
||||
DrawValueLayer::Shadow => GlyphDrawValues {
|
||||
atlas_page_index,
|
||||
font,
|
||||
brush: self.shadow_brush_at(i),
|
||||
height,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_font(&self) -> FontResource {
|
||||
(*self.font).clone()
|
||||
}
|
||||
@@ -637,23 +699,18 @@ impl FormattedText {
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Vector2<f32> {
|
||||
let mut font_state = self.font.state();
|
||||
let Some(font) = font_state.data() else {
|
||||
return Default::default();
|
||||
};
|
||||
let mut metrics = GlyphMetrics {
|
||||
font,
|
||||
size: **self.font_size(),
|
||||
};
|
||||
let line_height: f32 = metrics.ascender();
|
||||
|
||||
self.lines.clear();
|
||||
let mut lines = std::mem::take(&mut self.lines);
|
||||
lines.clear();
|
||||
let sink = WrapSink {
|
||||
lines: &mut self.lines,
|
||||
lines: &mut lines,
|
||||
max_width: self.constraint.x,
|
||||
};
|
||||
if let Some(mask) = *self.mask_char {
|
||||
let advance = metrics.advance(mask);
|
||||
let advance = GlyphMetrics {
|
||||
font: &mut self.font.data_ref(),
|
||||
size: **self.font_size,
|
||||
}
|
||||
.advance(mask);
|
||||
match *self.wrap {
|
||||
WrapMode::NoWrap => wrap_mask(NoWrap::new(sink), self.text.len(), mask, advance),
|
||||
WrapMode::Letter => wrap_mask(
|
||||
@@ -665,16 +722,24 @@ 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), &mut metrics, self.text.as_slice()),
|
||||
WrapMode::Letter => wrap(LetterWrap::new(sink), &mut metrics, self.text.as_slice()),
|
||||
WrapMode::Word => wrap(WordWrap::new(sink), &mut metrics, self.text.as_slice()),
|
||||
WrapMode::NoWrap => wrap(NoWrap::new(sink), source),
|
||||
WrapMode::Letter => wrap(LetterWrap::new(sink), source),
|
||||
WrapMode::Word => wrap(WordWrap::new(sink), source),
|
||||
}
|
||||
}
|
||||
|
||||
let total_height = line_height * self.lines.len() as f32;
|
||||
let mut total_height = 0.0;
|
||||
// Align lines according to desired alignment.
|
||||
for line in self.lines.iter_mut() {
|
||||
for line in lines.iter_mut() {
|
||||
match *self.horizontal_alignment {
|
||||
HorizontalAlignment::Left => line.x_offset = 0.0,
|
||||
HorizontalAlignment::Center => {
|
||||
@@ -694,6 +759,26 @@ impl FormattedText {
|
||||
HorizontalAlignment::Stretch => line.x_offset = 0.0,
|
||||
}
|
||||
}
|
||||
// Calculate line height
|
||||
for line in lines.iter_mut() {
|
||||
if self.mask_char.is_some() || self.runs.is_empty() {
|
||||
line.height = GlyphMetrics {
|
||||
font: &mut self.font.data_ref(),
|
||||
size: **self.font_size,
|
||||
}
|
||||
.ascender();
|
||||
} else {
|
||||
for i in line.begin..line.end {
|
||||
let h = GlyphMetrics {
|
||||
font: &mut self.font_at(i).data_ref(),
|
||||
size: self.font_size_at(i),
|
||||
}
|
||||
.ascender();
|
||||
line.height = line.height.max(h);
|
||||
}
|
||||
}
|
||||
total_height += line.height;
|
||||
}
|
||||
|
||||
// Generate glyphs for each text line.
|
||||
self.glyphs.clear();
|
||||
@@ -716,29 +801,40 @@ impl FormattedText {
|
||||
}
|
||||
VerticalAlignment::Stretch => 0.0,
|
||||
};
|
||||
|
||||
let mut y: f32 = cursor_y_start.floor();
|
||||
for line in self.lines.iter_mut() {
|
||||
for line in lines.iter_mut() {
|
||||
let mut x = line.x_offset.floor();
|
||||
if let Some(mask) = *self.mask_char {
|
||||
let mut prev = None;
|
||||
let mut metrics = GlyphMetrics {
|
||||
font: &mut self.font.data_ref(),
|
||||
size: **self.font_size,
|
||||
};
|
||||
for c in std::iter::repeat_n(mask, line.len()) {
|
||||
let (glyph, advance) =
|
||||
build_glyph(&mut metrics, x, y, c, prev, self.super_sampling_scale);
|
||||
build_glyph(&mut metrics, x, y, 0, c, prev, self.super_sampling_scale);
|
||||
self.glyphs.push(glyph);
|
||||
x += advance;
|
||||
prev = Some(c);
|
||||
}
|
||||
} else {
|
||||
let mut prev = None;
|
||||
for c in self.text.iter().take(line.end).skip(line.begin).cloned() {
|
||||
for (i, &c) in self.text.iter().enumerate().take(line.end).skip(line.begin) {
|
||||
let font = self.font_at(i);
|
||||
let font = &mut font.data_ref();
|
||||
let mut metrics = GlyphMetrics {
|
||||
font,
|
||||
size: self.font_size_at(i),
|
||||
};
|
||||
match c {
|
||||
'\n' => {
|
||||
x += metrics.newline_advance();
|
||||
}
|
||||
_ => {
|
||||
let y1 = y + line.height - metrics.ascender();
|
||||
let scale = self.super_sampling_scale;
|
||||
let (glyph, advance) =
|
||||
build_glyph(&mut metrics, x, y, c, prev, self.super_sampling_scale);
|
||||
build_glyph(&mut metrics, x, y1, i, c, prev, scale);
|
||||
self.glyphs.push(glyph);
|
||||
x += advance;
|
||||
}
|
||||
@@ -746,26 +842,56 @@ impl FormattedText {
|
||||
prev = Some(c);
|
||||
}
|
||||
}
|
||||
line.height = line_height;
|
||||
line.y_offset = y;
|
||||
y += line_height;
|
||||
y += line.height;
|
||||
}
|
||||
|
||||
let size_x = self
|
||||
.lines
|
||||
.iter()
|
||||
.map(|line| line.width)
|
||||
.max_by(f32::total_cmp)
|
||||
.unwrap_or_default();
|
||||
// Minus here is because descender has negative value.
|
||||
let size_y = total_height - metrics.descender();
|
||||
let size_x = if self.constraint.x.is_finite() {
|
||||
self.constraint.x
|
||||
} else {
|
||||
lines
|
||||
.iter()
|
||||
.map(|line| line.width)
|
||||
.max_by(f32::total_cmp)
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let size_y = if self.constraint.y.is_finite() {
|
||||
self.constraint.y
|
||||
} else {
|
||||
let descender = if self.mask_char.is_some() || self.runs.is_empty() {
|
||||
GlyphMetrics {
|
||||
font: &mut self.font.data_ref(),
|
||||
size: **self.font_size,
|
||||
}
|
||||
.descender()
|
||||
} else if let Some(line) = self.lines.last() {
|
||||
(line.begin..line.end)
|
||||
.map(|i| {
|
||||
GlyphMetrics {
|
||||
font: &mut self.font_at(i).data_ref(),
|
||||
size: self.font_size_at(i),
|
||||
}
|
||||
.descender()
|
||||
})
|
||||
.min_by(f32::total_cmp)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
// Minus here is because descender has negative value.
|
||||
total_height - descender
|
||||
};
|
||||
self.lines = lines;
|
||||
Vector2::new(size_x, size_y)
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap<W: TextWrapper>(mut wrapper: W, metrics: &mut GlyphMetrics, text: &[char]) {
|
||||
for &character in text.iter() {
|
||||
let advance = metrics.advance(character);
|
||||
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();
|
||||
@@ -793,6 +919,7 @@ pub struct FormattedTextBuilder {
|
||||
shadow_offset: Vector2<f32>,
|
||||
font_size: StyledProperty<f32>,
|
||||
super_sampling_scaling: f32,
|
||||
runs: Vec<Run>,
|
||||
}
|
||||
|
||||
impl FormattedTextBuilder {
|
||||
@@ -813,6 +940,7 @@ impl FormattedTextBuilder {
|
||||
shadow_offset: Vector2::new(1.0, 1.0),
|
||||
font_size: 14.0f32.into(),
|
||||
super_sampling_scaling: 1.0,
|
||||
runs: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,6 +1015,22 @@ impl FormattedTextBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds the given run to the text to set the style for a portion of the text.
|
||||
/// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
|
||||
/// sets a property that conflicts with an earlier run.
|
||||
pub fn with_run(mut self, run: Run) -> Self {
|
||||
self.runs.push(run);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds multiple runs to the text to set the style of portions of the text.
|
||||
/// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
|
||||
/// sets a property that conflicts with an earlier run.
|
||||
pub fn with_runs<I: IntoIterator<Item = Run>>(mut self, runs: I) -> Self {
|
||||
self.runs.extend(runs);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> FormattedText {
|
||||
FormattedText {
|
||||
text: self.text.chars().collect::<Vec<char>>().into(),
|
||||
@@ -905,6 +1049,7 @@ impl FormattedTextBuilder {
|
||||
font: self.font.into(),
|
||||
shadow_dilation: self.shadow_dilation.into(),
|
||||
shadow_offset: self.shadow_offset.into(),
|
||||
runs: self.runs.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::font::FontHeight;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Default, Reflect)]
|
||||
pub struct RunSet(Vec<Run>);
|
||||
|
||||
impl From<&[Run]> for RunSet {
|
||||
fn from(value: &[Run]) -> Self {
|
||||
Self(value.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Run>> for RunSet {
|
||||
fn from(value: Vec<Run>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for RunSet {
|
||||
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
|
||||
self.0.visit(name, visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for RunSet {
|
||||
type Target = Vec<Run>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for RunSet {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl RunSet {
|
||||
pub fn font_at(&self, index: usize) -> Option<FontResource> {
|
||||
for run in self.0.iter().rev() {
|
||||
if run.range.contains(&(index as u32)) && run.font().is_some() {
|
||||
return Some(run.font().unwrap().clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn font_size_at(&self, index: usize) -> Option<f32> {
|
||||
for run in self.0.iter().rev() {
|
||||
if run.range.contains(&(index as u32)) && run.font_size().is_some() {
|
||||
return Some(run.font_size().unwrap());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn brush_at(&self, index: usize) -> Option<Brush> {
|
||||
for run in self.0.iter().rev() {
|
||||
if run.range.contains(&(index as u32)) && run.brush().is_some() {
|
||||
return Some(run.brush().unwrap().clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn shadow_at(&self, index: usize) -> Option<bool> {
|
||||
for run in self.0.iter().rev() {
|
||||
if run.range.contains(&(index as u32)) && run.shadow().is_some() {
|
||||
return Some(run.shadow().unwrap());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn shadow_brush_at(&self, index: usize) -> Option<Brush> {
|
||||
for run in self.0.iter().rev() {
|
||||
if run.range.contains(&(index as u32)) && run.shadow_brush().is_some() {
|
||||
return Some(run.shadow_brush().unwrap().clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn shadow_dilation_at(&self, index: usize) -> Option<f32> {
|
||||
for run in self.0.iter().rev() {
|
||||
if run.range.contains(&(index as u32)) && run.shadow_dilation().is_some() {
|
||||
return Some(run.shadow_dilation().unwrap());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn shadow_offset_at(&self, index: usize) -> Option<Vector2<f32>> {
|
||||
for run in self.0.iter().rev() {
|
||||
if run.range.contains(&(index as u32)) && run.shadow_offset().is_some() {
|
||||
return Some(run.shadow_offset().unwrap());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The style of a partion of text within a range.
|
||||
#[derive(Clone, PartialEq, Debug, Default, Visit, Reflect)]
|
||||
pub struct Run {
|
||||
/// The range of characters that this run applies to within the text.
|
||||
pub range: Range<u32>,
|
||||
font: Option<FontResource>,
|
||||
brush: Option<Brush>,
|
||||
font_size: Option<f32>,
|
||||
shadow: Option<bool>,
|
||||
shadow_brush: Option<Brush>,
|
||||
shadow_dilation: Option<f32>,
|
||||
shadow_offset: Option<Vector2<f32>>,
|
||||
}
|
||||
|
||||
impl Run {
|
||||
/// The font of the characters in this run, or None if the font is unmodified.
|
||||
pub fn font(&self) -> Option<&FontResource> {
|
||||
self.font.as_ref()
|
||||
}
|
||||
/// The brush of the characters in this run, or None if the brush is unmodified.
|
||||
pub fn brush(&self) -> Option<&Brush> {
|
||||
self.brush.as_ref()
|
||||
}
|
||||
/// The size of the characters in this run, or None if the size is unmodified.
|
||||
pub fn font_size(&self) -> Option<f32> {
|
||||
self.font_size
|
||||
}
|
||||
/// True if the characters in this run should have a shadow. None if this run does not change
|
||||
/// whether the characters are shadowed.
|
||||
pub fn shadow(&self) -> Option<bool> {
|
||||
self.shadow
|
||||
}
|
||||
/// The brush for the shadow of the characters in this run, or None if the brush is unmodified.
|
||||
pub fn shadow_brush(&self) -> Option<&Brush> {
|
||||
self.shadow_brush.as_ref()
|
||||
}
|
||||
/// The dilation for the shadow in this run, or None if the dilation is unmodified.
|
||||
pub fn shadow_dilation(&self) -> Option<f32> {
|
||||
self.shadow_dilation
|
||||
}
|
||||
/// The offset for the shadow in this run, or None if the offset is unmodified.
|
||||
pub fn shadow_offset(&self) -> Option<Vector2<f32>> {
|
||||
self.shadow_offset
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum DrawValueLayer {
|
||||
Main,
|
||||
Shadow,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct GlyphDrawValues {
|
||||
pub atlas_page_index: usize,
|
||||
pub font: FontResource,
|
||||
pub brush: Brush,
|
||||
/// Font size scaled by super sampling scaling to pick correct atlas page.
|
||||
pub height: FontHeight,
|
||||
}
|
||||
|
||||
pub struct RunBuilder {
|
||||
range: Range<u32>,
|
||||
font: Option<FontResource>,
|
||||
brush: Option<Brush>,
|
||||
font_size: Option<f32>,
|
||||
shadow: Option<bool>,
|
||||
shadow_brush: Option<Brush>,
|
||||
shadow_dilation: Option<f32>,
|
||||
shadow_offset: Option<Vector2<f32>>,
|
||||
}
|
||||
|
||||
impl RunBuilder {
|
||||
pub fn new(range: Range<u32>) -> Self {
|
||||
Self {
|
||||
range,
|
||||
font: None,
|
||||
brush: None,
|
||||
font_size: None,
|
||||
shadow: None,
|
||||
shadow_brush: None,
|
||||
shadow_dilation: None,
|
||||
shadow_offset: None,
|
||||
}
|
||||
}
|
||||
pub fn build(self) -> Run {
|
||||
Run {
|
||||
range: self.range,
|
||||
font: self.font,
|
||||
brush: self.brush,
|
||||
font_size: self.font_size,
|
||||
shadow: self.shadow,
|
||||
shadow_brush: self.shadow_brush,
|
||||
shadow_dilation: self.shadow_dilation,
|
||||
shadow_offset: self.shadow_offset,
|
||||
}
|
||||
}
|
||||
/// Set this run to modify the font of the text within the range.
|
||||
pub fn with_font(mut self, font: FontResource) -> Self {
|
||||
self.font = Some(font);
|
||||
self
|
||||
}
|
||||
/// Set this run to modify the brush of the text within the range.
|
||||
pub fn with_brush(mut self, brush: Brush) -> Self {
|
||||
self.brush = Some(brush);
|
||||
self
|
||||
}
|
||||
/// Set this run to modify the size of the text within the range.
|
||||
pub fn with_size(mut self, size: f32) -> Self {
|
||||
self.font_size = Some(size);
|
||||
self
|
||||
}
|
||||
/// Set this run to enable or disable the shadow of the text within the range.
|
||||
pub fn with_shadow(mut self, shadow: bool) -> Self {
|
||||
self.shadow = Some(shadow);
|
||||
self
|
||||
}
|
||||
/// Set this run to modify the brush of the shadow within the range.
|
||||
pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
|
||||
self.shadow_brush = Some(brush);
|
||||
self
|
||||
}
|
||||
/// Set this run to modify the dilation of the shadow within the range.
|
||||
pub fn with_shadow_dilation(mut self, size: f32) -> Self {
|
||||
self.shadow_dilation = Some(size);
|
||||
self
|
||||
}
|
||||
/// Set this run to modify the offset of the shadow within the range.
|
||||
pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
|
||||
self.shadow_offset = Some(offset);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use crate::formatted_text::Run;
|
||||
use crate::style::StyledProperty;
|
||||
use crate::{
|
||||
brush::Brush,
|
||||
@@ -502,6 +503,7 @@ pub struct TextBuilder {
|
||||
shadow_dilation: f32,
|
||||
shadow_offset: Vector2<f32>,
|
||||
font_size: Option<StyledProperty<f32>>,
|
||||
runs: Vec<Run>,
|
||||
}
|
||||
|
||||
impl TextBuilder {
|
||||
@@ -519,6 +521,7 @@ impl TextBuilder {
|
||||
shadow_dilation: 1.0,
|
||||
shadow_offset: Vector2::new(1.0, 1.0),
|
||||
font_size: None,
|
||||
runs: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,6 +592,22 @@ impl TextBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds the given run to the text to set the style for a portion of the text.
|
||||
/// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
|
||||
/// sets a property that conflicts with an earlier run.
|
||||
pub fn with_run(mut self, run: Run) -> Self {
|
||||
self.runs.push(run);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds multiple runs to the text to set the style of portions of the text.
|
||||
/// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
|
||||
/// sets a property that conflicts with an earlier run.
|
||||
pub fn with_runs<I: IntoIterator<Item = Run>>(mut self, runs: I) -> Self {
|
||||
self.runs.extend(runs);
|
||||
self
|
||||
}
|
||||
|
||||
/// Finishes text widget creation and registers it in the user interface, returning its handle to you.
|
||||
pub fn build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
|
||||
let font = if let Some(font) = self.font {
|
||||
@@ -617,6 +636,7 @@ impl TextBuilder {
|
||||
self.font_size
|
||||
.unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
|
||||
)
|
||||
.with_runs(self.runs)
|
||||
.build(),
|
||||
),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user