mirror of
https://github.com/danbulant/cushy
synced 2026-06-12 11:00:30 +00:00
This reduces the complexity of operations capable with Dynamic, and also makes it easier to shortcut deadlocking operations.
235 lines
7.7 KiB
Rust
235 lines
7.7 KiB
Rust
//! 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<Progress>,
|
|
}
|
|
|
|
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<Progress>) -> 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<T = ZeroToOne> {
|
|
/// 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<AnimationHandle>,
|
|
start: &Dynamic<ZeroToOne>,
|
|
end: &Dynamic<ZeroToOne>,
|
|
) {
|
|
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<T>: IntoDynamic<T> + 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<T>`. If
|
|
/// `None` is the maximum value, an indeterminant progress bar will be
|
|
/// displayed.
|
|
fn progress_bar_to(self, max: impl IntoValue<T::Value>) -> ProgressBar
|
|
where
|
|
T: Send,
|
|
T::Value: PartialEq + Ranged + Send + Clone,
|
|
{
|
|
let max = max.into_value();
|
|
match max {
|
|
Value::Constant(max) => self.progress_bar_between(<T::Value>::MIN..=max),
|
|
Value::Dynamic(max) => {
|
|
self.progress_bar_between(max.map_each(|max| <T::Value>::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<T>`. If
|
|
/// `None` is specified as the range, an indeterminant progress bar will be
|
|
/// displayed.
|
|
fn progress_bar_between<Range>(self, range: Range) -> ProgressBar
|
|
where
|
|
T: Send,
|
|
T::Value: Send,
|
|
Range: IntoValue<RangeInclusive<T::Value>>,
|
|
{
|
|
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<U> Progressable<U> for Dynamic<U> 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<RangeInclusive<&Self::Value>>) -> Progress;
|
|
}
|
|
|
|
impl<T> ProgressValue for T
|
|
where
|
|
T: Ranged + PercentBetween + 'static,
|
|
{
|
|
type Value = T;
|
|
|
|
fn to_progress(&self, range: Option<RangeInclusive<&Self::Value>>) -> 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<T> ProgressValue for Option<T>
|
|
where
|
|
T: Ranged + PercentBetween + 'static,
|
|
{
|
|
type Value = T;
|
|
|
|
fn to_progress(&self, range: Option<RangeInclusive<&Self::Value>>) -> Progress {
|
|
self.as_ref()
|
|
.map_or(Progress::Indeterminant, |value| value.to_progress(range))
|
|
}
|
|
}
|
|
|
|
impl<T> ProgressValue for Progress<T>
|
|
where
|
|
T: Ranged + PercentBetween + 'static,
|
|
{
|
|
type Value = T;
|
|
|
|
fn to_progress(&self, range: Option<RangeInclusive<&Self::Value>>) -> Progress {
|
|
match self {
|
|
Progress::Indeterminant => Progress::Indeterminant,
|
|
Progress::Percent(value) => value.to_progress(range),
|
|
}
|
|
}
|
|
}
|