1use crate::core::border::{self, Border};
23use crate::core::layout;
24use crate::core::mouse;
25use crate::core::renderer;
26use crate::core::widget::Tree;
27use crate::core::{
28 self, Background, Color, Element, Layout, Length, Rectangle, Size, Theme, Widget,
29};
30
31use std::ops::RangeInclusive;
32
33pub struct ProgressBar<'a, Theme = crate::Theme>
55where
56 Theme: Catalog,
57{
58 range: RangeInclusive<f32>,
59 value: f32,
60 length: Length,
61 girth: Length,
62 is_vertical: bool,
63 class: Theme::Class<'a>,
64}
65
66impl<'a, Theme> ProgressBar<'a, Theme>
67where
68 Theme: Catalog,
69{
70 pub const DEFAULT_GIRTH: f32 = 30.0;
72
73 pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
79 ProgressBar {
80 value: value.clamp(*range.start(), *range.end()),
81 range,
82 length: Length::Fill,
83 girth: Length::from(Self::DEFAULT_GIRTH),
84 is_vertical: false,
85 class: Theme::default(),
86 }
87 }
88
89 pub fn length(mut self, length: impl Into<Length>) -> Self {
91 self.length = length.into();
92 self
93 }
94
95 pub fn girth(mut self, girth: impl Into<Length>) -> Self {
97 self.girth = girth.into();
98 self
99 }
100
101 pub fn vertical(mut self) -> Self {
105 self.is_vertical = true;
106 self
107 }
108
109 #[must_use]
111 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
112 where
113 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
114 {
115 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
116 self
117 }
118
119 #[cfg(feature = "advanced")]
121 #[must_use]
122 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
123 self.class = class.into();
124 self
125 }
126
127 fn width(&self) -> Length {
128 if self.is_vertical {
129 self.girth
130 } else {
131 self.length
132 }
133 }
134
135 fn height(&self) -> Length {
136 if self.is_vertical {
137 self.length
138 } else {
139 self.girth
140 }
141 }
142}
143
144impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for ProgressBar<'_, Theme>
145where
146 Theme: Catalog,
147 Renderer: core::Renderer,
148{
149 fn size(&self) -> Size<Length> {
150 Size {
151 width: self.width(),
152 height: self.height(),
153 }
154 }
155
156 fn layout(
157 &mut self,
158 _tree: &mut Tree,
159 _renderer: &Renderer,
160 limits: &layout::Limits,
161 ) -> layout::Node {
162 layout::atomic(limits, self.width(), self.height())
163 }
164
165 fn draw(
166 &self,
167 _tree: &Tree,
168 renderer: &mut Renderer,
169 theme: &Theme,
170 _style: &renderer::Style,
171 layout: Layout<'_>,
172 _cursor: mouse::Cursor,
173 _viewport: &Rectangle,
174 ) {
175 let bounds = layout.bounds();
176 let (range_start, range_end) = self.range.clone().into_inner();
177
178 let length = if self.is_vertical {
179 bounds.height
180 } else {
181 bounds.width
182 };
183
184 let active_progress_length = if range_start >= range_end {
185 0.0
186 } else {
187 length * (self.value - range_start) / (range_end - range_start)
188 };
189
190 let style = theme.style(&self.class);
191
192 renderer.fill_quad(
193 renderer::Quad {
194 bounds: Rectangle { ..bounds },
195 border: style.border,
196 ..renderer::Quad::default()
197 },
198 style.background,
199 );
200
201 if active_progress_length > 0.0 {
202 let bounds = if self.is_vertical {
203 Rectangle {
204 y: bounds.y + bounds.height - active_progress_length,
205 height: active_progress_length,
206 ..bounds
207 }
208 } else {
209 Rectangle {
210 width: active_progress_length,
211 ..bounds
212 }
213 };
214
215 renderer.fill_quad(
216 renderer::Quad {
217 bounds,
218 border: Border {
219 color: Color::TRANSPARENT,
220 ..style.border
221 },
222 ..renderer::Quad::default()
223 },
224 style.bar,
225 );
226 }
227 }
228}
229
230impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>
231 for Element<'a, Message, Theme, Renderer>
232where
233 Message: 'a,
234 Theme: 'a + Catalog,
235 Renderer: 'a + core::Renderer,
236{
237 fn from(progress_bar: ProgressBar<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
238 Element::new(progress_bar)
239 }
240}
241
242#[derive(Debug, Clone, Copy, PartialEq)]
244pub struct Style {
245 pub background: Background,
247 pub bar: Background,
249 pub border: Border,
251}
252
253pub trait Catalog: Sized {
255 type Class<'a>;
257
258 fn default<'a>() -> Self::Class<'a>;
260
261 fn style(&self, class: &Self::Class<'_>) -> Style;
263}
264
265pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
269
270impl Catalog for Theme {
271 type Class<'a> = StyleFn<'a, Self>;
272
273 fn default<'a>() -> Self::Class<'a> {
274 Box::new(primary)
275 }
276
277 fn style(&self, class: &Self::Class<'_>) -> Style {
278 class(self)
279 }
280}
281
282pub fn primary(theme: &Theme) -> Style {
284 let palette = theme.extended_palette();
285
286 styled(palette.background.strong.color, palette.primary.base.color)
287}
288
289pub fn secondary(theme: &Theme) -> Style {
291 let palette = theme.extended_palette();
292
293 styled(
294 palette.background.strong.color,
295 palette.secondary.base.color,
296 )
297}
298
299pub fn success(theme: &Theme) -> Style {
301 let palette = theme.extended_palette();
302
303 styled(palette.background.strong.color, palette.success.base.color)
304}
305
306pub fn warning(theme: &Theme) -> Style {
308 let palette = theme.extended_palette();
309
310 styled(palette.background.strong.color, palette.warning.base.color)
311}
312
313pub fn danger(theme: &Theme) -> Style {
315 let palette = theme.extended_palette();
316
317 styled(palette.background.strong.color, palette.danger.base.color)
318}
319
320fn styled(background: impl Into<Background>, bar: impl Into<Background>) -> Style {
321 Style {
322 background: background.into(),
323 bar: bar.into(),
324 border: border::rounded(2),
325 }
326}