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