livesplit_core/component/delta/
mod.rs

1//! Provides the Delta Component and relevant types for using it. The Delta
2//! Component is a component that shows the how far ahead or behind the current
3//! attempt is compared to the chosen comparison.
4
5use super::key_value;
6use crate::{
7    analysis::{delta, state_helper},
8    comparison,
9    platform::prelude::*,
10    settings::{Color, Field, Gradient, SemanticColor, SettingsDescription, Value},
11    timing::{
12        formatter::{Accuracy, Delta, TimeFormatter},
13        Snapshot,
14    },
15    GeneralLayoutSettings,
16};
17use alloc::borrow::Cow;
18use core::fmt::Write;
19use serde::{Deserialize, Serialize};
20
21#[cfg(test)]
22mod tests;
23
24/// The Delta Component is a component that shows the how far ahead or behind
25/// the current attempt is compared to the chosen comparison.
26#[derive(Default, Clone)]
27pub struct Component {
28    settings: Settings,
29}
30
31/// The Settings for this component.
32#[derive(Clone, Serialize, Deserialize)]
33#[serde(default)]
34pub struct Settings {
35    /// The background shown behind the component.
36    pub background: Gradient,
37    /// The comparison chosen. Uses the Timer's current comparison if set to
38    /// `None`.
39    pub comparison_override: Option<String>,
40    /// Specifies whether to display the name of the component and its value in
41    /// two separate rows.
42    pub display_two_rows: bool,
43    /// The color of the label. If `None` is specified, the color is taken from
44    /// the layout.
45    pub label_color: Option<Color>,
46    /// Specifies if the decimals should not be shown anymore when the
47    /// visualized delta is above one minute.
48    pub drop_decimals: bool,
49    /// The accuracy of the time shown.
50    pub accuracy: Accuracy,
51}
52
53impl Default for Settings {
54    fn default() -> Self {
55        Self {
56            background: key_value::DEFAULT_GRADIENT,
57            comparison_override: None,
58            display_two_rows: false,
59            label_color: None,
60            drop_decimals: true,
61            accuracy: Accuracy::Tenths,
62        }
63    }
64}
65
66impl Component {
67    /// Creates a new Delta Component.
68    pub fn new() -> Self {
69        Default::default()
70    }
71
72    /// Creates a new Delta Component with the given settings.
73    pub const fn with_settings(settings: Settings) -> Self {
74        Self { settings }
75    }
76
77    /// Accesses the settings of the component.
78    pub const fn settings(&self) -> &Settings {
79        &self.settings
80    }
81
82    /// Grants mutable access to the settings of the component.
83    pub fn settings_mut(&mut self) -> &mut Settings {
84        &mut self.settings
85    }
86
87    /// Accesses the name of the component.
88    pub fn name(&self) -> Cow<'static, str> {
89        if let Some(comparison) = &self.settings.comparison_override {
90            format!("Delta ({comparison})").into()
91        } else {
92            "Delta".into()
93        }
94    }
95
96    /// Updates the component's state based on the timer and layout settings
97    /// provided.
98    pub fn update_state(
99        &self,
100        state: &mut key_value::State,
101        timer: &Snapshot<'_>,
102        layout_settings: &GeneralLayoutSettings,
103    ) {
104        let comparison = comparison::resolve(&self.settings.comparison_override, timer);
105        let text = comparison.unwrap_or_else(|| timer.current_comparison());
106        let comparison = comparison::or_current(comparison, timer);
107
108        let (delta, use_live_delta) = delta::calculate(timer, comparison);
109
110        let mut index = timer.current_split_index();
111        if !use_live_delta {
112            index = index.and_then(|i| i.checked_sub(1));
113        }
114
115        let semantic_color = if let Some(index) = index {
116            state_helper::split_color(
117                timer,
118                delta,
119                index,
120                true,
121                false,
122                comparison,
123                timer.current_timing_method(),
124            )
125        } else {
126            SemanticColor::Default
127        };
128
129        let value_color = Some(semantic_color.visualize(layout_settings));
130
131        state.background = self.settings.background;
132        state.key_color = self.settings.label_color;
133        state.value_color = value_color;
134
135        state.key.clear();
136        state.key.push_str(text);
137
138        state.value.clear();
139        let _ = write!(
140            state.value,
141            "{}",
142            Delta::custom(self.settings.drop_decimals, self.settings.accuracy).format(delta),
143        );
144
145        state.key_abbreviations.clear();
146        if let Some(abbreviation) = comparison::try_shorten(text) {
147            state.key_abbreviations.push(abbreviation.into());
148        }
149
150        state.display_two_rows = self.settings.display_two_rows;
151        state.updates_frequently = use_live_delta;
152    }
153
154    /// Calculates the component's state based on the timer and the layout
155    /// settings provided.
156    pub fn state(
157        &self,
158        timer: &Snapshot<'_>,
159        layout_settings: &GeneralLayoutSettings,
160    ) -> key_value::State {
161        let mut state = Default::default();
162        self.update_state(&mut state, timer, layout_settings);
163        state
164    }
165
166    /// Accesses a generic description of the settings available for this
167    /// component and their current values.
168    pub fn settings_description(&self) -> SettingsDescription {
169        SettingsDescription::with_fields(vec![
170            Field::new("Background".into(), self.settings.background.into()),
171            Field::new(
172                "Comparison".into(),
173                self.settings.comparison_override.clone().into(),
174            ),
175            Field::new(
176                "Display 2 Rows".into(),
177                self.settings.display_two_rows.into(),
178            ),
179            Field::new("Label Color".into(), self.settings.label_color.into()),
180            Field::new("Drop Decimals".into(), self.settings.drop_decimals.into()),
181            Field::new("Accuracy".into(), self.settings.accuracy.into()),
182        ])
183    }
184
185    /// Sets a setting's value by its index to the given value.
186    ///
187    /// # Panics
188    ///
189    /// This panics if the type of the value to be set is not compatible with
190    /// the type of the setting's value. A panic can also occur if the index of
191    /// the setting provided is out of bounds.
192    pub fn set_value(&mut self, index: usize, value: Value) {
193        match index {
194            0 => self.settings.background = value.into(),
195            1 => self.settings.comparison_override = value.into(),
196            2 => self.settings.display_two_rows = value.into(),
197            3 => self.settings.label_color = value.into(),
198            4 => self.settings.drop_decimals = value.into(),
199            5 => self.settings.accuracy = value.into(),
200            _ => panic!("Unsupported Setting Index"),
201        }
202    }
203}