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