Skip to main content

ferroui_base/animation/
iteration_count.rs

1use crate::utilities::FormatError;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::str::FromStr;
5
6/// Defines the valid modes for an [`IterationCount`].
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[repr(i32)]
9pub enum IterationType {
10    #[default]
11    Many = 0,
12    Infinite = 1,
13}
14
15/// Determines the number of iterations of an animation. Also defines its
16/// repeat type.
17#[derive(Clone, Copy, Debug, Default)]
18pub struct IterationCount {
19    type_: IterationType,
20    value: u64,
21}
22
23impl IterationCount {
24    /// An instance that makes the animation loop forever.
25    pub const INFINITE: IterationCount = IterationCount { type_: IterationType::Infinite, value: 0 };
26
27    /// Creates a count of `value` iterations.
28    pub const fn new(value: u64) -> Self {
29        Self { type_: IterationType::Many, value }
30    }
31
32    /// Creates a count with an explicit repeat type.
33    pub const fn with_type(value: u64, type_: IterationType) -> Self {
34        Self { type_, value }
35    }
36
37    /// The repeat type.
38    pub const fn repeat_type(&self) -> IterationType {
39        self.type_
40    }
41
42    /// Whether the animation loops forever.
43    pub const fn is_infinite(&self) -> bool {
44        matches!(self.type_, IterationType::Infinite)
45    }
46
47    /// The number of iterations.
48    pub const fn value(&self) -> u64 {
49        self.value
50    }
51
52    /// Parses `"Infinite"` (case-insensitive) or a non-negative integer.
53    pub fn parse(s: &str) -> Result<IterationCount, FormatError> {
54        let s = s.to_uppercase();
55        let s = s.trim();
56
57        if s.ends_with("INFINITE") {
58            Ok(Self::INFINITE)
59        } else {
60            if s.starts_with('-') {
61                return Err(FormatError::from_string("IterationCount can't be a negative number.".to_string()));
62            }
63
64            let digits = s.strip_prefix('+').unwrap_or(s);
65            if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
66                return Err(FormatError::from_string(format!("Invalid IterationCount string: \"{s}\".")));
67            }
68            let value = digits
69                .parse::<u64>()
70                .map_err(|_| FormatError::from_string(format!("Invalid IterationCount string: \"{s}\".")))?;
71
72            Ok(IterationCount::new(value))
73        }
74    }
75}
76
77impl PartialEq for IterationCount {
78    fn eq(&self, other: &Self) -> bool {
79        (self.is_infinite() && other.is_infinite()) || (self.value == other.value && self.type_ == other.type_)
80    }
81}
82
83impl Eq for IterationCount {}
84
85impl Hash for IterationCount {
86    fn hash<H: Hasher>(&self, state: &mut H) {
87        // Equal values hash equally: the count is ignored when infinite.
88        if self.is_infinite() {
89            IterationType::Infinite.hash(state);
90        } else {
91            self.value.hash(state);
92            self.type_.hash(state);
93        }
94    }
95}
96
97impl fmt::Display for IterationCount {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        if self.is_infinite() {
100            f.write_str("Infinite")
101        } else {
102            write!(f, "{}", self.value)
103        }
104    }
105}
106
107impl FromStr for IterationCount {
108    type Err = FormatError;
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        IterationCount::parse(s)
111    }
112}