use crate::AnyView;
use crate::SignalExt;
use crate::ViewExt;
use nami::Computed;
use nami::signal::IntoComputed;
use waterui_controls::IntoLabel;
use waterui_core::View;
use waterui_core::configurable;
use waterui_core::layout::StretchAxis;
use waterui_macros::text;
fn progress_value_label(value: &Computed<f64>) -> Computed<String> {
value
.clone()
.map(|value| {
if value.is_finite() {
format!("{:.0}%", value.clamp(0.0, 1.0) * 100.0)
} else {
String::new()
}
})
.computed()
}
#[non_exhaustive]
#[derive(Debug)]
pub struct ProgressConfig {
pub label: AnyView,
pub value_label: AnyView,
pub value: Computed<f64>,
pub style: ProgressStyle,
pub four_color: bool,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub enum ProgressStyle {
Circular,
Linear,
Loading,
}
configurable!(
Progress,
ProgressConfig,
|config| match config.style {
ProgressStyle::Linear => StretchAxis::Horizontal,
ProgressStyle::Circular | ProgressStyle::Loading => StretchAxis::None,
}
);
#[derive(Debug)]
pub struct ProgressWithTotal(Progress);
impl ProgressWithTotal {
#[must_use]
pub fn label(self, label: impl IntoLabel) -> Self {
Self(self.0.label(label))
}
#[must_use]
pub fn circular(self) -> Self {
Self(self.0.circular())
}
#[must_use]
pub fn linear(self) -> Self {
Self(self.0.linear())
}
}
impl View for ProgressWithTotal {
fn body(self, _env: &waterui_core::Environment) -> impl View {
self.0
}
}
impl Progress {
pub fn new(value: impl IntoComputed<f64>) -> Self {
let value = value.into_signal().computed();
let value_label = progress_value_label(&value);
Self(ProgressConfig {
label: text!("Please wait...").anyview(),
value_label: text!("{value_label}").anyview(),
value,
style: ProgressStyle::Linear,
four_color: false,
})
}
pub fn total(mut self, total: impl IntoComputed<f64>) -> ProgressWithTotal {
let total = total.into_signal();
let value = self.0.value;
self.0.value = total.zip(&value).map(|(t, v)| v / t).computed();
let value_label = progress_value_label(&self.0.value);
self.0.value_label = text!("{value_label}").anyview();
ProgressWithTotal(self)
}
#[must_use]
pub fn infinity() -> Self {
Self::new(f32::INFINITY)
}
#[must_use]
pub fn label(mut self, label: impl IntoLabel) -> Self {
self.0.label = label.into_label().anyview();
self
}
const fn style(mut self, style: ProgressStyle) -> Self {
self.0.style = style;
self
}
#[must_use]
pub const fn circular(self) -> Self {
self.style(ProgressStyle::Circular)
}
#[must_use]
pub const fn linear(self) -> Self {
self.style(ProgressStyle::Linear)
}
#[must_use]
pub const fn loading(self) -> Self {
self.style(ProgressStyle::Loading)
}
#[must_use]
pub const fn four_color(mut self) -> Self {
self.0.four_color = true;
self
}
}
pub fn progress(value: impl IntoComputed<f64>) -> Progress {
Progress::new(value)
}
#[must_use]
pub fn loading() -> Progress {
Progress::infinity().loading()
}
#[cfg(test)]
mod tests {
use nami::{Computed, Signal as _};
use super::progress_value_label;
#[test]
fn progress_value_label_formats_fraction_as_percent() {
assert_eq!(progress_value_label(&Computed::constant(0.42)).get(), "42%");
assert_eq!(
progress_value_label(&Computed::constant(0.755)).get(),
"76%"
);
}
#[test]
fn progress_value_label_clamps_to_valid_progress_range() {
assert_eq!(progress_value_label(&Computed::constant(-0.25)).get(), "0%");
assert_eq!(
progress_value_label(&Computed::constant(1.25)).get(),
"100%"
);
}
}