use crate::element::{ElementProps, ElementWidget};
use crate::parser::color::parse_color;
use crate::parser::values::{parse_attribute, parse_float};
use crate::widgets::Widget;
use bevy::asset::AssetServer;
use bevy::color::Color;
use bevy::ecs::system::EntityCommands;
use bevy::prelude::*;
use bevy::ui::{BackgroundColor, BorderRadius, Node, PositionType, Val};
use roxmltree::Node as XmlNode;
pub(crate) fn sync_progress_bar_visuals(
pb_query: Query<(&ProgressBarState, &Children), Changed<ProgressBarState>>,
mut fill_query: Query<&mut Node, With<ProgressBarFill>>,
) {
for (pb, children) in &pb_query {
let pct = pb.percentage();
for &child in children {
if let Ok(mut fill_node) = fill_query.get_mut(child) {
fill_node.width = Val::Percent(pct);
}
}
}
}
#[derive(Component, Debug, Clone, PartialEq)]
pub struct ProgressBarState {
pub min: f32,
pub max: f32,
pub value: f32,
}
impl ProgressBarState {
pub fn normalized(&self) -> f32 {
if self.max > self.min {
((self.value - self.min) / (self.max - self.min)).clamp(0.0, 1.0)
} else {
0.0
}
}
pub fn percentage(&self) -> f32 {
self.normalized() * 100.0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProgressBarWidget {
pub min: f32,
pub max: f32,
pub value: f32,
pub track_color: Option<Color>,
pub fill_color: Option<Color>,
pub track_height: Option<f32>,
}
impl Widget for ProgressBarWidget {
fn spawn(&self, commands: &mut EntityCommands, _: &AssetServer) -> Entity {
let norm_val = if self.max > self.min {
((self.value - self.min) / (self.max - self.min)).clamp(0.0, 1.0)
} else {
0.0
};
let pct = norm_val * 100.0;
let track_color = self.track_color.unwrap_or(Color::srgb(0.16, 0.17, 0.20));
let fill_color = self.fill_color.unwrap_or(Color::srgb(0.38, 0.69, 0.94));
let track_h = self.track_height.unwrap_or(12.0);
commands
.insert(ProgressBarState {
min: self.min,
max: self.max,
value: self.value,
})
.with_children(|parent| {
parent.spawn((
ProgressBarTrack,
Node {
width: Val::Percent(100.0),
height: Val::Px(track_h),
border_radius: BorderRadius::all(Val::Px(track_h / 2.0)),
position_type: PositionType::Absolute,
..default()
},
BackgroundColor(track_color),
));
parent.spawn((
ProgressBarFill,
Node {
width: Val::Percent(pct),
height: Val::Px(track_h),
border_radius: BorderRadius::all(Val::Px(track_h / 2.0)),
position_type: PositionType::Absolute,
..default()
},
BackgroundColor(fill_color),
));
});
commands.id()
}
fn parse(
node: &XmlNode,
prefix: Option<&str>,
base: Option<&ElementWidget>,
) -> Result<Self, String>
where
Self: Sized,
{
let base_pb = match base {
Some(ElementWidget::ProgressBar(pb)) => Some(pb),
_ => None,
};
let min = parse_attribute(node, "min", prefix, parse_float)?
.or_else(|| base_pb.map(|p| p.min))
.unwrap_or(0.0);
let max = parse_attribute(node, "max", prefix, parse_float)?
.or_else(|| base_pb.map(|p| p.max))
.unwrap_or(100.0);
if min >= max {
return Err(format!(
"ProgressBar 'min' ({}) must be strictly less than 'max' ({})",
min, max
));
}
let value = parse_attribute(node, "value", prefix, parse_float)?
.or_else(|| base_pb.map(|p| p.value))
.unwrap_or(min)
.clamp(min, max);
let track_color = parse_attribute(node, "track-color", prefix, parse_color)?
.or_else(|| base_pb.and_then(|p| p.track_color));
let fill_color = parse_attribute(node, "fill-color", prefix, parse_color)?
.or_else(|| base_pb.and_then(|p| p.fill_color));
let track_height = parse_attribute(node, "track-height", prefix, parse_float)?
.or_else(|| base_pb.and_then(|p| p.track_height));
Ok(ProgressBarWidget {
min,
max,
value,
track_color,
fill_color,
track_height,
})
}
fn apply_defaults(
node: &XmlNode,
default: &mut ElementProps,
_hover: &mut ElementProps,
_click: &mut ElementProps,
) {
if !node.has_attribute("width") {
default.node.width = Val::Px(200.0);
}
if !node.has_attribute("height") {
default.node.height = Val::Px(12.0);
}
default.node.align_items = AlignItems::Center;
default.node.justify_content = JustifyContent::FlexStart;
default.node.position_type = PositionType::Relative;
}
}
#[derive(Component)]
pub struct ProgressBarTrack;
#[derive(Component)]
pub struct ProgressBarFill;