use crate::element::{ElementId, ElementProps, ElementWidget};
use crate::events::{ElementClick, ElementToggle};
use crate::parser::color::{darken_color, lighten_color, parse_color};
use crate::parser::values::{parse_attribute, parse_bool};
use crate::widgets::Widget;
use bevy::asset::AssetServer;
use bevy::color::Color;
use bevy::prelude::{
AlignItems, AlignSelf, BorderColor, Changed, Children, Commands, Component, Entity,
EntityCommands, FontSize, JustifyContent, JustifySelf, Node, On, Query, Text, TextColor,
TextFont, UiRect, Val, With,
};
use bevy::ui::Display;
use roxmltree::Node as XmlNode;
pub(crate) fn toggle_checkbox(
trigger: On<ElementClick>,
mut commands: Commands,
mut query: Query<(&mut CheckboxState, Option<&ElementId>), With<CheckboxWidget>>,
) {
if let Ok((mut state, id)) = query.get_mut(trigger.entity) {
state.0 = !state.0;
commands.trigger(ElementToggle {
entity: trigger.entity,
id: id.cloned(),
state: state.0,
});
}
}
pub(crate) fn sync_visuals(
query: Query<(&CheckboxState, &Children), Changed<CheckboxState>>,
mut checkmarks: Query<&mut Node, With<CheckboxCheckmark>>,
) {
for (state, children) in &query {
for child in children.iter() {
if let Ok(mut node) = checkmarks.get_mut(*child) {
node.display = if state.0 {
Display::Flex
} else {
Display::None
};
}
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Component)]
pub struct CheckboxState(pub bool);
#[derive(Component, Debug, Clone, Copy)]
pub struct CheckboxCheckmark;
#[derive(Clone, Debug, Component)]
pub struct CheckboxWidget {
pub state: bool,
pub check_color: Option<Color>,
pub symbol: String,
}
impl Widget for CheckboxWidget {
fn spawn(&self, commands: &mut EntityCommands, _: &AssetServer) -> Entity {
let is_checked = self.state;
let check_color = self.check_color.unwrap_or(Color::WHITE);
let symbol = self.symbol.clone();
commands.insert((self.clone(), CheckboxState(is_checked)));
commands.with_children(|parent| {
parent.spawn((
CheckboxCheckmark,
Text::new(symbol),
TextFont {
font_size: FontSize::Px(14.0),
..Default::default()
},
TextColor(check_color),
Node {
display: if is_checked {
Display::Flex
} else {
Display::None
},
align_self: AlignSelf::Center,
justify_self: JustifySelf::Center,
..Default::default()
},
));
});
commands.id()
}
fn parse(
node: &XmlNode,
prefix: Option<&str>,
base: Option<&ElementWidget>,
) -> Result<Self, String>
where
Self: Sized,
{
let base_cb = match base {
Some(ElementWidget::Checkbox(cb)) => Some(cb),
_ => None,
};
let state = parse_attribute(node, "checked", prefix, parse_bool)?
.or_else(|| base_cb.map(|b| b.state))
.unwrap_or(false);
let check_color = parse_attribute(node, "check-color", prefix, parse_color)?
.or_else(|| base_cb.and_then(|b| b.check_color));
let symbol = parse_attribute(node, "check-symbol", prefix, |s| Ok(s.to_string()))?
.or_else(|| base_cb.map(|b| b.symbol.clone()))
.unwrap_or_else(|| "x".to_string());
Ok(Self {
state,
check_color,
symbol,
})
}
fn apply_defaults(
node: &XmlNode,
default: &mut ElementProps,
hover: &mut ElementProps,
click: &mut ElementProps,
) {
if !node.has_attribute("width") {
default.node.width = Val::Px(20.0);
}
if !node.has_attribute("height") {
hover.node.height = Val::Px(20.0);
}
default.node.align_items = AlignItems::Center;
default.node.justify_content = JustifyContent::Center;
hover.node.align_items = AlignItems::Center;
hover.node.justify_content = JustifyContent::Center;
click.node.align_items = AlignItems::Center;
click.node.justify_content = JustifyContent::Center;
let base_bg = default
.bg_color
.unwrap_or_else(|| Color::srgb(0.18, 0.18, 0.22));
if default.bg_color.is_none() {
default.bg_color = Some(base_bg);
}
if !node.has_attribute("hover.bg-color") {
hover.bg_color = Some(lighten_color(base_bg, 0.12));
}
if !node.has_attribute("click.bg-color") {
click.bg_color = Some(darken_color(base_bg, 0.08));
}
if default.border_color.is_none() {
let border = BorderColor::all(Color::srgb(0.4, 0.4, 0.45));
default.border_color = Some(border);
if node.attribute("hover:border-color").is_none() {
hover.border_color = Some(BorderColor::all(Color::srgb(0.6, 0.6, 0.7)));
}
}
if default.node.border == UiRect::ZERO {
let border_rect = UiRect::all(Val::Px(1.5));
default.node.border = border_rect;
hover.node.border = border_rect;
click.node.border = border_rect;
}
}
}