rg3d-ui 0.15.0

Extendable UI library
Documentation
use crate::{
    brush::Brush,
    core::{algebra::Vector2, color::Color, pool::Handle},
    define_constructor,
    draw::DrawingContext,
    formatted_text::{FormattedText, FormattedTextBuilder, WrapMode},
    message::{MessageDirection, UiMessage},
    ttf::SharedFont,
    widget::{Widget, WidgetBuilder},
    BuildContext, Control, HorizontalAlignment, UiNode, UserInterface, VerticalAlignment,
};
use std::{
    any::{Any, TypeId},
    cell::RefCell,
    ops::{Deref, DerefMut},
};

#[derive(Debug, Clone, PartialEq)]
pub enum TextMessage {
    Text(String),
    Wrap(WrapMode),
    Font(SharedFont),
    VerticalAlignment(VerticalAlignment),
    HorizontalAlignment(HorizontalAlignment),
}

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(SharedFont), layout: false);
    define_constructor!(TextMessage:VerticalAlignment => fn vertical_alignment(VerticalAlignment), layout: false);
    define_constructor!(TextMessage:HorizontalAlignment => fn horizontal_alignment(HorizontalAlignment), layout: false);
}

#[derive(Clone)]
pub struct Text {
    widget: Widget,
    formatted_text: RefCell<FormattedText>,
}

crate::define_widget_deref!(Text);

impl Control for Text {
    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
        if type_id == TypeId::of::<Self>() {
            Some(self)
        } else {
            None
        }
    }

    fn measure_override(&self, _: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
        self.formatted_text
            .borrow_mut()
            .set_constraint(available_size)
            .set_brush(self.widget.foreground())
            .build()
    }

    fn draw(&self, drawing_context: &mut DrawingContext) {
        let bounds = self.widget.screen_bounds();
        drawing_context.draw_text(
            self.clip_bounds(),
            bounds.position,
            &self.formatted_text.borrow(),
        );
    }

    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>() {
                match msg {
                    TextMessage::Text(text) => {
                        self.formatted_text.borrow_mut().set_text(text);
                        self.invalidate_layout();
                    }
                    &TextMessage::Wrap(wrap) => {
                        if self.formatted_text.borrow().wrap_mode() != wrap {
                            self.formatted_text.borrow_mut().set_wrap(wrap);
                            self.invalidate_layout();
                        }
                    }
                    TextMessage::Font(font) => {
                        self.formatted_text.borrow_mut().set_font(font.clone());
                        self.invalidate_layout();
                    }
                    &TextMessage::HorizontalAlignment(horizontal_alignment) => {
                        self.formatted_text
                            .borrow_mut()
                            .set_horizontal_alignment(horizontal_alignment);
                        self.invalidate_layout();
                    }
                    &TextMessage::VerticalAlignment(vertical_alignment) => {
                        self.formatted_text
                            .borrow_mut()
                            .set_vertical_alignment(vertical_alignment);
                        self.invalidate_layout();
                    }
                }
            }
        }
    }
}

impl Text {
    pub fn new(widget: Widget) -> Self {
        Self {
            widget,
            formatted_text: RefCell::new(
                FormattedTextBuilder::new()
                    .with_font(crate::DEFAULT_FONT.clone())
                    .build(),
            ),
        }
    }

    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) -> SharedFont {
        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<SharedFont>,
    vertical_text_alignment: VerticalAlignment,
    horizontal_text_alignment: HorizontalAlignment,
    wrap: WrapMode,
}

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,
        }
    }

    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: SharedFont) -> Self {
        self.font = Some(font);
        self
    }

    pub fn with_opt_font(mut self, font: Option<SharedFont>) -> 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_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 build(mut self, ui: &mut BuildContext) -> Handle<UiNode> {
        let font = if let Some(font) = self.font {
            font
        } else {
            crate::DEFAULT_FONT.clone()
        };

        if self.widget_builder.foreground.is_none() {
            self.widget_builder.foreground = Some(Brush::Solid(Color::opaque(220, 220, 220)));
        }

        let text = Text {
            widget: self.widget_builder.build(),
            formatted_text: RefCell::new(
                FormattedTextBuilder::new()
                    .with_text(self.text.unwrap_or_default())
                    .with_vertical_alignment(self.vertical_text_alignment)
                    .with_horizontal_alignment(self.horizontal_text_alignment)
                    .with_font(font)
                    .with_wrap(self.wrap)
                    .build(),
            ),
        };
        ui.add_node(UiNode::new(text))
    }
}