bevy_pages 0.2.0

A lightweight and elegant framework to upgrade your Bevy UI experience.
Documentation
use crate::element::{ElementProps, ElementWidget};
use crate::parser::values::parse_attribute;
use crate::widgets::Widget;
use bevy::asset::AssetServer;
use bevy::color::Color;
use bevy::prelude::{
    Component, Entity, EntityCommands, FontSize, FontSource, FontStyle, FontWeight, FontWidth,
    Text, TextColor, TextFont,
};
use roxmltree::Node;

/// A text widget.
///
/// ## XML Usage
///
/// Build a new text widget using the `<Text></Text>` tag.
/// You can either use the `content` attribute or the text between the tags to specify the content.
///
/// ### Attributes
/// - `content = "<string>"`: The content of the text widget.
/// - `font = "<string>"`: A path to the font file to use.
/// - `font-weight = "<int|string>"`: The weight of the font. Either an int or a string representing one of the constants of [FontWeight].
/// - `font-width = "<float>"`: The width of the font.
/// - `font-size = "<fontSize>"`: The size of the font. See [parse_font_size](crate::parser::values::parse_font_size).
/// - `font-style = "<normal|italic|oblique>"`: The style of the font.
/// - `color = "<color>"`: The color of the text.
///
/// ## Logic
///
/// This widget does not have any special code logic.
/// Use the element events to implement custom behavior.
#[derive(Clone, Debug, Component)]
pub struct TextWidget {
    /// The content of the text.
    pub content: String,
    /// An optional font path.
    pub font: Option<String>,
    /// The optional weight of the font.
    pub font_weight: Option<FontWeight>,
    /// The optional width of the font.
    pub font_width: Option<FontWidth>,
    /// The optional size of the font.
    pub font_size: Option<FontSize>,
    /// The optional style of the font.
    pub font_style: Option<FontStyle>,
    /// The optional color of the text.
    pub color: Option<Color>,
}

impl Widget for TextWidget {
    fn spawn(&self, commands: &mut EntityCommands, assets: &AssetServer) -> Entity {
        let text = Text::new(&self.content);
        let text_font = TextFont {
            font: self
                .font
                .as_ref()
                .map(|f| FontSource::Handle(assets.load(f)))
                .unwrap_or_default(),
            font_size: self.font_size.unwrap_or_default(),
            weight: self.font_weight.unwrap_or_default(),
            width: self.font_width.unwrap_or_default(),
            style: self.font_style.unwrap_or_default(),
            ..Default::default()
        };
        let text_color = TextColor(self.color.unwrap_or(Color::WHITE));

        commands.insert((text, text_font, text_color));

        commands.id()
    }

    fn parse(
        node: &Node,
        prefix: Option<&str>,
        base: Option<&ElementWidget>,
    ) -> Result<Self, String>
    where
        Self: Sized,
    {
        let base_text = match base {
            Some(ElementWidget::Text(TextWidget {
                content,
                font,
                font_weight,
                font_width,
                font_size,
                font_style,
                color,
            })) => Some((
                content,
                font,
                font_weight,
                font_width,
                font_size,
                font_style,
                color,
            )),
            _ => None,
        };

        let content = parse_attribute(node, "content", prefix, |s| Ok(s.to_string()))?
            .or_else(|| {
                if prefix.is_none() {
                    node.text().map(str::trim).map(str::to_string)
                } else {
                    None
                }
            })
            .or_else(|| base_text.map(|b| b.0.clone()))
            .unwrap_or_default();

        let font = parse_attribute(node, "font", prefix, |s| Ok(s.to_string()))?
            .or_else(|| base_text.and_then(|b| b.1.clone()));

        let font_weight = parse_attribute(node, "font-weight", prefix, |s| {
            crate::parser::values::parse_int(s)
                .map(|i| Ok(FontWeight(i as u16)))
                .unwrap_or_else(|_| {
                    crate::parser::values::parse_matches(
                        s,
                        &[
                            ("thin", || Ok(FontWeight::THIN)),
                            ("extra_light", || Ok(FontWeight::EXTRA_LIGHT)),
                            ("light", || Ok(FontWeight::LIGHT)),
                            ("normal", || Ok(FontWeight::NORMAL)),
                            ("medium", || Ok(FontWeight::MEDIUM)),
                            ("semibold", || Ok(FontWeight::SEMIBOLD)),
                            ("bold", || Ok(FontWeight::BOLD)),
                            ("extra_bold", || Ok(FontWeight::EXTRA_BOLD)),
                            ("black", || Ok(FontWeight::BLACK)),
                            ("extra_black", || Ok(FontWeight::EXTRA_BLACK)),
                        ],
                    )
                })
        })?
        .or_else(|| base_text.and_then(|b| *b.2));

        let font_width = parse_attribute(node, "font-width", prefix, |s| {
            crate::parser::values::parse_float(s).map(FontWidth)
        })?
        .or_else(|| base_text.and_then(|b| *b.3));

        let font_size = parse_attribute(
            node,
            "font-size",
            prefix,
            crate::parser::values::parse_font_size,
        )?
        .or_else(|| base_text.and_then(|b| *b.4));

        let font_style = parse_attribute(node, "font-style", prefix, |s| {
            crate::parser::values::parse_matches(
                s,
                &[
                    ("normal", || Ok(FontStyle::Normal)),
                    ("italic", || Ok(FontStyle::Italic)),
                    ("oblique", || Ok(FontStyle::Oblique(None))),
                ],
            )
        })?
        .or_else(|| base_text.and_then(|b| *b.5));

        let color = parse_attribute(node, "color", prefix, crate::parser::color::parse_color)?
            .or_else(|| base_text.and_then(|b| *b.6));

        Ok(Self {
            content,
            font,
            font_weight,
            font_width,
            font_size,
            font_style,
            color,
        })
    }

    fn apply_defaults(_: &Node, _: &mut ElementProps, _: &mut ElementProps, _: &mut ElementProps) {}
}