bevy_progressbar/
lib.rs

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/// The Progress Bar.
28/// Has Different Colored section with relative size to each other
29/// and a Color for the empty space
30#[derive(Component, Clone)]
31pub struct ProgressBar {
32    /// The Progress
33    /// a f32 between 0.0 and 1.0
34    progress: f32,
35    /// The Different Sections
36    /// The amount is the space relative to the other Sections.
37    pub sections: Vec<(u32, Color)>,
38    /// The Color of the space that is not progressed to
39    pub empty_color: Color,
40}
41
42impl ProgressBar {
43    /// Creates a new ProgressBar
44    ///
45    /// # Examples
46    /// ```
47    /// use bevy_progressbar::ProgressBar;
48    /// use bevy_color::palettes::tailwind;
49    /// let bar = ProgressBar::new(vec![(10, tailwind::RED_500.into()), (9, tailwind::BLUE_500.into())]);
50    /// ```
51    pub fn new(sections: Vec<(u32, Color)>) -> Self {
52        Self {
53            progress: 0.0,
54            sections,
55            empty_color: Color::NONE,
56        }
57    }
58    /// Creates a new ProgressBar with a single section
59    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    /// Sets the progress of the bar
68    ///
69    /// # Arguments
70    ///
71    /// * `amount` - The Progress. gets clamped between 0.0 and 1.0
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use bevy_progressbar::ProgressBar;
77    ///
78    /// let mut bar = ProgressBar::default();
79    /// bar.set_progress(0.5);
80    /// assert_eq!(bar.get_progress(), 0.5);
81    /// bar.set_progress(10.0);
82    /// assert_eq!(bar.get_progress(), 1.0);
83    /// ```
84    pub fn set_progress(&mut self, amount: f32) -> &mut Self {
85        self.progress = amount.clamp(0.0, 1.0);
86        self
87    }
88
89    /// Returns the current progress
90    pub fn get_progress(&self) -> f32 {
91        self.progress
92    }
93
94    /// Increases the progress
95    /// the new progress is at most 1.0
96    ///
97    /// # Examples
98    /// ```
99    /// use bevy_progressbar::ProgressBar;
100    /// let mut bar = ProgressBar::default();
101    /// bar.increase_progress(0.5);
102    /// assert_eq!(bar.get_progress(), 0.5);
103    /// bar.increase_progress(4.2);
104    /// assert_eq!(bar.get_progress(), 1.0);
105    /// ```
106    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    /// Resets the progress to 0.0
113    pub fn reset(&mut self) -> &mut Self {
114        self.progress = 0.0;
115        self
116    }
117
118    /// Returns true if the ProgressBar is is_finished
119    ///
120    /// # Examples
121    /// ```
122    /// use bevy_progressbar::ProgressBar;
123    /// let mut bar = ProgressBar::default();
124    /// assert_eq!(bar.is_finished(), false);
125    /// bar.increase_progress(1.0);
126    /// assert_eq!(bar.is_finished(), true);
127    /// ```
128    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/// The Material for the ProgressBar
179/// uses a simple wgsl shader
180#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
181pub struct ProgressBarMaterial {
182    #[uniform(0)]
183    empty_color: LinearRgba,
184    #[uniform(1)]
185    progress: f32,
186    /// The color of each section
187    #[storage(2, read_only)]
188    sections_color: Vec<LinearRgba>,
189    #[storage(3, read_only)]
190    sections_start_percentage: Vec<f32>,
191    /// the length of the `sections_color` / `sections_start_percentage` vec.
192    /// needs to be set for the shader
193    #[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    /// Updates the material to match the ProgressBar
211    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}