use bevy_app::prelude::Plugin;
use bevy_asset::{load_internal_asset, prelude::Assets, Asset, Handle};
use bevy_color::{Color, LinearRgba};
use bevy_ecs::prelude::{Bundle, Component, Query, ResMut};
use bevy_reflect::TypePath;
use bevy_render::render_resource::{AsBindGroup, Shader};
use bevy_ui::{node_bundles::MaterialNodeBundle, Style, UiMaterial, UiMaterialPlugin};
use bevy_utils::default;
pub const PROGRESS_BAR_HANDLE: Handle<Shader> =
Handle::weak_from_u128(8714649747086695632918559878778085427);
pub struct ProgressBarPlugin;
impl Plugin for ProgressBarPlugin {
fn build(&self, app: &mut bevy_app::App) {
load_internal_asset!(
app,
PROGRESS_BAR_HANDLE,
"progress_shader.wgsl",
Shader::from_wgsl
);
app.add_systems(bevy_app::Update, update_progress_bar)
.add_plugins(UiMaterialPlugin::<ProgressBarMaterial>::default());
}
}
#[derive(Component, Clone)]
pub struct ProgressBar {
progress: f32,
pub sections: Vec<(u32, Color)>,
pub empty_color: Color,
}
impl ProgressBar {
pub fn new(sections: Vec<(u32, Color)>) -> Self {
Self {
progress: 0.0,
sections,
empty_color: Color::NONE,
}
}
pub fn single(color: Color) -> Self {
Self {
progress: 0.0,
sections: vec![(1, color)],
empty_color: Color::NONE,
}
}
pub fn set_progress(&mut self, amount: f32) -> &mut Self {
self.progress = amount.clamp(0.0, 1.0);
self
}
pub fn get_progress(&self) -> f32 {
self.progress
}
pub fn increase_progress(&mut self, amount: f32) -> &mut Self {
self.progress += amount;
self.progress = self.progress.clamp(0.0, 1.0);
self
}
pub fn reset(&mut self) -> &mut Self {
self.progress = 0.0;
self
}
pub fn is_finished(&self) -> bool {
self.progress >= 1.0
}
pub fn clear_sections(&mut self) -> &mut Self {
self.sections.clear();
self
}
pub fn add_section(&mut self, amount: u32, color: Color) -> &mut Self {
self.sections.push((amount, color));
self
}
}
impl Default for ProgressBar {
fn default() -> Self {
Self {
progress: 0.0,
sections: vec![],
empty_color: Color::NONE,
}
}
}
#[derive(Bundle)]
pub struct ProgressBarBundle {
progressbar: ProgressBar,
material_node_bundle: MaterialNodeBundle<ProgressBarMaterial>,
}
impl ProgressBarBundle {
pub fn new(
progressbar: ProgressBar,
materials: &mut ResMut<Assets<ProgressBarMaterial>>,
) -> ProgressBarBundle {
ProgressBarBundle {
progressbar,
material_node_bundle: MaterialNodeBundle {
style: Style {
width: bevy_ui::Val::Percent(100.0),
..default()
},
material: materials.add(ProgressBarMaterial::default()),
..default()
},
}
}
}
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
pub struct ProgressBarMaterial {
#[uniform(0)]
empty_color: LinearRgba,
#[uniform(1)]
progress: f32,
#[storage(2, read_only)]
sections_color: Vec<LinearRgba>,
#[storage(3, read_only)]
sections_start_percentage: Vec<f32>,
#[uniform(4)]
sections_count: u32,
}
impl Default for ProgressBarMaterial {
fn default() -> Self {
Self {
empty_color: LinearRgba::NONE,
progress: 0.0,
sections_color: vec![],
sections_start_percentage: vec![],
sections_count: 0,
}
}
}
impl ProgressBarMaterial {
pub fn update(&mut self, bar: &ProgressBar) {
self.empty_color = bar.empty_color.to_linear();
self.progress = bar.progress;
self.sections_color = vec![];
self.sections_start_percentage = vec![];
let total_amount: u32 = bar.sections.iter().map(|(amount, _)| amount).sum();
for (amount, color) in bar.sections.iter() {
self.sections_start_percentage
.push(1. / (total_amount as f32 / *amount as f32));
self.sections_color.push(color.to_linear());
}
self.sections_count = bar.sections.len() as u32;
}
}
impl UiMaterial for ProgressBarMaterial {
fn fragment_shader() -> bevy_render::render_resource::ShaderRef {
PROGRESS_BAR_HANDLE.into()
}
}
fn update_progress_bar(
bar_query: Query<(&ProgressBar, &Handle<ProgressBarMaterial>)>,
mut materials: ResMut<Assets<ProgressBarMaterial>>,
) {
for (bar, handle) in bar_query.iter() {
let Some(material) = materials.get_mut(handle) else {
continue;
};
material.update(bar);
}
}