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::widget::operation::Operation;
28use crate::core::widget::operation::accessible::{Accessible, Live, Role, Value};
29use crate::core::{
30 self, Background, Color, Element, Layout, Length, Rectangle, Size, Theme, Widget,
31};
32
33use std::ops::RangeInclusive;
34
35pub 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> 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 operate(
168 &mut self,
169 _tree: &mut Tree,
170 layout: Layout<'_>,
171 _renderer: &Renderer,
172 operation: &mut dyn Operation,
173 ) {
174 operation.accessible(
175 None,
176 layout.bounds(),
177 &Accessible {
178 role: Role::ProgressIndicator,
179 value: Some(Value::Numeric {
180 current: self.value as f64,
181 min: *self.range.start() as f64,
182 max: *self.range.end() as f64,
183 step: None,
184 }),
185 live: Some(Live::Polite),
186 ..Accessible::default()
187 },
188 );
189 }
190
191 fn draw(
192 &self,
193 _tree: &Tree,
194 renderer: &mut Renderer,
195 theme: &Theme,
196 _style: &renderer::Style,
197 layout: Layout<'_>,
198 _cursor: mouse::Cursor,
199 _viewport: &Rectangle,
200 ) {
201 let bounds = layout.bounds();
202 let (range_start, range_end) = self.range.clone().into_inner();
203
204 let length = if self.is_vertical {
205 bounds.height
206 } else {
207 bounds.width
208 };
209
210 let active_progress_length = if range_start >= range_end {
211 0.0
212 } else {
213 length * (self.value - range_start) / (range_end - range_start)
214 };
215
216 let style = theme.style(&self.class);
217
218 renderer.fill_quad(
219 renderer::Quad {
220 bounds: Rectangle { ..bounds },
221 border: style.border,
222 ..renderer::Quad::default()
223 },
224 style.background,
225 );
226
227 if active_progress_length > 0.0 {
228 let bounds = if self.is_vertical {
229 Rectangle {
230 y: bounds.y + bounds.height - active_progress_length,
231 height: active_progress_length,
232 ..bounds
233 }
234 } else {
235 Rectangle {
236 width: active_progress_length,
237 ..bounds
238 }
239 };
240
241 renderer.fill_quad(
242 renderer::Quad {
243 bounds,
244 border: Border {
245 color: Color::TRANSPARENT,
246 ..style.border
247 },
248 ..renderer::Quad::default()
249 },
250 style.bar,
251 );
252 }
253 }
254}
255
256impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>
257 for Element<'a, Message, Theme, Renderer>
258where
259 Message: 'a,
260 Theme: 'a + Catalog,
261 Renderer: 'a + core::Renderer,
262{
263 fn from(progress_bar: ProgressBar<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
264 Element::new(progress_bar)
265 }
266}
267
268#[derive(Debug, Clone, Copy, PartialEq)]
270pub struct Style {
271 pub background: Background,
273 pub bar: Background,
275 pub border: Border,
277}
278
279pub trait Catalog: Sized {
281 type Class<'a>;
283
284 fn default<'a>() -> Self::Class<'a>;
286
287 fn style(&self, class: &Self::Class<'_>) -> Style;
289}
290
291pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
295
296impl Catalog for Theme {
297 type Class<'a> = StyleFn<'a, Self>;
298
299 fn default<'a>() -> Self::Class<'a> {
300 Box::new(primary)
301 }
302
303 fn style(&self, class: &Self::Class<'_>) -> Style {
304 class(self)
305 }
306}
307
308pub fn primary(theme: &Theme) -> Style {
310 let palette = theme.palette();
311
312 styled(palette.background.strong.color, palette.primary.base.color)
313}
314
315pub fn secondary(theme: &Theme) -> Style {
317 let palette = theme.palette();
318
319 styled(
320 palette.background.strong.color,
321 palette.secondary.base.color,
322 )
323}
324
325pub fn success(theme: &Theme) -> Style {
327 let palette = theme.palette();
328
329 styled(palette.background.strong.color, palette.success.base.color)
330}
331
332pub fn warning(theme: &Theme) -> Style {
334 let palette = theme.palette();
335
336 styled(palette.background.strong.color, palette.warning.base.color)
337}
338
339pub fn danger(theme: &Theme) -> Style {
341 let palette = theme.palette();
342
343 styled(palette.background.strong.color, palette.danger.base.color)
344}
345
346fn styled(background: impl Into<Background>, bar: impl Into<Background>) -> Style {
347 Style {
348 background: background.into(),
349 bar: bar.into(),
350 border: border::rounded(2),
351 }
352}