livesplit_core/component/segment_time/
mod.rs

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