1use bevy_app::prelude::Plugin;
2use bevy_asset::{load_internal_asset, prelude::Assets, Asset, Handle};
3use bevy_color::{Color, LinearRgba};
4use bevy_ecs::prelude::{Bundle, Component, Query, ResMut};
5use bevy_reflect::TypePath;
6use bevy_render::render_resource::{AsBindGroup, Shader};
7use bevy_ui::{node_bundles::MaterialNodeBundle, Style, UiMaterial, UiMaterialPlugin};
8use bevy_utils::default;
9
10pub const PROGRESS_BAR_HANDLE: Handle<Shader> =
11 Handle::weak_from_u128(8714649747086695632918559878778085427);
12pub struct ProgressBarPlugin;
13
14impl Plugin for ProgressBarPlugin {
15 fn build(&self, app: &mut bevy_app::App) {
16 load_internal_asset!(
17 app,
18 PROGRESS_BAR_HANDLE,
19 "progress_shader.wgsl",
20 Shader::from_wgsl
21 );
22 app.add_systems(bevy_app::Update, update_progress_bar)
23 .add_plugins(UiMaterialPlugin::<ProgressBarMaterial>::default());
24 }
25}
26
27#[derive(Component, Clone)]
31pub struct ProgressBar {
32 progress: f32,
35 pub sections: Vec<(u32, Color)>,
38 pub empty_color: Color,
40}
41
42impl ProgressBar {
43 pub fn new(sections: Vec<(u32, Color)>) -> Self {
52 Self {
53 progress: 0.0,
54 sections,
55 empty_color: Color::NONE,
56 }
57 }
58 pub fn single(color: Color) -> Self {
60 Self {
61 progress: 0.0,
62 sections: vec![(1, color)],
63 empty_color: Color::NONE,
64 }
65 }
66
67 pub fn set_progress(&mut self, amount: f32) -> &mut Self {
85 self.progress = amount.clamp(0.0, 1.0);
86 self
87 }
88
89 pub fn get_progress(&self) -> f32 {
91 self.progress
92 }
93
94 pub fn increase_progress(&mut self, amount: f32) -> &mut Self {
107 self.progress += amount;
108 self.progress = self.progress.clamp(0.0, 1.0);
109 self
110 }
111
112 pub fn reset(&mut self) -> &mut Self {
114 self.progress = 0.0;
115 self
116 }
117
118 pub fn is_finished(&self) -> bool {
129 self.progress >= 1.0
130 }
131
132 pub fn clear_sections(&mut self) -> &mut Self {
133 self.sections.clear();
134 self
135 }
136
137 pub fn add_section(&mut self, amount: u32, color: Color) -> &mut Self {
138 self.sections.push((amount, color));
139 self
140 }
141}
142
143impl Default for ProgressBar {
144 fn default() -> Self {
145 Self {
146 progress: 0.0,
147 sections: vec![],
148 empty_color: Color::NONE,
149 }
150 }
151}
152
153#[derive(Bundle)]
154pub struct ProgressBarBundle {
155 progressbar: ProgressBar,
156 material_node_bundle: MaterialNodeBundle<ProgressBarMaterial>,
157}
158
159impl ProgressBarBundle {
160 pub fn new(
161 progressbar: ProgressBar,
162 materials: &mut ResMut<Assets<ProgressBarMaterial>>,
163 ) -> ProgressBarBundle {
164 ProgressBarBundle {
165 progressbar,
166 material_node_bundle: MaterialNodeBundle {
167 style: Style {
168 width: bevy_ui::Val::Percent(100.0),
169 ..default()
170 },
171 material: materials.add(ProgressBarMaterial::default()),
172 ..default()
173 },
174 }
175 }
176}
177
178#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
181pub struct ProgressBarMaterial {
182 #[uniform(0)]
183 empty_color: LinearRgba,
184 #[uniform(1)]
185 progress: f32,
186 #[storage(2, read_only)]
188 sections_color: Vec<LinearRgba>,
189 #[storage(3, read_only)]
190 sections_start_percentage: Vec<f32>,
191 #[uniform(4)]
194 sections_count: u32,
195}
196
197impl Default for ProgressBarMaterial {
198 fn default() -> Self {
199 Self {
200 empty_color: LinearRgba::NONE,
201 progress: 0.0,
202 sections_color: vec![],
203 sections_start_percentage: vec![],
204 sections_count: 0,
205 }
206 }
207}
208
209impl ProgressBarMaterial {
210 pub fn update(&mut self, bar: &ProgressBar) {
212 self.empty_color = bar.empty_color.to_linear();
213 self.progress = bar.progress;
214 self.sections_color = vec![];
215 self.sections_start_percentage = vec![];
216 let total_amount: u32 = bar.sections.iter().map(|(amount, _)| amount).sum();
217 for (amount, color) in bar.sections.iter() {
218 self.sections_start_percentage
219 .push(1. / (total_amount as f32 / *amount as f32));
220 self.sections_color.push(color.to_linear());
221 }
222 self.sections_count = bar.sections.len() as u32;
223 }
224}
225
226impl UiMaterial for ProgressBarMaterial {
227 fn fragment_shader() -> bevy_render::render_resource::ShaderRef {
228 PROGRESS_BAR_HANDLE.into()
229 }
230}
231
232fn update_progress_bar(
233 bar_query: Query<(&ProgressBar, &Handle<ProgressBarMaterial>)>,
234 mut materials: ResMut<Assets<ProgressBarMaterial>>,
235) {
236 for (bar, handle) in bar_query.iter() {
237 let Some(material) = materials.get_mut(handle) else {
238 continue;
239 };
240
241 material.update(bar);
242 }
243}