#![warn(missing_docs)]
use crate::{
brush::Brush,
core::{
algebra::Vector2, color::Color, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
uuid_provider, visitor::prelude::*,
},
draw::DrawingContext,
font::FontResource,
formatted_text::{FormattedText, FormattedTextBuilder, Run, RunSet, WrapMode},
message::{MessageData, UiMessage},
style::{resource::StyleResourceExt, Style, StyledProperty},
widget::{Widget, WidgetBuilder},
BBCode, BuildContext, Control, HorizontalAlignment, UiNode, UserInterface, VerticalAlignment,
};
use fyrox_core::algebra::Matrix3;
use fyrox_core::variable::InheritableVariable;
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
use std::cell::RefCell;
#[derive(Debug, Clone, PartialEq)]
pub enum TextMessage {
BBCode(String),
Text(String),
Wrap(WrapMode),
Font(FontResource),
VerticalAlignment(VerticalAlignment),
HorizontalAlignment(HorizontalAlignment),
Shadow(bool),
ShadowDilation(f32),
ShadowBrush(Brush),
ShadowOffset(Vector2<f32>),
FontSize(StyledProperty<f32>),
Runs(RunSet),
}
impl MessageData for TextMessage {}
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
#[reflect(derived_type = "UiNode")]
pub struct Text {
pub widget: Widget,
#[visit(optional)]
#[reflect(hidden)]
pub bbcode: InheritableVariable<String>,
pub formatted_text: RefCell<FormattedText>,
}
impl ConstructorProvider<UiNode, UserInterface> for Text {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("Text", |ui| {
TextBuilder::new(WidgetBuilder::new().with_name("Text"))
.with_text("Text")
.build(&mut ui.build_ctx())
.to_base()
.into()
})
.with_group("Visual")
}
}
crate::define_widget_deref!(Text);
uuid_provider!(Text = "22f7f502-7622-4ecb-8c5f-ba436e7ee823");
impl Control for Text {
fn measure_override(&self, _: &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()
}
fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
self.formatted_text.borrow_mut().arrange(final_size);
self.widget.arrange_override(ui, final_size)
}
fn draw(&self, drawing_context: &mut DrawingContext) {
self.formatted_text
.borrow_mut()
.set_brush(self.widget.foreground());
let bounds = self.widget.bounding_rect();
drawing_context.draw_text(
self.clip_bounds(),
bounds.position,
&self.material,
&self.formatted_text.borrow(),
);
}
fn on_visual_transform_changed(
&self,
_old_transform: &Matrix3<f32>,
_new_transform: &Matrix3<f32>,
) {
let mut text = self.formatted_text.borrow_mut();
let new_super_sampling_scale = self.visual_max_scaling();
if new_super_sampling_scale != text.super_sampling_scale() {
text.set_super_sampling_scale(new_super_sampling_scale)
.measure_and_arrange();
}
}
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
if message.destination() == self.handle() {
if let Some(msg) = message.data::<TextMessage>() {
let mut text_ref = self.formatted_text.borrow_mut();
match msg {
TextMessage::BBCode(text) => {
drop(text_ref);
self.set_bbcode(text.clone());
}
TextMessage::Text(text) => {
text_ref.set_text(text);
drop(text_ref);
self.invalidate_layout();
}
&TextMessage::Wrap(wrap) => {
if text_ref.wrap_mode() != wrap {
text_ref.set_wrap(wrap);
drop(text_ref);
self.invalidate_layout();
}
}
TextMessage::Font(font) => {
if &text_ref.get_font() != font {
text_ref.set_font(font.clone());
drop(text_ref);
self.invalidate_layout();
}
}
&TextMessage::HorizontalAlignment(horizontal_alignment) => {
if text_ref.horizontal_alignment() != horizontal_alignment {
text_ref.set_horizontal_alignment(horizontal_alignment);
drop(text_ref);
self.invalidate_layout();
}
}
&TextMessage::VerticalAlignment(vertical_alignment) => {
if text_ref.vertical_alignment() != vertical_alignment {
text_ref.set_vertical_alignment(vertical_alignment);
drop(text_ref);
self.invalidate_layout();
}
}
&TextMessage::Shadow(shadow) => {
if *text_ref.shadow != shadow {
text_ref.set_shadow(shadow);
drop(text_ref);
self.invalidate_layout();
}
}
TextMessage::ShadowBrush(brush) => {
if &*text_ref.shadow_brush != brush {
text_ref.set_shadow_brush(brush.clone());
drop(text_ref);
self.invalidate_layout();
}
}
&TextMessage::ShadowDilation(dilation) => {
if *text_ref.shadow_dilation != dilation {
text_ref.set_shadow_dilation(dilation);
drop(text_ref);
self.invalidate_layout();
}
}
&TextMessage::ShadowOffset(offset) => {
if *text_ref.shadow_offset != offset {
text_ref.set_shadow_offset(offset);
drop(text_ref);
self.invalidate_layout();
}
}
TextMessage::FontSize(height) => {
if text_ref.font_size() != height {
text_ref.set_font_size(height.clone());
drop(text_ref);
self.invalidate_layout();
}
}
TextMessage::Runs(runs) => {
text_ref.set_runs(runs.clone());
drop(text_ref);
self.invalidate_layout();
}
}
}
}
}
}
impl Text {
pub fn set_bbcode(&mut self, code: String) {
self.bbcode.set_value_and_mark_modified(code);
let code: BBCode = self.bbcode.parse().unwrap();
let mut formatted = self.formatted_text.borrow_mut();
let font = formatted.get_font();
formatted.set_runs(code.build_runs(&font));
formatted.set_text(code.text);
self.invalidate_layout();
}
pub fn wrap_mode(&self) -> WrapMode {
self.formatted_text.borrow().wrap_mode()
}
pub fn text(&self) -> String {
self.formatted_text.borrow().text()
}
pub fn font(&self) -> FontResource {
self.formatted_text.borrow().get_font()
}
pub fn vertical_alignment(&self) -> VerticalAlignment {
self.formatted_text.borrow().vertical_alignment()
}
pub fn horizontal_alignment(&self) -> HorizontalAlignment {
self.formatted_text.borrow().horizontal_alignment()
}
}
pub struct TextBuilder {
widget_builder: WidgetBuilder,
bbcode: Option<String>,
text: Option<String>,
font: Option<FontResource>,
vertical_text_alignment: VerticalAlignment,
horizontal_text_alignment: HorizontalAlignment,
wrap: WrapMode,
shadow: bool,
shadow_brush: Brush,
shadow_dilation: f32,
shadow_offset: Vector2<f32>,
font_size: Option<StyledProperty<f32>>,
runs: Vec<Run>,
trim_text: bool,
}
impl TextBuilder {
pub fn new(widget_builder: WidgetBuilder) -> Self {
Self {
widget_builder,
bbcode: None,
text: None,
font: None,
vertical_text_alignment: VerticalAlignment::Top,
horizontal_text_alignment: HorizontalAlignment::Left,
wrap: WrapMode::NoWrap,
shadow: false,
shadow_brush: Brush::Solid(Color::BLACK),
shadow_dilation: 1.0,
shadow_offset: Vector2::new(1.0, 1.0),
font_size: None,
runs: Vec::default(),
trim_text: true,
}
}
pub fn with_bbcode<P: Into<String>>(mut self, text: P) -> Self {
self.bbcode = Some(text.into());
self
}
pub fn with_text<P: Into<String>>(mut self, text: P) -> Self {
self.text = Some(text.into());
self
}
pub fn with_font(mut self, font: FontResource) -> Self {
self.font = Some(font);
self
}
pub fn with_opt_font(mut self, font: Option<FontResource>) -> Self {
self.font = font;
self
}
pub fn with_vertical_text_alignment(mut self, valign: VerticalAlignment) -> Self {
self.vertical_text_alignment = valign;
self
}
pub fn with_font_size(mut self, font_size: StyledProperty<f32>) -> Self {
self.font_size = Some(font_size);
self
}
pub fn with_horizontal_text_alignment(mut self, halign: HorizontalAlignment) -> Self {
self.horizontal_text_alignment = halign;
self
}
pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
self.wrap = wrap;
self
}
pub fn with_shadow(mut self, shadow: bool) -> Self {
self.shadow = shadow;
self
}
pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
self.shadow_brush = brush;
self
}
pub fn with_shadow_dilation(mut self, thickness: f32) -> Self {
self.shadow_dilation = thickness;
self
}
pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
self.shadow_offset = offset;
self
}
pub fn with_run(mut self, run: Run) -> Self {
self.runs.push(run);
self
}
pub fn with_runs<I: IntoIterator<Item = Run>>(mut self, runs: I) -> Self {
self.runs.extend(runs);
self
}
pub fn with_trim_text(mut self, trim: bool) -> Self {
self.trim_text = trim;
self
}
pub fn build(mut self, ctx: &mut BuildContext) -> Handle<Text> {
let font = if let Some(font) = self.font {
font
} else {
ctx.default_font()
};
if self.widget_builder.foreground.is_none() {
self.widget_builder.foreground = Some(ctx.style.property(Style::BRUSH_TEXT));
}
let text_builder = if let Some(bbcode) = &self.bbcode {
let code: BBCode = bbcode.parse().unwrap();
code.build_formatted_text(font)
} else {
FormattedTextBuilder::new(font)
.with_text(self.text.unwrap_or_default())
.with_runs(self.runs)
};
let formatted_text = text_builder
.with_vertical_alignment(self.vertical_text_alignment)
.with_horizontal_alignment(self.horizontal_text_alignment)
.with_wrap(self.wrap)
.with_shadow(self.shadow)
.with_shadow_brush(self.shadow_brush)
.with_shadow_dilation(self.shadow_dilation)
.with_shadow_offset(self.shadow_offset)
.with_trim_text(self.trim_text)
.with_font_size(
self.font_size
.unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
)
.build();
let text = Text {
widget: self.widget_builder.build(ctx),
bbcode: self.bbcode.unwrap_or_default().into(),
formatted_text: RefCell::new(formatted_text),
};
ctx.add(text)
}
}
#[cfg(test)]
mod test {
use crate::text::TextBuilder;
use crate::{test::test_widget_deletion, widget::WidgetBuilder};
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| TextBuilder::new(WidgetBuilder::new()).build(ctx));
}
}