livesplit_core/component/
previous_segment.rs

1//! Provides the Previous Segment Component and relevant types for using it. The
2//! Previous Segment Component is a component that shows how much time was saved
3//! or lost during the previous [`Segment`](crate::run::Segment) based on the
4//! chosen comparison. Additionally, the potential time save for the previous
5//! [`Segment`](crate::run::Segment) can be displayed. This component switches
6//! to a `Live Segment` view that shows active time loss whenever the runner is
7//! losing time on the current [`Segment`](crate::run::Segment).
8
9use super::key_value;
10use crate::{
11    analysis, comparison,
12    platform::prelude::*,
13    settings::{Color, Field, Gradient, SemanticColor, SettingsDescription, Value},
14    timing::{
15        formatter::{Accuracy, Delta, SegmentTime, TimeFormatter},
16        Snapshot,
17    },
18    GeneralLayoutSettings, TimerPhase,
19};
20use alloc::borrow::Cow;
21use core::fmt::Write as FmtWrite;
22use serde::{Deserialize, Serialize};
23
24/// The Previous Segment Component is a component that shows how much time was
25/// saved or lost during the previous [`Segment`](crate::run::Segment) based on
26/// the chosen comparison. Additionally, the potential time save for the previous
27/// [`Segment`](crate::run::Segment) can be displayed. This component switches
28/// to a `Live Segment` view that shows active time loss whenever the runner is
29/// losing time on the current [`Segment`](crate::run::Segment).
30#[derive(Default, Clone)]
31pub struct Component {
32    settings: Settings,
33}
34
35/// The Settings for this component.
36#[derive(Clone, Serialize, Deserialize)]
37#[serde(default)]
38pub struct Settings {
39    /// The background shown behind the component.
40    pub background: Gradient,
41    /// The comparison chosen. Uses the Timer's current comparison if set to
42    /// `None`.
43    pub comparison_override: Option<String>,
44    /// Specifies whether to display the name of the component and its value in
45    /// two separate rows.
46    pub display_two_rows: bool,
47    /// The color of the label. If `None` is specified, the color is taken from
48    /// the layout.
49    pub label_color: Option<Color>,
50    /// Specifies if the decimals should not be shown anymore when the
51    /// visualized delta is above one minute.
52    pub drop_decimals: bool,
53    /// The accuracy of the time shown.
54    pub accuracy: Accuracy,
55    /// Determines if the time save that could've been saved is shown in
56    /// addition to the previous segment.
57    pub show_possible_time_save: bool,
58}
59
60impl Default for Settings {
61    fn default() -> Self {
62        Self {
63            background: key_value::DEFAULT_GRADIENT,
64            comparison_override: None,
65            display_two_rows: false,
66            label_color: None,
67            drop_decimals: true,
68            accuracy: Accuracy::Tenths,
69            show_possible_time_save: false,
70        }
71    }
72}
73
74impl Component {
75    /// Creates a new Previous Segment Component.
76    pub fn new() -> Self {
77        Default::default()
78    }
79
80    /// Creates a new Previous Segment Component with the given settings.
81    pub const fn with_settings(settings: Settings) -> Self {
82        Self { settings }
83    }
84
85    /// Accesses the settings of the component.
86    pub const fn settings(&self) -> &Settings {
87        &self.settings
88    }
89
90    /// Grants mutable access to the settings of the component.
91    pub fn settings_mut(&mut self) -> &mut Settings {
92        &mut self.settings
93    }
94
95    /// Accesses the name of the component.
96    pub fn name(&self) -> Cow<'static, str> {
97        self.text(
98            false,
99            self.settings
100                .comparison_override
101                .as_ref()
102                .map(String::as_ref),
103        )
104    }
105
106    fn text(&self, live: bool, comparison: Option<&str>) -> Cow<'static, str> {
107        let text = if live {
108            "Live Segment"
109        } else {
110            "Previous Segment"
111        };
112        let mut text = Cow::from(text);
113        if let Some(comparison) = comparison {
114            write!(text.to_mut(), " ({})", comparison::shorten(comparison)).unwrap();
115        }
116        text
117    }
118
119    /// Updates the component's state based on the timer and layout settings
120    /// provided.
121    pub fn update_state(
122        &self,
123        state: &mut key_value::State,
124        timer: &Snapshot<'_>,
125        layout_settings: &GeneralLayoutSettings,
126    ) {
127        let mut time_change = None;
128        let mut previous_possible = None;
129        let resolved_comparison = comparison::resolve(&self.settings.comparison_override, timer);
130        let comparison = comparison::or_current(resolved_comparison, timer);
131        let live_segment =
132            analysis::check_live_delta(timer, false, comparison, timer.current_timing_method());
133
134        let phase = timer.current_phase();
135        let method = timer.current_timing_method();
136        let semantic_color = if phase != TimerPhase::NotRunning {
137            let split_index = timer.current_split_index().unwrap();
138            if live_segment.is_some() {
139                time_change = analysis::live_segment_delta(timer, split_index, comparison, method);
140                if self.settings.show_possible_time_save {
141                    previous_possible = analysis::possible_time_save::calculate(
142                        timer,
143                        split_index,
144                        comparison,
145                        false,
146                    )
147                    .0;
148                }
149            } else if let Some(prev_split_index) = split_index.checked_sub(1) {
150                time_change =
151                    analysis::previous_segment_delta(timer, prev_split_index, comparison, method);
152                if self.settings.show_possible_time_save {
153                    previous_possible = analysis::possible_time_save::calculate(
154                        timer,
155                        prev_split_index,
156                        comparison,
157                        false,
158                    )
159                    .0;
160                }
161            };
162
163            if let Some(time_change) = time_change {
164                if live_segment.is_some() {
165                    analysis::split_color(
166                        timer,
167                        time_change.into(),
168                        split_index,
169                        false,
170                        false,
171                        comparison,
172                        method,
173                    )
174                } else if let Some(prev_split_index) = split_index.checked_sub(1) {
175                    analysis::split_color(
176                        timer,
177                        time_change.into(),
178                        prev_split_index,
179                        false,
180                        true,
181                        comparison,
182                        method,
183                    )
184                } else {
185                    SemanticColor::Default
186                }
187            } else if let Some(prev_split_index) = split_index.checked_sub(1) {
188                analysis::split_color(
189                    timer,
190                    None,
191                    prev_split_index,
192                    true,
193                    true,
194                    comparison,
195                    method,
196                )
197            } else {
198                SemanticColor::Default
199            }
200        } else {
201            SemanticColor::Default
202        };
203
204        let value_color = Some(semantic_color.visualize(layout_settings));
205
206        let text = self.text(live_segment.is_some(), resolved_comparison);
207
208        state.background = self.settings.background;
209        state.key_color = self.settings.label_color;
210        state.value_color = value_color;
211        state.semantic_color = semantic_color;
212
213        state.key.clear();
214        state.key.push_str(&text); // FIXME: Uncow
215
216        state.value.clear();
217        let _ = write!(
218            state.value,
219            "{}",
220            Delta::custom(self.settings.drop_decimals, self.settings.accuracy).format(time_change),
221        );
222
223        if self.settings.show_possible_time_save {
224            let _ = write!(
225                state.value,
226                " / {}",
227                SegmentTime::with_accuracy(self.settings.accuracy).format(previous_possible),
228            );
229        }
230
231        state.key_abbreviations.clear();
232        if live_segment.is_some() {
233            state.key_abbreviations.push("Live Segment".into());
234            state.key_abbreviations.push("Live Seg.".into());
235        } else {
236            state.key_abbreviations.push("Previous Segment".into());
237            state.key_abbreviations.push("Prev. Segment".into());
238            state.key_abbreviations.push("Prev. Seg.".into());
239        }
240
241        state.display_two_rows = self.settings.display_two_rows;
242        state.updates_frequently = live_segment.is_some() && phase.is_running();
243    }
244
245    /// Calculates the component's state based on the timer and the layout
246    /// settings provided.
247    pub fn state(
248        &self,
249        timer: &Snapshot<'_>,
250        layout_settings: &GeneralLayoutSettings,
251    ) -> key_value::State {
252        let mut state = Default::default();
253        self.update_state(&mut state, timer, layout_settings);
254        state
255    }
256
257    /// Accesses a generic description of the settings available for this
258    /// component and their current values.
259    pub fn settings_description(&self) -> SettingsDescription {
260        SettingsDescription::with_fields(vec![
261            Field::new("Background".into(), self.settings.background.into()),
262            Field::new(
263                "Comparison".into(),
264                self.settings.comparison_override.clone().into(),
265            ),
266            Field::new(
267                "Display 2 Rows".into(),
268                self.settings.display_two_rows.into(),
269            ),
270            Field::new("Label Color".into(), self.settings.label_color.into()),
271            Field::new("Drop Decimals".into(), self.settings.drop_decimals.into()),
272            Field::new("Accuracy".into(), self.settings.accuracy.into()),
273            Field::new(
274                "Show Possible Time Save".into(),
275                self.settings.show_possible_time_save.into(),
276            ),
277        ])
278    }
279
280    /// Sets a setting's value by its index to the given value.
281    ///
282    /// # Panics
283    ///
284    /// This panics if the type of the value to be set is not compatible with
285    /// the type of the setting's value. A panic can also occur if the index of
286    /// the setting provided is out of bounds.
287    pub fn set_value(&mut self, index: usize, value: Value) {
288        match index {
289            0 => self.settings.background = value.into(),
290            1 => self.settings.comparison_override = value.into(),
291            2 => self.settings.display_two_rows = value.into(),
292            3 => self.settings.label_color = value.into(),
293            4 => self.settings.drop_decimals = value.into(),
294            5 => self.settings.accuracy = value.into(),
295            6 => self.settings.show_possible_time_save = value.into(),
296            _ => panic!("Unsupported Setting Index"),
297        }
298    }
299}