//! Widgets for displaying progress indicators. use std::ops::RangeInclusive; use std::time::Duration; use kludgine::figures::Ranged; use crate::animation::easings::{EaseInOutQuadradic, EaseOutQuadradic}; use crate::animation::{ AnimationHandle, AnimationTarget, IntoAnimate, PercentBetween, Spawn, ZeroToOne, }; use crate::value::{Dynamic, IntoDynamic, IntoValue, MapEach, Value}; use crate::widget::{MakeWidget, WidgetInstance}; use crate::widgets::slider::Slidable; use crate::widgets::Data; /// A bar-shaped progress indicator. #[derive(Debug)] pub struct ProgressBar { progress: Value, } impl ProgressBar { /// Returns an indeterminant progress bar. #[must_use] pub const fn indeterminant() -> Self { Self { progress: Value::Constant(Progress::Indeterminant), } } /// Returns a new progress bar that displays `progress`. #[must_use] pub fn new(progress: impl IntoDynamic) -> Self { Self { progress: Value::Dynamic(progress.into_dynamic()), } } } /// A measurement of progress for an indicator widget like [`ProgressBar`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Progress { /// The task has an indeterminant length. Indeterminant, /// The task is a specified amount complete. Percent(T), } impl MakeWidget for ProgressBar { fn make_widget(self) -> WidgetInstance { let start = Dynamic::new(ZeroToOne::ZERO); let end = Dynamic::new(ZeroToOne::ZERO); let value = (&start, &end).map_each(|(start, end)| *start..=*end); let mut indeterminant_animation = None; update_progress_bar( self.progress.get(), &mut indeterminant_animation, &start, &end, ); let slider = value.slider().knobless().non_interactive(); match self.progress { Value::Dynamic(progress) => { progress.for_each(move |progress| { update_progress_bar(*progress, &mut indeterminant_animation, &start, &end); }); Data::new_wrapping(progress, slider).make_widget() } Value::Constant(_) => Data::new_wrapping(indeterminant_animation, slider).make_widget(), } } } fn update_progress_bar( progress: Progress, indeterminant_animation: &mut Option, start: &Dynamic, end: &Dynamic, ) { match progress { Progress::Indeterminant => { if indeterminant_animation.is_none() { *indeterminant_animation = Some( ( start.transition_to(ZeroToOne::ZERO), end.transition_to(ZeroToOne::ZERO), ) .immediately() .and_then( end.transition_to(ZeroToOne::new(0.75)) .over(Duration::from_millis(500)) .with_easing(EaseOutQuadradic), ) .and_then( start .transition_to(ZeroToOne::new(0.25)) .over(Duration::from_millis(500)) .with_easing(EaseOutQuadradic), ) .and_then( end.transition_to(ZeroToOne::ONE) .over(Duration::from_millis(250)) .with_easing(EaseOutQuadradic), ) .and_then( start .transition_to(ZeroToOne::ONE) .over(Duration::from_millis(250)) .with_easing(EaseInOutQuadradic), ) .cycle() .spawn(), ); } } Progress::Percent(value) => { let _stopped_animation = indeterminant_animation.take(); start.set(ZeroToOne::ZERO); end.set(value); } } } /// A value that can be used in a progress indicator. pub trait Progressable: IntoDynamic + Sized where T: ProgressValue + Send, { /// Returns a new progress bar that displays progress from `T::MIN` to /// `T::MAX`. fn progress_bar(self) -> ProgressBar { ProgressBar::new( self.into_dynamic() .map_each(|value| value.to_progress(None)), ) } /// Returns a new progress bar that displays progress from `T::MIN` to /// `max`. The maximum value can be either a `T` or an `Option`. If /// `None` is the maximum value, an indeterminant progress bar will be /// displayed. fn progress_bar_to(self, max: impl IntoValue) -> ProgressBar where T: Send, T::Value: PartialEq + Ranged + Send + Clone, { let max = max.into_value(); match max { Value::Constant(max) => self.progress_bar_between(::MIN..=max), Value::Dynamic(max) => { self.progress_bar_between(max.map_each(|max| ::MIN..=max.clone())) } } } /// Returns a new progress bar that displays progress over the specified /// `range` of `T`. The range can be either a `T..=T` or an `Option`. If /// `None` is specified as the range, an indeterminant progress bar will be /// displayed. fn progress_bar_between(self, range: Range) -> ProgressBar where T: Send, T::Value: Send, Range: IntoValue>, { let value = self.into_dynamic(); let range = range.into_value(); match range { Value::Constant(range) => ProgressBar::new( value.map_each(move |value| value.to_progress(Some(range.start()..=range.end()))), ), Value::Dynamic(range) => { ProgressBar::new((&range, &value).map_each(|(range, value)| { value.to_progress(Some(range.start()..=range.end())) })) } } } } impl Progressable for Dynamic where U: ProgressValue + Send {} /// A value that can be used in a progress indicator. pub trait ProgressValue: 'static { /// The type that progress is ranged over. type Value; /// Converts this value to a progress using the range given, if provided. If /// no range is provided, the full range of the type should be considered. fn to_progress(&self, range: Option>) -> Progress; } impl ProgressValue for T where T: Ranged + PercentBetween + 'static, { type Value = T; fn to_progress(&self, range: Option>) -> Progress { if let Some(range) = range { Progress::Percent(self.percent_between(range.start(), range.end())) } else { Progress::Percent(self.percent_between(&T::MIN, &T::MAX)) } } } impl ProgressValue for Option where T: Ranged + PercentBetween + 'static, { type Value = T; fn to_progress(&self, range: Option>) -> Progress { self.as_ref() .map_or(Progress::Indeterminant, |value| value.to_progress(range)) } } impl ProgressValue for Progress where T: Ranged + PercentBetween + 'static, { type Value = T; fn to_progress(&self, range: Option>) -> Progress { match self { Progress::Indeterminant => Progress::Indeterminant, Progress::Percent(value) => value.to_progress(range), } } }