livesplit_core/component/
possible_time_save.rs

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