use crate::element::{ElementActive, ElementProps};
use crate::parser::AttributeMap;
use crate::parser::values::parse_attribute;
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::prelude::{
Changed, Entity, FontSize, FontSource, FontStyle, FontWeight, FontWidth, IntoScheduleConfigs,
Or, Query, Res, Text, TextColor, TextFont, With, World,
};
use bevy::ui::Interaction;
use roxmltree::Node;
fn update_props(
assets: Res<AssetServer>,
mut query: Query<
(
&Interaction,
&Properties<TextProps>,
&mut Text,
&mut TextColor,
&mut TextFont,
),
(
With<ElementActive>,
Or<(Changed<Interaction>, Changed<Properties<TextProps>>)>,
),
>,
) {
for (interaction, props, mut text, mut color, mut font) in &mut query {
let active_props = match interaction {
Interaction::Pressed => &props.click,
Interaction::Hovered => &props.hover,
Interaction::None => &props.default,
};
crate::set_if_changed!(
text.0, active_props.content => active_props.content.clone();
color.0, active_props.color;
font.font_size, active_props.font_size;
font.weight, active_props.font_weight;
font.width, active_props.font_width;
font.style, active_props.font_style;
);
if let Some(ref f) = active_props.font {
let handle: Handle<bevy::text::Font> = assets.load(f);
let src = FontSource::Handle(handle);
crate::set_if_changed!(font.font, src);
}
}
}
#[derive(Clone, Debug, Default)]
pub struct TextWidget {
props: Properties<TextProps>,
}
impl Widget for TextWidget {
fn name() -> &'static str
where
Self: Sized,
{
"Text"
}
fn setup(&self, app: &mut App) {
app.add_systems(Update, update_props.in_set(PageSystemSet));
}
fn parse(&mut self, node: &Node, attrs: &AttributeMap) -> Result<(), String> {
let default = TextProps::parse(node, attrs, None, &TextProps::default())?;
let hover = TextProps::parse(node, attrs, Some("hover"), &default)?;
let click = TextProps::parse(node, 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 text = Text::new(&props.content);
let text_font = TextFont {
font: props
.font
.as_ref()
.map(|f| FontSource::Handle(assets.load(f)))
.unwrap_or_default(),
font_size: props.font_size,
weight: props.font_weight,
width: props.font_width,
style: props.font_style,
..Default::default()
};
let text_color = TextColor(props.color);
world
.entity_mut(entity)
.insert((self.props.clone(), text, text_font, text_color));
entity
}
fn apply_defaults(
&self,
_: &AttributeMap,
_: &mut ElementProps,
_: &mut ElementProps,
_: &mut ElementProps,
) {
}
fn dyn_clone(&self) -> Box<dyn Widget> {
Box::new(self.clone())
}
}
#[derive(Clone, Debug)]
pub struct TextProps {
pub content: String,
pub font: Option<String>,
pub font_weight: FontWeight,
pub font_width: FontWidth,
pub font_size: FontSize,
pub font_style: FontStyle,
pub color: Color,
}
impl TextProps {
fn parse(
node: &Node,
attrs: &AttributeMap,
prefix: Option<&str>,
base: &Self,
) -> Result<Self, String> {
let content = parse_attribute(attrs, "content", prefix, |s| Ok(s.to_string()))?
.or_else(|| {
if prefix.is_none() {
node.text().map(str::trim).map(str::to_string)
} else {
None
}
})
.unwrap_or_else(|| base.content.clone());
let font = parse_attribute(attrs, "font", prefix, |s| Ok(s.to_string()))?
.or_else(|| base.font.clone());
let font_weight = parse_attribute(attrs, "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)),
],
)
})
})?
.unwrap_or(base.font_weight);
let font_width = parse_attribute(attrs, "font-width", prefix, |s| {
crate::parser::values::parse_float(s).map(FontWidth)
})?
.unwrap_or(base.font_width);
let font_size = parse_attribute(
attrs,
"font-size",
prefix,
crate::parser::values::parse_font_size,
)?
.unwrap_or(base.font_size);
let font_style = parse_attribute(attrs, "font-style", prefix, |s| {
crate::parser::values::parse_matches(
s,
&[
("normal", &|| Ok(FontStyle::Normal)),
("italic", &|| Ok(FontStyle::Italic)),
("oblique", &|| Ok(FontStyle::Oblique(None))),
],
)
})?
.unwrap_or(base.font_style);
let color = parse_attribute(attrs, "color", prefix, crate::parser::color::parse_color)?
.unwrap_or(base.color);
Ok(Self {
content,
font,
font_weight,
font_width,
font_size,
font_style,
color,
})
}
}
impl Default for TextProps {
#[inline(always)]
fn default() -> Self {
Self {
content: String::new(),
font: None,
font_weight: FontWeight::NORMAL,
font_width: FontWidth::NORMAL,
font_size: FontSize::Px(16.0),
font_style: FontStyle::Normal,
color: Color::WHITE,
}
}
}