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::color::parse_color;
use crate::parser::values::parse_attribute;
use crate::parser::values::{parse_bool, parse_matches, parse_rect};
use crate::widgets::Widget;
use bevy::asset::AssetServer;
use bevy::color::Color;
use bevy::math::Rect;
use bevy::prelude::{
    Component, Entity, EntityCommands, ImageNode, NodeImageMode, TextureSlicer, VisualBox,
};
use roxmltree::Node;

/// An image widget.
///
/// ## XML Usage
///
/// Build a new image widget using the `<Image />` tag.
///
/// ### Attributes
/// - `src = "<string>"`: The asset path to the actual image to display. Required.
/// - `color = "<color>"`: The color/tint of the image.
/// - `flip-x = "<bool>"`: Whether to flip the image horizontally.
/// - `flip-y = "<bool>"`: Whether to flip the image vertically.
/// - `rect = "<rect>"`: The rect to clip the image to.
/// - `mode = "<auto|sliced|tiled|stretch>"`: The layout mode to use for the image.
///
/// ## Logic
///
/// This widget does not have any special code logic.
/// Use element events to implement custom behavior.
#[derive(Clone, Debug, Component)]
pub struct ImageWidget {
    /// The source path of the image.
    pub src: String,
    /// The optional color/tint of the image.
    pub color: Option<Color>,
    /// The optional flip-x attribute.
    pub flip_x: Option<bool>,
    /// The optional flip-y attribute.
    pub flip_y: Option<bool>,
    /// The optional rect to use for the image.
    pub rect: Option<Rect>,
    /// The optional mode to use for the image.
    pub mode: Option<NodeImageMode>,
    /// The optional visual box to use for the image.
    pub visual_box: Option<VisualBox>,
}

impl Widget for ImageWidget {
    fn spawn(&self, commands: &mut EntityCommands, assets: &AssetServer) -> Entity {
        let image = ImageNode {
            color: self.color.unwrap_or(Color::WHITE),
            image: assets.load(&self.src),
            texture_atlas: None,
            flip_x: self.flip_x.unwrap_or_default(),
            flip_y: self.flip_y.unwrap_or_default(),
            rect: self.rect,
            image_mode: self.mode.clone().unwrap_or_default(),
            visual_box: self.visual_box.unwrap_or_default(),
        };

        commands.insert(image);

        commands.id()
    }

    fn parse(
        node: &Node,
        prefix: Option<&str>,
        base: Option<&ElementWidget>,
    ) -> Result<Self, String>
    where
        Self: Sized,
    {
        let base_img = match base {
            Some(ElementWidget::Image(ImageWidget {
                src,
                color,
                flip_x,
                flip_y,
                rect,
                mode,
                visual_box,
            })) => Some((src, color, flip_x, flip_y, rect, mode, visual_box)),
            _ => None,
        };

        let src = parse_attribute(node, "src", prefix, |s| Ok(s.to_string()))?
            .or_else(|| base_img.map(|b| b.0.clone()))
            .ok_or_else(|| "Must provide a 'src' attribute for widget <Image>".to_string())?;

        let color = parse_attribute(node, "color", prefix, parse_color)?
            .or_else(|| base_img.and_then(|b| *b.1));

        let flip_x = parse_attribute(node, "flip-x", prefix, parse_bool)?
            .or_else(|| base_img.and_then(|b| *b.2));

        let flip_y = parse_attribute(node, "flip-y", prefix, parse_bool)?
            .or_else(|| base_img.and_then(|b| *b.3));

        let rect = parse_attribute(node, "rect", prefix, parse_rect)?
            .or_else(|| base_img.and_then(|b| *b.4));

        let mode = parse_attribute(node, "mode", prefix, |s| {
            parse_matches(
                s,
                &[
                    ("auto", || Ok(NodeImageMode::Auto)),
                    ("sliced", || {
                        Ok(NodeImageMode::Sliced(TextureSlicer::default()))
                    }),
                    ("tiled", || {
                        Ok(NodeImageMode::Tiled {
                            tile_x: true,
                            tile_y: true,
                            stretch_value: 1.0,
                        })
                    }),
                    ("stretch", || Ok(NodeImageMode::Stretch)),
                ],
            )
        })?
        .or_else(|| base_img.and_then(|b| b.5.clone()));

        Ok(Self {
            src,
            color,
            flip_x,
            flip_y,
            rect,
            mode,
            visual_box: None,
        })
    }

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