#![warn(missing_docs)]
use crate::style::StyledProperty;
use crate::{
brush::Brush,
core::{
algebra::Vector2, color::Color, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
uuid_provider, visitor::prelude::*,
},
define_constructor,
draw::DrawingContext,
font::FontResource,
formatted_text::{FormattedText, FormattedTextBuilder, WrapMode},
message::{MessageDirection, UiMessage},
style::{resource::StyleResourceExt, Style},
widget::{Widget, WidgetBuilder},
BuildContext, Control, HorizontalAlignment, UiNode, UserInterface, VerticalAlignment,
};
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
use std::{
cell::RefCell,
ops::{Deref, DerefMut},
};
#[derive(Debug, Clone, PartialEq)]
pub enum TextMessage {
Text(String),
Wrap(WrapMode),
Font(FontResource),
VerticalAlignment(VerticalAlignment),
HorizontalAlignment(HorizontalAlignment),
Shadow(bool),
ShadowDilation(f32),
ShadowBrush(Brush),
ShadowOffset(Vector2<f32>),
FontSize(StyledProperty<f32>),
}
impl TextMessage {
define_constructor!(
TextMessage:Text => fn text(String), layout: false
);
define_constructor!(
TextMessage:Wrap => fn wrap(WrapMode), layout: false
);
define_constructor!(
TextMessage:Font => fn font(FontResource), layout: false
);
define_constructor!(
TextMessage:VerticalAlignment => fn vertical_alignment(VerticalAlignment), layout: false
);
define_constructor!(
TextMessage:HorizontalAlignment => fn horizontal_alignment(HorizontalAlignment), layout: false
);
define_constructor!(
TextMessage:Shadow => fn shadow(bool), layout: false
);
define_constructor!(
TextMessage:ShadowDilation => fn shadow_dilation(f32), layout: false
);
define_constructor!(
TextMessage:ShadowBrush => fn shadow_brush(Brush), layout: false
);
define_constructor!(
TextMessage:ShadowOffset => fn shadow_offset(Vector2<f32>), layout: false
);
define_constructor!(
TextMessage:FontSize => fn font_size(StyledProperty<f32>), layout: false
);
}
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
pub struct Text {
pub widget: Widget,
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())
.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)
.build()
}
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.formatted_text.borrow(),
);
}
fn on_visual_transform_changed(&self) {
self.formatted_text
.borrow_mut()
.set_super_sampling_scale(self.visual_max_scaling())
.build();
}
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::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();
}
}
}
}
}
}
}
impl Text {
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,
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>>,
}
impl TextBuilder {
pub fn new(widget_builder: WidgetBuilder) -> Self {
Self {
widget_builder,
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,
}
}
pub fn with_text<P: AsRef<str>>(mut self, text: P) -> Self {
self.text = Some(text.as_ref().to_owned());
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 build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
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 = Text {
widget: self.widget_builder.build(ctx),
formatted_text: RefCell::new(
FormattedTextBuilder::new(font)
.with_text(self.text.unwrap_or_default())
.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_font_size(
self.font_size
.unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
)
.build(),
),
};
ctx.add_node(UiNode::new(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));
}
}