livesplit_core/component/
timer.rs

1//! Provides the `Timer` Component and relevant types for using it. The `Timer`
2//! Component is a component that shows the total time of the current attempt as
3//! a digital clock. The color of the time shown is based on a how well the
4//! current attempt is doing compared to the chosen comparison.
5
6use crate::{
7    analysis::split_color,
8    platform::prelude::*,
9    settings::{Color, Field, Gradient, SemanticColor, SettingsDescription, Value},
10    timing::{
11        formatter::{timer as formatter, Accuracy, DigitsFormat, TimeFormatter},
12        Snapshot,
13    },
14    GeneralLayoutSettings, TimeSpan, TimerPhase, TimingMethod,
15};
16use core::fmt::Write;
17use serde::{Deserialize, Serialize};
18
19/// The `Timer` Component is a component that shows the total time of the current
20/// attempt as a digital clock. The color of the time shown is based on a how
21/// well the current attempt is doing compared to the chosen comparison.
22#[derive(Default, Clone)]
23pub struct Component {
24    settings: Settings,
25}
26
27/// Represents the possible backgrounds for a timer.
28#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
29#[serde(from = "serialize::DeltaGradient", into = "serialize::DeltaGradient")]
30pub enum DeltaGradient {
31    /// A normal gradient of some kind
32    Gradient(Gradient),
33    /// Delta based plain color
34    DeltaPlain,
35    /// Delta based gradient, Vertical
36    DeltaVertical,
37    /// Delta based gradient, horizontal
38    DeltaHorizontal,
39}
40
41impl From<Gradient> for DeltaGradient {
42    fn from(g: Gradient) -> Self {
43        Self::Gradient(g)
44    }
45}
46
47impl Default for DeltaGradient {
48    fn default() -> Self {
49        Self::Gradient(Gradient::default())
50    }
51}
52
53impl DeltaGradient {
54    /// Converts the DeltaGradient to a normal gradient for purposes of rendering
55    ///
56    ///  # Arguments
57    /// * `delta` - the color used to represent the timer's current state
58    pub fn gradient(&self, delta: Color) -> Gradient {
59        let [h, s, v, a] = delta.to_hsva();
60
61        match self {
62            DeltaGradient::Gradient(g) => *g,
63            DeltaGradient::DeltaVertical => {
64                let color_a = Color::hsva(h, s * 0.5, v * 0.25, a * (1.0 / 6.0));
65                let color_b = Color::hsva(h, s * 0.5, v * 0.25, a);
66
67                Gradient::Vertical(color_a, color_b)
68            }
69            DeltaGradient::DeltaPlain => {
70                Gradient::Plain(Color::hsva(h, s * 0.5, v * 0.25, a * (7.0 / 12.0)))
71            }
72            DeltaGradient::DeltaHorizontal => {
73                let color_a = Color::hsva(h, s * 0.5, v * 0.25, a * (1.0 / 6.0));
74                let color_b = Color::hsva(h, s * 0.5, v * 0.25, a);
75
76                Gradient::Horizontal(color_a, color_b)
77            }
78        }
79    }
80}
81/// The Settings for this component.
82#[derive(Clone, Serialize, Deserialize)]
83#[serde(default)]
84pub struct Settings {
85    /// The background shown behind the component.
86    pub background: DeltaGradient,
87    /// Specifies the Timing Method to use. If set to `None` the Timing Method
88    /// of the Timer is used for showing the time. Otherwise the Timing Method
89    /// provided is used.
90    pub timing_method: Option<TimingMethod>,
91    /// The height of the timer.
92    pub height: u32,
93    /// Instead of automatically determining the color of the time shown, based
94    /// on a how well the current attempt is doing, a specific color to always
95    /// be used can be provided instead.
96    pub color_override: Option<Color>,
97    /// The Timer Component automatically converts the color it is supposed to
98    /// use into a gradient and shows that. If this is set to `false` the actual
99    /// color is used instead of a gradient.
100    pub show_gradient: bool,
101    /// Determines how many digits are to always be shown. If the duration is
102    /// lower than the digits to be shown, they are filled up with zeros.
103    pub digits_format: DigitsFormat,
104    /// The accuracy of the time shown.
105    pub accuracy: Accuracy,
106    /// Specifies whether to show the how much time has passed since the start
107    /// current segment, rather than how much time has passed since the start of
108    /// the current attempt.
109    pub is_segment_timer: bool,
110}
111
112impl Default for Settings {
113    fn default() -> Self {
114        Self {
115            background: DeltaGradient::default(),
116            timing_method: None,
117            height: 60,
118            color_override: None,
119            show_gradient: true,
120            digits_format: DigitsFormat::SingleDigitSeconds,
121            accuracy: Accuracy::Hundredths,
122            is_segment_timer: false,
123        }
124    }
125}
126
127/// The state object describes the information to visualize for this component.
128#[derive(Default, Serialize, Deserialize)]
129pub struct State {
130    /// The background shown behind the component.
131    pub background: Gradient,
132    /// The time shown by the component without the fractional part.
133    pub time: String,
134    /// The fractional part of the time shown (including the dot).
135    pub fraction: String,
136    /// The semantic coloring information the time carries.
137    pub semantic_color: SemanticColor,
138    /// The top color of the timer's gradient.
139    pub top_color: Color,
140    /// The bottom color of the timer's gradient.
141    pub bottom_color: Color,
142    /// The height of the timer.
143    pub height: u32,
144    /// This value indicates whether the timer is currently frequently being
145    /// updated. This can be used for rendering optimizations.
146    pub updates_frequently: bool,
147}
148
149#[cfg(feature = "std")]
150impl State {
151    /// Encodes the state object's information as JSON.
152    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
153    where
154        W: std::io::Write,
155    {
156        serde_json::to_writer(writer, self)
157    }
158}
159
160impl Component {
161    /// Creates a new Timer Component.
162    pub fn new() -> Self {
163        Default::default()
164    }
165
166    /// Creates a new Timer Component with the given settings.
167    pub const fn with_settings(settings: Settings) -> Self {
168        Self { settings }
169    }
170
171    /// Accesses the settings of the component.
172    pub const fn settings(&self) -> &Settings {
173        &self.settings
174    }
175
176    /// Grants mutable access to the settings of the component.
177    pub fn settings_mut(&mut self) -> &mut Settings {
178        &mut self.settings
179    }
180
181    /// Accesses the name of the component.
182    pub const fn name(&self) -> &'static str {
183        if self.settings.is_segment_timer {
184            "Segment Timer"
185        } else {
186            "Timer"
187        }
188    }
189
190    /// Updates the component's state based on the timer and layout settings
191    /// provided.
192    pub fn update_state(
193        &self,
194        state: &mut State,
195        timer: &Snapshot<'_>,
196        layout_settings: &GeneralLayoutSettings,
197    ) {
198        let method = self
199            .settings
200            .timing_method
201            .unwrap_or_else(|| timer.current_timing_method());
202
203        let phase = timer.current_phase();
204
205        let (time, semantic_color) = if self.settings.is_segment_timer {
206            let last_split_index = if phase == TimerPhase::Ended {
207                timer.run().len() - 1
208            } else {
209                timer.current_split_index().unwrap_or_default()
210            };
211            let mut segment_time = calculate_live_segment_time(timer, method, last_split_index);
212
213            if segment_time.is_none() && method == TimingMethod::GameTime {
214                segment_time =
215                    calculate_live_segment_time(timer, TimingMethod::RealTime, last_split_index);
216            }
217
218            (segment_time, SemanticColor::Default)
219        } else {
220            let time = timer.current_time();
221            let time = time[method].or(time.real_time).unwrap_or_default();
222            let current_comparison = timer.current_comparison();
223
224            let semantic_color = match phase {
225                TimerPhase::Running if time >= TimeSpan::zero() => {
226                    let pb_split_time = timer
227                        .current_split()
228                        .unwrap()
229                        .comparison(current_comparison)[method];
230
231                    if let Some(pb_split_time) = pb_split_time {
232                        split_color(
233                            timer,
234                            Some(time - pb_split_time),
235                            timer.current_split_index().unwrap(),
236                            true,
237                            false,
238                            current_comparison,
239                            method,
240                        )
241                        .or(SemanticColor::AheadGainingTime)
242                    } else {
243                        SemanticColor::AheadGainingTime
244                    }
245                }
246                TimerPhase::Paused => SemanticColor::Paused,
247                TimerPhase::Ended => {
248                    let pb_time = timer
249                        .run()
250                        .segments()
251                        .last()
252                        .unwrap()
253                        .comparison(current_comparison)[method];
254
255                    if pb_time.map_or(true, |t| time < t) {
256                        SemanticColor::PersonalBest
257                    } else {
258                        SemanticColor::BehindLosingTime
259                    }
260                }
261                _ => SemanticColor::NotRunning,
262            };
263
264            (Some(time), semantic_color)
265        };
266
267        let not_overwritten_visual_color = semantic_color.visualize(layout_settings);
268        let visual_color = if let Some(color) = self.settings.color_override {
269            color
270        } else {
271            not_overwritten_visual_color
272        };
273
274        (state.top_color, state.bottom_color) = if self.settings.show_gradient {
275            top_and_bottom_color(visual_color)
276        } else {
277            (visual_color, visual_color)
278        };
279
280        state.background = self
281            .settings
282            .background
283            .gradient(not_overwritten_visual_color);
284
285        state.time.clear();
286        let _ = write!(
287            state.time,
288            "{}",
289            formatter::Time::with_digits_format(self.settings.digits_format).format(time),
290        );
291
292        state.fraction.clear();
293        let _ = write!(
294            state.fraction,
295            "{}",
296            formatter::Fraction::with_accuracy(self.settings.accuracy).format(time),
297        );
298
299        state.updates_frequently = phase.is_running() && time.is_some();
300        state.semantic_color = semantic_color;
301        state.height = self.settings.height;
302    }
303
304    /// Calculates the component's state based on the timer and the layout
305    /// settings provided.
306    pub fn state(&self, timer: &Snapshot<'_>, layout_settings: &GeneralLayoutSettings) -> State {
307        let mut state = Default::default();
308        self.update_state(&mut state, timer, layout_settings);
309        state
310    }
311
312    /// Accesses a generic description of the settings available for this
313    /// component and their current values.
314    pub fn settings_description(&self) -> SettingsDescription {
315        SettingsDescription::with_fields(vec![
316            Field::new("Background".into(), self.settings.background.into()),
317            Field::new(
318                "Segment Timer".into(),
319                self.settings.is_segment_timer.into(),
320            ),
321            Field::new("Timing Method".into(), self.settings.timing_method.into()),
322            Field::new("Height".into(), u64::from(self.settings.height).into()),
323            Field::new("Text Color".into(), self.settings.color_override.into()),
324            Field::new("Show Gradient".into(), self.settings.show_gradient.into()),
325            Field::new("Digits Format".into(), self.settings.digits_format.into()),
326            Field::new("Accuracy".into(), self.settings.accuracy.into()),
327        ])
328    }
329
330    /// Sets a setting's value by its index to the given value.
331    ///
332    /// # Panics
333    ///
334    /// This panics if the type of the value to be set is not compatible with
335    /// the type of the setting's value. A panic can also occur if the index of
336    /// the setting provided is out of bounds.
337    pub fn set_value(&mut self, index: usize, value: Value) {
338        match index {
339            0 => self.settings.background = value.into(),
340            1 => self.settings.is_segment_timer = value.into(),
341            2 => self.settings.timing_method = value.into(),
342            3 => self.settings.height = value.into_uint().unwrap() as _,
343            4 => self.settings.color_override = value.into(),
344            5 => self.settings.show_gradient = value.into(),
345            6 => self.settings.digits_format = value.into(),
346            7 => self.settings.accuracy = value.into(),
347            _ => panic!("Unsupported Setting Index"),
348        }
349    }
350}
351
352/// Calculates the top and bottom color the Timer Component would use for the
353/// gradient of the times it is showing.
354pub fn top_and_bottom_color(color: Color) -> (Color, Color) {
355    let [h, s, v, a] = color.to_hsva();
356
357    let top_color = Color::hsva(h, 0.5 * s, (1.5 * v + 0.1).min(1.0), a);
358    let bottom_color = Color::hsva(h, s, 0.8 * v, a);
359
360    (top_color, bottom_color)
361}
362
363fn calculate_live_segment_time(
364    timer: &Snapshot<'_>,
365    timing_method: TimingMethod,
366    last_split_index: usize,
367) -> Option<TimeSpan> {
368    let last_split = if last_split_index > 0 {
369        timer.run().segment(last_split_index - 1).split_time()[timing_method]
370    } else {
371        Some(TimeSpan::zero())
372    };
373
374    if timer.current_phase() == TimerPhase::NotRunning {
375        Some(timer.run().offset())
376    } else {
377        Some(timer.current_time()[timing_method]? - last_split?)
378    }
379}
380
381// FIXME: Workaround for #[serde(flatten)] not being a thing on enums.
382mod serialize {
383    #[derive(serde::Serialize, serde::Deserialize)]
384    #[serde(untagged)]
385    pub enum DeltaGradient {
386        Gradient(super::Gradient),
387        Delta(Delta),
388    }
389
390    #[derive(serde::Serialize, serde::Deserialize)]
391    #[allow(clippy::enum_variant_names)]
392    pub enum Delta {
393        DeltaPlain,
394        DeltaVertical,
395        DeltaHorizontal,
396    }
397
398    impl From<DeltaGradient> for super::DeltaGradient {
399        fn from(v: DeltaGradient) -> Self {
400            match v {
401                DeltaGradient::Gradient(g) => super::DeltaGradient::Gradient(g),
402                DeltaGradient::Delta(d) => match d {
403                    Delta::DeltaPlain => super::DeltaGradient::DeltaPlain,
404                    Delta::DeltaVertical => super::DeltaGradient::DeltaVertical,
405                    Delta::DeltaHorizontal => super::DeltaGradient::DeltaHorizontal,
406                },
407            }
408        }
409    }
410
411    impl From<super::DeltaGradient> for DeltaGradient {
412        fn from(v: super::DeltaGradient) -> Self {
413            match v {
414                super::DeltaGradient::Gradient(g) => DeltaGradient::Gradient(g),
415                super::DeltaGradient::DeltaPlain => DeltaGradient::Delta(Delta::DeltaPlain),
416                super::DeltaGradient::DeltaVertical => DeltaGradient::Delta(Delta::DeltaVertical),
417                super::DeltaGradient::DeltaHorizontal => {
418                    DeltaGradient::Delta(Delta::DeltaHorizontal)
419                }
420            }
421        }
422    }
423}