use crate::element::ElementProps;
use crate::parser::AttributeMap;
use crate::parser::color::parse_color;
use crate::parser::values::{parse_attribute, parse_border_rect, parse_float};
use crate::parser::values::{parse_bool, parse_matches, parse_rect};
use crate::props::Properties;
use crate::systems::PageSystemSet;
use crate::widgets::Widget;
use bevy::app::{App, Update};
use bevy::asset::{AssetServer, Handle};
use bevy::color::Color;
use bevy::image::Image;
use bevy::math::Rect;
use bevy::prelude::{
BorderRect, Changed, Entity, ImageNode, Interaction, IntoScheduleConfigs, NodeImageMode, Or,
Query, Res, TextureSlicer, VisualBox, World,
};
use bevy::sprite::SliceScaleMode;
use bevy::ui::Val;
use roxmltree::Node;
fn update_props(
assets: Res<AssetServer>,
mut query: Query<
(&Interaction, &Properties<ImageProps>, &mut ImageNode),
Or<(Changed<Interaction>, Changed<Properties<ImageProps>>)>,
>,
) {
for (interaction, props, mut image_node) in &mut query {
let props = match interaction {
Interaction::Pressed => &props.click,
Interaction::Hovered => &props.hover,
Interaction::None => &props.default,
};
let image = props.src.fetch(&assets);
crate::set_if_changed!(
image_node.color, props.color;
image_node.flip_x, props.flip_x;
image_node.flip_y, props.flip_y;
image_node.rect, props.rect;
image_node.image_mode, props.mode => props.mode.clone();
image_node.visual_box, props.visual_box;
image_node.image, image;
);
}
}
#[derive(Clone, Debug)]
pub enum ImageSource {
Path(String),
Handle(Handle<Image>),
}
impl ImageSource {
pub fn fetch(&self, assets: &AssetServer) -> Handle<Image> {
match self {
ImageSource::Path(path) => assets.load(path),
ImageSource::Handle(image) => image.clone(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct ImageWidget {
props: Properties<ImageProps>,
}
impl Widget for ImageWidget {
fn name() -> &'static str
where
Self: Sized,
{
"Image"
}
fn setup(&self, app: &mut App) {
app.add_systems(Update, update_props.in_set(PageSystemSet));
}
fn parse(&mut self, _: &Node, attrs: &AttributeMap) -> Result<(), String>
where
Self: Sized,
{
let default = ImageProps::parse(attrs, None, &ImageProps::default())?;
let hover = ImageProps::parse(attrs, Some("hover"), &default)?;
let click = ImageProps::parse(attrs, Some("click"), &default)?;
self.props = Properties {
default,
hover,
click,
};
Ok(())
}
fn spawn(&self, entity: Entity, world: &mut World) -> Entity {
let assets = world.resource::<AssetServer>();
let props = &self.props.default;
let image = ImageNode {
color: props.color,
image: props.src.fetch(assets),
texture_atlas: None,
flip_x: props.flip_x,
flip_y: props.flip_y,
rect: props.rect,
image_mode: props.mode.clone(),
visual_box: props.visual_box,
};
world.entity_mut(entity).insert((self.props.clone(), image));
entity
}
fn apply_defaults(
&self,
attrs: &AttributeMap,
default: &mut ElementProps,
hover: &mut ElementProps,
click: &mut ElementProps,
) {
crate::set_missing_attrs!(
attrs,
"width" => default.node.width = Val::Px(250.0),
"hover.width" => hover.node.width = default.node.width,
"click.width" => click.node.width = default.node.width,
"height" => default.node.height = Val::Px(250.0),
"hover.height" => hover.node.height = default.node.height,
"click.height" => click.node.height = default.node.height,
"flex-grow" => default.node.flex_grow = 1.0,
"hover.flex-grow" => hover.node.flex_grow = default.node.flex_grow,
"click.flex-grow" => click.node.flex_grow = default.node.flex_grow,
);
}
fn dyn_clone(&self) -> Box<dyn Widget> {
Box::new(self.clone())
}
}
#[derive(Clone, Debug)]
pub struct ImageProps {
pub src: ImageSource,
pub color: Color,
pub flip_x: bool,
pub flip_y: bool,
pub rect: Option<Rect>,
pub mode: NodeImageMode,
pub visual_box: VisualBox,
pub sliced_border: BorderRect,
pub sliced_center_scale_stretch: f32,
pub sliced_center_scale: SliceScaleMode,
pub sliced_sides_scale_stretch: f32,
pub sliced_sides_scale: SliceScaleMode,
pub sliced_max_corner_scale: f32,
pub tiled_x: bool,
pub tiled_y: bool,
pub tiled_stretch: f32,
}
impl ImageProps {
fn parse(
attrs: &AttributeMap,
prefix: Option<&str>,
base: &Self,
) -> bevy::prelude::Result<Self, String> {
let src = parse_attribute(attrs, "src", prefix, |s| {
Ok(ImageSource::Path(s.to_string()))
})?
.unwrap_or_else(|| base.src.clone());
let color = parse_attribute(attrs, "color", prefix, parse_color)?.unwrap_or(base.color);
let flip_x = parse_attribute(attrs, "flip-x", prefix, parse_bool)?.unwrap_or(base.flip_x);
let flip_y = parse_attribute(attrs, "flip-y", prefix, parse_bool)?.unwrap_or(base.flip_y);
let rect = parse_attribute(attrs, "rect", prefix, parse_rect)?.or(base.rect);
let sliced_border = parse_attribute(attrs, "sliced-border", prefix, parse_border_rect)?
.unwrap_or(base.sliced_border);
let sliced_center_scale_stretch =
parse_attribute(attrs, "sliced-center-scale-stretch", prefix, parse_float)?
.unwrap_or(base.sliced_center_scale_stretch);
let sliced_center_scale = parse_attribute(attrs, "sliced-center-scale", prefix, |s| {
parse_matches(
s,
&[
("stretch", &|| Ok(SliceScaleMode::Stretch)),
("tile", &|| {
Ok(SliceScaleMode::Tile {
stretch_value: sliced_center_scale_stretch,
})
}),
],
)
})?
.unwrap_or(base.sliced_center_scale);
let sliced_sides_scale_stretch =
parse_attribute(attrs, "sliced-sides-scale-stretch", prefix, parse_float)?
.unwrap_or(base.sliced_sides_scale_stretch);
let sliced_sides_scale = parse_attribute(attrs, "sliced-sides-scale", prefix, |s| {
parse_matches(
s,
&[
("stretch", &|| Ok(SliceScaleMode::Stretch)),
("tile", &|| {
Ok(SliceScaleMode::Tile {
stretch_value: sliced_sides_scale_stretch,
})
}),
],
)
})?
.unwrap_or(base.sliced_sides_scale);
let sliced_max_corner_scale =
parse_attribute(attrs, "sliced-max-corner-scale", prefix, parse_float)?
.unwrap_or(base.sliced_max_corner_scale);
let tiled_x =
parse_attribute(attrs, "tiled-x", prefix, parse_bool)?.unwrap_or(base.tiled_x);
let tiled_y =
parse_attribute(attrs, "tiled-y", prefix, parse_bool)?.unwrap_or(base.tiled_y);
let tiled_stretch = parse_attribute(attrs, "tiled-stretch", prefix, parse_float)?
.unwrap_or(base.tiled_stretch);
let mode = parse_attribute(attrs, "mode", prefix, |s| {
parse_matches(
s,
&[
("auto", &|| Ok(NodeImageMode::Auto)),
("sliced", &|| {
Ok(NodeImageMode::Sliced(TextureSlicer::default()))
}),
("tiled", &|| {
Ok(NodeImageMode::Tiled {
tile_x: tiled_x,
tile_y: tiled_y,
stretch_value: tiled_stretch,
})
}),
("stretch", &|| Ok(NodeImageMode::Stretch)),
],
)
})?
.unwrap_or_else(|| base.mode.clone());
let visual_box = parse_attribute(attrs, "visual-box", prefix, |s| {
parse_matches(
s,
&[
("padding", &|| Ok(VisualBox::PaddingBox)),
("content", &|| Ok(VisualBox::ContentBox)),
("border", &|| Ok(VisualBox::BorderBox)),
],
)
})?
.unwrap_or(base.visual_box);
let mut props = Self {
src,
color,
flip_x,
flip_y,
rect,
mode,
visual_box,
sliced_border,
sliced_center_scale_stretch,
sliced_center_scale,
sliced_sides_scale_stretch,
sliced_sides_scale,
sliced_max_corner_scale,
tiled_x,
tiled_y,
tiled_stretch,
};
props.mode = props.compute_mode();
Ok(props)
}
pub fn compute_mode(&self) -> NodeImageMode {
match &self.mode {
NodeImageMode::Auto => NodeImageMode::Auto,
NodeImageMode::Stretch => NodeImageMode::Stretch,
NodeImageMode::Sliced(_) => {
let center_scale_mode = match self.sliced_center_scale {
SliceScaleMode::Stretch => SliceScaleMode::Stretch,
SliceScaleMode::Tile { .. } => SliceScaleMode::Tile {
stretch_value: self.sliced_center_scale_stretch,
},
};
let sides_scale_mode = match self.sliced_sides_scale {
SliceScaleMode::Stretch => SliceScaleMode::Stretch,
SliceScaleMode::Tile { .. } => SliceScaleMode::Tile {
stretch_value: self.sliced_sides_scale_stretch,
},
};
NodeImageMode::Sliced(TextureSlicer {
border: self.sliced_border,
center_scale_mode,
sides_scale_mode,
max_corner_scale: self.sliced_max_corner_scale,
})
}
NodeImageMode::Tiled { .. } => NodeImageMode::Tiled {
tile_x: self.tiled_x,
tile_y: self.tiled_y,
stretch_value: self.tiled_stretch,
},
}
}
}
impl Default for ImageProps {
#[inline(always)]
fn default() -> Self {
Self {
src: ImageSource::Path("".to_string()),
color: Color::WHITE,
flip_x: false,
flip_y: false,
rect: None,
mode: NodeImageMode::default(),
visual_box: VisualBox::default(),
sliced_border: BorderRect::default(),
sliced_center_scale_stretch: 1.0,
sliced_center_scale: SliceScaleMode::default(),
sliced_sides_scale_stretch: 1.0,
sliced_sides_scale: SliceScaleMode::default(),
sliced_max_corner_scale: 1.0,
tiled_x: true,
tiled_y: true,
tiled_stretch: 1.0,
}
}
}