1use crate::core::border::{self, Border};
23use crate::core::layout;
24use crate::core::mouse;
25use crate::core::renderer;
26use crate::core::widget::{Meta, Tree};
27use crate::core::{self, Background, Color, Layout, Length, Rectangle, Size, Theme, Widget};
28
29use std::ops::RangeInclusive;
30
31pub struct ProgressBar<'a, Theme = crate::Theme>
53where
54 Theme: Catalog,
55{
56 range: RangeInclusive<f32>,
57 value: f32,
58 length: Length,
59 girth: Length,
60 is_vertical: bool,
61 class: Theme::Class<'a>,
62}
63
64impl<'a, Theme> ProgressBar<'a, Theme>
65where
66 Theme: Catalog,
67{
68 pub const DEFAULT_GIRTH: f32 = 30.0;
70
71 pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
77 ProgressBar {
78 value: value.clamp(*range.start(), *range.end()),
79 range,
80 length: Length::Fill,
81 girth: Length::from(Self::DEFAULT_GIRTH),
82 is_vertical: false,
83 class: Theme::default(),
84 }
85 }
86
87 pub fn length(mut self, length: impl Into<Length>) -> Self {
89 self.length = length.into();
90 self
91 }
92
93 pub fn girth(mut self, girth: impl Into<Length>) -> Self {
95 self.girth = girth.into();
96 self
97 }
98
99 pub fn vertical(mut self, vertical: bool) -> Self {
103 self.is_vertical = vertical;
104 self
105 }
106
107 #[must_use]
109 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
110 where
111 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
112 {
113 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
114 self
115 }
116
117 #[cfg(feature = "advanced")]
119 #[must_use]
120 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
121 self.class = class.into();
122 self
123 }
124
125 fn width(&self) -> Length {
126 if self.is_vertical {
127 self.girth
128 } else {
129 self.length
130 }
131 }
132
133 fn height(&self) -> Length {
134 if self.is_vertical {
135 self.length
136 } else {
137 self.girth
138 }
139 }
140}
141
142impl<Theme> Meta for ProgressBar<'_, Theme> where Theme: Catalog {}
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(&mut self, tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits) {
157 tree.size = layout::atomic(limits, self.width(), self.height());
158 }
159
160 fn draw(
161 &self,
162 _tree: &Tree,
163 renderer: &mut Renderer,
164 theme: &Theme,
165 _style: &renderer::Style,
166 layout: Layout,
167 _cursor: mouse::Cursor,
168 _viewport: &Rectangle,
169 ) {
170 let bounds = layout.bounds();
171 let (range_start, range_end) = self.range.clone().into_inner();
172
173 let length = if self.is_vertical {
174 bounds.height
175 } else {
176 bounds.width
177 };
178
179 let active_progress_length = if range_start >= range_end {
180 0.0
181 } else {
182 length * (self.value - range_start) / (range_end - range_start)
183 };
184
185 let style = theme.style(&self.class);
186
187 renderer.fill_quad(
188 renderer::Quad {
189 bounds: Rectangle { ..bounds },
190 border: style.border,
191 ..renderer::Quad::default()
192 },
193 style.background,
194 );
195
196 if active_progress_length > 0.0 {
197 let bounds = if self.is_vertical {
198 Rectangle {
199 y: bounds.y + bounds.height - active_progress_length,
200 height: active_progress_length,
201 ..bounds
202 }
203 } else {
204 Rectangle {
205 width: active_progress_length,
206 ..bounds
207 }
208 };
209
210 renderer.fill_quad(
211 renderer::Quad {
212 bounds,
213 border: Border {
214 color: Color::TRANSPARENT,
215 ..style.border
216 },
217 ..renderer::Quad::default()
218 },
219 style.bar,
220 );
221 }
222 }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq)]
227pub struct Style {
228 pub background: Background,
230 pub bar: Background,
232 pub border: Border,
234}
235
236pub trait Catalog: Sized {
238 type Class<'a>;
240
241 fn default<'a>() -> Self::Class<'a>;
243
244 fn style(&self, class: &Self::Class<'_>) -> Style;
246}
247
248pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
252
253impl Catalog for Theme {
254 type Class<'a> = StyleFn<'a, Self>;
255
256 fn default<'a>() -> Self::Class<'a> {
257 Box::new(primary)
258 }
259
260 fn style(&self, class: &Self::Class<'_>) -> Style {
261 class(self)
262 }
263}
264
265pub fn primary(theme: &Theme) -> Style {
267 let palette = theme.palette();
268
269 styled(palette.background.strong.color, palette.primary.base.color)
270}
271
272pub fn secondary(theme: &Theme) -> Style {
274 let palette = theme.palette();
275
276 styled(
277 palette.background.strong.color,
278 palette.secondary.base.color,
279 )
280}
281
282pub fn success(theme: &Theme) -> Style {
284 let palette = theme.palette();
285
286 styled(palette.background.strong.color, palette.success.base.color)
287}
288
289pub fn warning(theme: &Theme) -> Style {
291 let palette = theme.palette();
292
293 styled(palette.background.strong.color, palette.warning.base.color)
294}
295
296pub fn danger(theme: &Theme) -> Style {
298 let palette = theme.palette();
299
300 styled(palette.background.strong.color, palette.danger.base.color)
301}
302
303fn styled(background: impl Into<Background>, bar: impl Into<Background>) -> Style {
304 Style {
305 background: background.into(),
306 bar: bar.into(),
307 border: border::rounded(2),
308 }
309}