livesplit_core/component/detailed_timer/
mod.rs1use super::timer;
8use crate::{
9 analysis::comparison_single_segment_time,
10 comparison::{self, best_segments, none},
11 platform::prelude::*,
12 settings::{CachedImageId, Color, Field, Gradient, ImageData, SettingsDescription, Value},
13 timing::{
14 formatter::{Accuracy, DigitsFormat, SegmentTime, TimeFormatter},
15 Snapshot,
16 },
17 GeneralLayoutSettings, Segment, TimeSpan, TimerPhase,
18};
19use core::fmt::Write;
20use serde::{Deserialize, Serialize};
21
22#[cfg(test)]
23mod tests;
24
25#[derive(Default, Clone)]
30pub struct Component {
31 icon_id: CachedImageId,
32 timer: timer::Component,
33 segment_timer: timer::Component,
34 settings: Settings,
35}
36
37#[derive(Clone, Serialize, Deserialize)]
39#[serde(default)]
40pub struct Settings {
41 pub background: timer::DeltaGradient,
43 pub comparison1: Option<String>,
46 pub comparison2: Option<String>,
50 pub hide_second_comparison: bool,
52 pub timer: timer::Settings,
54 pub segment_timer: timer::Settings,
56 pub display_icon: bool,
58 pub show_segment_name: bool,
60}
61
62#[derive(Default, Serialize, Deserialize)]
64pub struct State {
65 pub background: Gradient,
67 pub timer: timer::State,
69 pub segment_timer: timer::State,
71 pub comparison1: Option<ComparisonState>,
73 pub comparison2: Option<ComparisonState>,
75 pub segment_name: Option<String>,
78 pub icon_change: Option<ImageData>,
83}
84
85#[derive(Serialize, Deserialize)]
87pub struct ComparisonState {
88 pub name: String,
90 pub time: String,
92}
93
94fn update_comparison(
95 state: &mut Option<ComparisonState>,
96 new_state: Option<(&str, Option<TimeSpan>)>,
97) {
98 if let Some((name, time)) = new_state {
99 let state = state.get_or_insert_with(|| ComparisonState {
100 name: String::new(),
101 time: String::new(),
102 });
103
104 state.name.clear();
105 state.name.push_str(name);
106
107 state.time.clear();
108 let _ = write!(state.time, "{}", SegmentTime::new().format(time));
109 } else {
110 *state = None;
111 }
112}
113
114impl Default for Settings {
115 fn default() -> Self {
116 Settings {
117 background: timer::DeltaGradient::from(Gradient::Transparent),
118 comparison1: None,
119 comparison2: Some(String::from(best_segments::NAME)),
120 hide_second_comparison: false,
121 timer: timer::Settings {
122 height: 40,
123 ..Default::default()
124 },
125 segment_timer: timer::Settings {
126 height: 25,
127 is_segment_timer: true,
128 color_override: Some(Color::rgba(
129 170.0 / 255.0,
130 170.0 / 255.0,
131 170.0 / 255.0,
132 1.0,
133 )),
134 ..Default::default()
135 },
136 display_icon: false,
137 show_segment_name: false,
138 }
139 }
140}
141
142#[cfg(feature = "std")]
143impl State {
144 pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
146 where
147 W: std::io::Write,
148 {
149 serde_json::to_writer(writer, self)
150 }
151}
152
153impl Component {
154 pub fn new() -> Self {
156 Self::with_settings(Default::default())
157 }
158
159 pub fn with_settings(settings: Settings) -> Self {
161 let timer = timer::Component::with_settings(settings.timer.clone());
162 let segment_timer = timer::Component::with_settings(settings.segment_timer.clone());
163 Self {
164 timer,
165 segment_timer,
166 settings,
167 ..Default::default()
168 }
169 }
170
171 pub const fn settings(&self) -> &Settings {
173 &self.settings
174 }
175
176 pub fn set_settings(&mut self, settings: Settings) {
178 self.settings = settings;
179 *self.timer.settings_mut() = self.settings.timer.clone();
180 *self.segment_timer.settings_mut() = self.settings.segment_timer.clone();
181 }
182
183 pub const fn name(&self) -> &'static str {
185 "Detailed Timer"
186 }
187
188 pub fn update_state(
191 &mut self,
192 state: &mut State,
193 timer: &Snapshot<'_>,
194 layout_settings: &GeneralLayoutSettings,
195 ) {
196 let current_phase = timer.current_phase();
197 let timing_method = self
198 .settings
199 .timer
200 .timing_method
201 .unwrap_or_else(|| timer.current_timing_method());
202
203 let run = timer.run();
204
205 let (current_split, last_split_index) = if current_phase == TimerPhase::Ended {
206 (run.segments().last(), run.len() - 1)
207 } else {
208 (
209 timer.current_split(),
210 timer.current_split_index().unwrap_or(0),
211 )
212 };
213
214 let (comparison1, comparison2) = if current_phase != TimerPhase::NotRunning {
215 let mut comparison1 = self
216 .settings
217 .comparison1
218 .as_deref()
219 .unwrap_or_else(|| timer.current_comparison());
220
221 let comparison2 = self
222 .settings
223 .comparison2
224 .as_deref()
225 .unwrap_or_else(|| timer.current_comparison());
226
227 let mut hide_comparison = self.settings.hide_second_comparison;
228
229 if hide_comparison
230 || !run.comparisons().any(|c| c == comparison2)
231 || comparison2 == none::NAME
232 {
233 hide_comparison = true;
234 if !run.comparisons().any(|c| c == comparison1) || comparison1 == none::NAME {
235 comparison1 = timer.current_comparison();
236 }
237 } else if !run.comparisons().any(|c| c == comparison1) || comparison1 == none::NAME {
238 hide_comparison = true;
239 comparison1 = comparison2;
240 } else if comparison1 == comparison2 {
241 hide_comparison = true;
242 }
243
244 let comparison1 = Some((
245 comparison::shorten(comparison1),
246 comparison_single_segment_time(run, last_split_index, comparison1, timing_method),
247 ));
248
249 let comparison2 = if !hide_comparison {
250 Some((
251 comparison::shorten(comparison2),
252 comparison_single_segment_time(
253 run,
254 last_split_index,
255 comparison2,
256 timing_method,
257 ),
258 ))
259 } else {
260 None
261 };
262
263 (comparison1, comparison2)
264 } else {
265 Default::default()
266 };
267
268 let display_icon = self.settings.display_icon;
269 let icon_change = self
270 .icon_id
271 .update_with(current_split.filter(|_| display_icon).map(Segment::icon))
272 .map(Into::into);
273
274 self.timer
275 .update_state(&mut state.timer, timer, layout_settings);
276
277 self.segment_timer
278 .update_state(&mut state.segment_timer, timer, layout_settings);
279
280 state.background = self
281 .settings
282 .background
283 .gradient(state.timer.semantic_color.visualize(layout_settings));
284
285 update_comparison(&mut state.comparison1, comparison1);
286 update_comparison(&mut state.comparison2, comparison2);
287
288 match current_split.filter(|_| self.settings.show_segment_name) {
289 Some(segment) => {
290 let segment_name = state.segment_name.get_or_insert_with(String::new);
291 segment_name.clear();
292 segment_name.push_str(segment.name());
293 }
294 None => state.segment_name = None,
295 }
296
297 state.icon_change = icon_change;
298 }
299
300 pub fn state(
303 &mut self,
304 timer: &Snapshot<'_>,
305 layout_settings: &GeneralLayoutSettings,
306 ) -> State {
307 let mut state = Default::default();
308 self.update_state(&mut state, timer, layout_settings);
309 state
310 }
311
312 pub fn remount(&mut self) {
318 self.icon_id.reset();
319 }
320
321 pub fn settings_description(&self) -> SettingsDescription {
324 SettingsDescription::with_fields(vec![
325 Field::new("Background".into(), self.settings.background.into()),
326 Field::new(
327 "Timing Method".into(),
328 self.settings.timer.timing_method.into(),
329 ),
330 Field::new(
331 "Comparison 1".into(),
332 self.settings.comparison1.clone().into(),
333 ),
334 Field::new(
335 "Comparison 2".into(),
336 self.settings.comparison2.clone().into(),
337 ),
338 Field::new(
339 "Hide Second Comparison".into(),
340 self.settings.hide_second_comparison.into(),
341 ),
342 Field::new(
343 "Timer Height".into(),
344 u64::from(self.settings.timer.height).into(),
345 ),
346 Field::new(
347 "Segment Timer Height".into(),
348 u64::from(self.settings.segment_timer.height).into(),
349 ),
350 Field::new(
351 "Timer Digits Format".into(),
352 self.settings.timer.digits_format.into(),
353 ),
354 Field::new("Timer Accuracy".into(), self.settings.timer.accuracy.into()),
355 Field::new(
356 "Segment Timer Digits Format".into(),
357 self.settings.segment_timer.digits_format.into(),
358 ),
359 Field::new(
360 "Segment Timer Accuracy".into(),
361 self.settings.segment_timer.accuracy.into(),
362 ),
363 Field::new(
364 "Show Segment Name".into(),
365 self.settings.show_segment_name.into(),
366 ),
367 Field::new("Display Icon".into(), self.settings.display_icon.into()),
368 ])
369 }
370
371 pub fn set_value(&mut self, index: usize, value: Value) {
379 match index {
380 0 => self.settings.background = value.into(),
381 1 => {
382 let value = value.into();
383 self.settings.timer.timing_method = value;
384 self.settings.segment_timer.timing_method = value;
385 }
386 2 => self.settings.comparison1 = value.into(),
387 3 => self.settings.comparison2 = value.into(),
388 4 => self.settings.hide_second_comparison = value.into(),
389 5 => {
390 let value = value.into_uint().unwrap() as _;
391 self.settings.timer.height = value;
392 self.timer.settings_mut().height = value;
393 }
394 6 => {
395 let value = value.into_uint().unwrap() as _;
396 self.settings.segment_timer.height = value;
397 self.segment_timer.settings_mut().height = value;
398 }
399 7 => {
400 let value: DigitsFormat = value.into();
401 self.settings.timer.digits_format = value;
402 self.timer.settings_mut().digits_format = value;
403 }
404 8 => {
405 let value: Accuracy = value.into();
406 self.settings.timer.accuracy = value;
407 self.timer.settings_mut().accuracy = value;
408 }
409 9 => {
410 let value: DigitsFormat = value.into();
411 self.settings.segment_timer.digits_format = value;
412 self.segment_timer.settings_mut().digits_format = value;
413 }
414 10 => {
415 let value: Accuracy = value.into();
416 self.settings.segment_timer.accuracy = value;
417 self.segment_timer.settings_mut().accuracy = value;
418 }
419 11 => self.settings.show_segment_name = value.into(),
420 12 => self.settings.display_icon = value.into(),
421 _ => panic!("Unsupported Setting Index"),
422 }
423 }
424}