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 width: Length,
63 height: Option<Length>,
64 class: Theme::Class<'a>,
65}
66
67impl<'a, Theme> ProgressBar<'a, Theme>
68where
69 Theme: Catalog,
70{
71 pub const DEFAULT_HEIGHT: 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 width: Length::Fill,
84 height: None,
85 class: Theme::default(),
86 }
87 }
88
89 pub fn width(mut self, width: impl Into<Length>) -> Self {
91 self.width = width.into();
92 self
93 }
94
95 pub fn height(mut self, height: impl Into<Length>) -> Self {
97 self.height = Some(height.into());
98 self
99 }
100
101 #[must_use]
103 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
104 where
105 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
106 {
107 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
108 self
109 }
110
111 #[cfg(feature = "advanced")]
113 #[must_use]
114 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
115 self.class = class.into();
116 self
117 }
118}
119
120impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
121 for ProgressBar<'a, Theme>
122where
123 Theme: Catalog,
124 Renderer: core::Renderer,
125{
126 fn size(&self) -> Size<Length> {
127 Size {
128 width: self.width,
129 height: self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)),
130 }
131 }
132
133 fn layout(
134 &self,
135 _tree: &mut Tree,
136 _renderer: &Renderer,
137 limits: &layout::Limits,
138 ) -> layout::Node {
139 layout::atomic(
140 limits,
141 self.width,
142 self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)),
143 )
144 }
145
146 fn draw(
147 &self,
148 _state: &Tree,
149 renderer: &mut Renderer,
150 theme: &Theme,
151 _style: &renderer::Style,
152 layout: Layout<'_>,
153 _cursor: mouse::Cursor,
154 _viewport: &Rectangle,
155 ) {
156 let bounds = layout.bounds();
157 let (range_start, range_end) = self.range.clone().into_inner();
158
159 let active_progress_width = if range_start >= range_end {
160 0.0
161 } else {
162 bounds.width * (self.value - range_start)
163 / (range_end - range_start)
164 };
165
166 let style = theme.style(&self.class);
167
168 renderer.fill_quad(
169 renderer::Quad {
170 bounds: Rectangle { ..bounds },
171 border: style.border,
172 ..renderer::Quad::default()
173 },
174 style.background,
175 );
176
177 if active_progress_width > 0.0 {
178 renderer.fill_quad(
179 renderer::Quad {
180 bounds: Rectangle {
181 width: active_progress_width,
182 ..bounds
183 },
184 border: Border {
185 color: Color::TRANSPARENT,
186 ..style.border
187 },
188 ..renderer::Quad::default()
189 },
190 style.bar,
191 );
192 }
193 }
194}
195
196impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>
197 for Element<'a, Message, Theme, Renderer>
198where
199 Message: 'a,
200 Theme: 'a + Catalog,
201 Renderer: 'a + core::Renderer,
202{
203 fn from(
204 progress_bar: ProgressBar<'a, Theme>,
205 ) -> Element<'a, Message, Theme, Renderer> {
206 Element::new(progress_bar)
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq)]
212pub struct Style {
213 pub background: Background,
215 pub bar: Background,
217 pub border: Border,
219}
220
221pub trait Catalog: Sized {
223 type Class<'a>;
225
226 fn default<'a>() -> Self::Class<'a>;
228
229 fn style(&self, class: &Self::Class<'_>) -> Style;
231}
232
233pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
237
238impl Catalog for Theme {
239 type Class<'a> = StyleFn<'a, Self>;
240
241 fn default<'a>() -> Self::Class<'a> {
242 Box::new(primary)
243 }
244
245 fn style(&self, class: &Self::Class<'_>) -> Style {
246 class(self)
247 }
248}
249
250pub fn primary(theme: &Theme) -> Style {
252 let palette = theme.extended_palette();
253
254 styled(
255 palette.background.strong.color,
256 palette.primary.strong.color,
257 )
258}
259
260pub fn secondary(theme: &Theme) -> Style {
262 let palette = theme.extended_palette();
263
264 styled(
265 palette.background.strong.color,
266 palette.secondary.base.color,
267 )
268}
269
270pub fn success(theme: &Theme) -> Style {
272 let palette = theme.extended_palette();
273
274 styled(palette.background.strong.color, palette.success.base.color)
275}
276
277pub fn danger(theme: &Theme) -> Style {
279 let palette = theme.extended_palette();
280
281 styled(palette.background.strong.color, palette.danger.base.color)
282}
283
284fn styled(
285 background: impl Into<Background>,
286 bar: impl Into<Background>,
287) -> Style {
288 Style {
289 background: background.into(),
290 bar: bar.into(),
291 border: border::rounded(2),
292 }
293}