1use crate::{
7 analysis::split_color,
8 platform::prelude::*,
9 settings::{Color, Field, Gradient, SemanticColor, SettingsDescription, Value},
10 timing::{
11 formatter::{timer as formatter, Accuracy, DigitsFormat, TimeFormatter},
12 Snapshot,
13 },
14 GeneralLayoutSettings, TimeSpan, TimerPhase, TimingMethod,
15};
16use core::fmt::Write;
17use serde::{Deserialize, Serialize};
18
19#[derive(Default, Clone)]
23pub struct Component {
24 settings: Settings,
25}
26
27#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
29#[serde(from = "serialize::DeltaGradient", into = "serialize::DeltaGradient")]
30pub enum DeltaGradient {
31 Gradient(Gradient),
33 DeltaPlain,
35 DeltaVertical,
37 DeltaHorizontal,
39}
40
41impl From<Gradient> for DeltaGradient {
42 fn from(g: Gradient) -> Self {
43 Self::Gradient(g)
44 }
45}
46
47impl Default for DeltaGradient {
48 fn default() -> Self {
49 Self::Gradient(Gradient::default())
50 }
51}
52
53impl DeltaGradient {
54 pub fn gradient(&self, delta: Color) -> Gradient {
59 let [h, s, v, a] = delta.to_hsva();
60
61 match self {
62 DeltaGradient::Gradient(g) => *g,
63 DeltaGradient::DeltaVertical => {
64 let color_a = Color::hsva(h, s * 0.5, v * 0.25, a * (1.0 / 6.0));
65 let color_b = Color::hsva(h, s * 0.5, v * 0.25, a);
66
67 Gradient::Vertical(color_a, color_b)
68 }
69 DeltaGradient::DeltaPlain => {
70 Gradient::Plain(Color::hsva(h, s * 0.5, v * 0.25, a * (7.0 / 12.0)))
71 }
72 DeltaGradient::DeltaHorizontal => {
73 let color_a = Color::hsva(h, s * 0.5, v * 0.25, a * (1.0 / 6.0));
74 let color_b = Color::hsva(h, s * 0.5, v * 0.25, a);
75
76 Gradient::Horizontal(color_a, color_b)
77 }
78 }
79 }
80}
81#[derive(Clone, Serialize, Deserialize)]
83#[serde(default)]
84pub struct Settings {
85 pub background: DeltaGradient,
87 pub timing_method: Option<TimingMethod>,
91 pub height: u32,
93 pub color_override: Option<Color>,
97 pub show_gradient: bool,
101 pub digits_format: DigitsFormat,
104 pub accuracy: Accuracy,
106 pub is_segment_timer: bool,
110}
111
112impl Default for Settings {
113 fn default() -> Self {
114 Self {
115 background: DeltaGradient::default(),
116 timing_method: None,
117 height: 60,
118 color_override: None,
119 show_gradient: true,
120 digits_format: DigitsFormat::SingleDigitSeconds,
121 accuracy: Accuracy::Hundredths,
122 is_segment_timer: false,
123 }
124 }
125}
126
127#[derive(Default, Serialize, Deserialize)]
129pub struct State {
130 pub background: Gradient,
132 pub time: String,
134 pub fraction: String,
136 pub semantic_color: SemanticColor,
138 pub top_color: Color,
140 pub bottom_color: Color,
142 pub height: u32,
144 pub updates_frequently: bool,
147}
148
149#[cfg(feature = "std")]
150impl State {
151 pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
153 where
154 W: std::io::Write,
155 {
156 serde_json::to_writer(writer, self)
157 }
158}
159
160impl Component {
161 pub fn new() -> Self {
163 Default::default()
164 }
165
166 pub const fn with_settings(settings: Settings) -> Self {
168 Self { settings }
169 }
170
171 pub const fn settings(&self) -> &Settings {
173 &self.settings
174 }
175
176 pub fn settings_mut(&mut self) -> &mut Settings {
178 &mut self.settings
179 }
180
181 pub const fn name(&self) -> &'static str {
183 if self.settings.is_segment_timer {
184 "Segment Timer"
185 } else {
186 "Timer"
187 }
188 }
189
190 pub fn update_state(
193 &self,
194 state: &mut State,
195 timer: &Snapshot<'_>,
196 layout_settings: &GeneralLayoutSettings,
197 ) {
198 let method = self
199 .settings
200 .timing_method
201 .unwrap_or_else(|| timer.current_timing_method());
202
203 let phase = timer.current_phase();
204
205 let (time, semantic_color) = if self.settings.is_segment_timer {
206 let last_split_index = if phase == TimerPhase::Ended {
207 timer.run().len() - 1
208 } else {
209 timer.current_split_index().unwrap_or_default()
210 };
211 let mut segment_time = calculate_live_segment_time(timer, method, last_split_index);
212
213 if segment_time.is_none() && method == TimingMethod::GameTime {
214 segment_time =
215 calculate_live_segment_time(timer, TimingMethod::RealTime, last_split_index);
216 }
217
218 (segment_time, SemanticColor::Default)
219 } else {
220 let time = timer.current_time();
221 let time = time[method].or(time.real_time).unwrap_or_default();
222 let current_comparison = timer.current_comparison();
223
224 let semantic_color = match phase {
225 TimerPhase::Running if time >= TimeSpan::zero() => {
226 let pb_split_time = timer
227 .current_split()
228 .unwrap()
229 .comparison(current_comparison)[method];
230
231 if let Some(pb_split_time) = pb_split_time {
232 split_color(
233 timer,
234 Some(time - pb_split_time),
235 timer.current_split_index().unwrap(),
236 true,
237 false,
238 current_comparison,
239 method,
240 )
241 .or(SemanticColor::AheadGainingTime)
242 } else {
243 SemanticColor::AheadGainingTime
244 }
245 }
246 TimerPhase::Paused => SemanticColor::Paused,
247 TimerPhase::Ended => {
248 let pb_time = timer
249 .run()
250 .segments()
251 .last()
252 .unwrap()
253 .comparison(current_comparison)[method];
254
255 if pb_time.map_or(true, |t| time < t) {
256 SemanticColor::PersonalBest
257 } else {
258 SemanticColor::BehindLosingTime
259 }
260 }
261 _ => SemanticColor::NotRunning,
262 };
263
264 (Some(time), semantic_color)
265 };
266
267 let not_overwritten_visual_color = semantic_color.visualize(layout_settings);
268 let visual_color = if let Some(color) = self.settings.color_override {
269 color
270 } else {
271 not_overwritten_visual_color
272 };
273
274 (state.top_color, state.bottom_color) = if self.settings.show_gradient {
275 top_and_bottom_color(visual_color)
276 } else {
277 (visual_color, visual_color)
278 };
279
280 state.background = self
281 .settings
282 .background
283 .gradient(not_overwritten_visual_color);
284
285 state.time.clear();
286 let _ = write!(
287 state.time,
288 "{}",
289 formatter::Time::with_digits_format(self.settings.digits_format).format(time),
290 );
291
292 state.fraction.clear();
293 let _ = write!(
294 state.fraction,
295 "{}",
296 formatter::Fraction::with_accuracy(self.settings.accuracy).format(time),
297 );
298
299 state.updates_frequently = phase.is_running() && time.is_some();
300 state.semantic_color = semantic_color;
301 state.height = self.settings.height;
302 }
303
304 pub fn state(&self, timer: &Snapshot<'_>, layout_settings: &GeneralLayoutSettings) -> State {
307 let mut state = Default::default();
308 self.update_state(&mut state, timer, layout_settings);
309 state
310 }
311
312 pub fn settings_description(&self) -> SettingsDescription {
315 SettingsDescription::with_fields(vec![
316 Field::new("Background".into(), self.settings.background.into()),
317 Field::new(
318 "Segment Timer".into(),
319 self.settings.is_segment_timer.into(),
320 ),
321 Field::new("Timing Method".into(), self.settings.timing_method.into()),
322 Field::new("Height".into(), u64::from(self.settings.height).into()),
323 Field::new("Text Color".into(), self.settings.color_override.into()),
324 Field::new("Show Gradient".into(), self.settings.show_gradient.into()),
325 Field::new("Digits Format".into(), self.settings.digits_format.into()),
326 Field::new("Accuracy".into(), self.settings.accuracy.into()),
327 ])
328 }
329
330 pub fn set_value(&mut self, index: usize, value: Value) {
338 match index {
339 0 => self.settings.background = value.into(),
340 1 => self.settings.is_segment_timer = value.into(),
341 2 => self.settings.timing_method = value.into(),
342 3 => self.settings.height = value.into_uint().unwrap() as _,
343 4 => self.settings.color_override = value.into(),
344 5 => self.settings.show_gradient = value.into(),
345 6 => self.settings.digits_format = value.into(),
346 7 => self.settings.accuracy = value.into(),
347 _ => panic!("Unsupported Setting Index"),
348 }
349 }
350}
351
352pub fn top_and_bottom_color(color: Color) -> (Color, Color) {
355 let [h, s, v, a] = color.to_hsva();
356
357 let top_color = Color::hsva(h, 0.5 * s, (1.5 * v + 0.1).min(1.0), a);
358 let bottom_color = Color::hsva(h, s, 0.8 * v, a);
359
360 (top_color, bottom_color)
361}
362
363fn calculate_live_segment_time(
364 timer: &Snapshot<'_>,
365 timing_method: TimingMethod,
366 last_split_index: usize,
367) -> Option<TimeSpan> {
368 let last_split = if last_split_index > 0 {
369 timer.run().segment(last_split_index - 1).split_time()[timing_method]
370 } else {
371 Some(TimeSpan::zero())
372 };
373
374 if timer.current_phase() == TimerPhase::NotRunning {
375 Some(timer.run().offset())
376 } else {
377 Some(timer.current_time()[timing_method]? - last_split?)
378 }
379}
380
381mod serialize {
383 #[derive(serde::Serialize, serde::Deserialize)]
384 #[serde(untagged)]
385 pub enum DeltaGradient {
386 Gradient(super::Gradient),
387 Delta(Delta),
388 }
389
390 #[derive(serde::Serialize, serde::Deserialize)]
391 #[allow(clippy::enum_variant_names)]
392 pub enum Delta {
393 DeltaPlain,
394 DeltaVertical,
395 DeltaHorizontal,
396 }
397
398 impl From<DeltaGradient> for super::DeltaGradient {
399 fn from(v: DeltaGradient) -> Self {
400 match v {
401 DeltaGradient::Gradient(g) => super::DeltaGradient::Gradient(g),
402 DeltaGradient::Delta(d) => match d {
403 Delta::DeltaPlain => super::DeltaGradient::DeltaPlain,
404 Delta::DeltaVertical => super::DeltaGradient::DeltaVertical,
405 Delta::DeltaHorizontal => super::DeltaGradient::DeltaHorizontal,
406 },
407 }
408 }
409 }
410
411 impl From<super::DeltaGradient> for DeltaGradient {
412 fn from(v: super::DeltaGradient) -> Self {
413 match v {
414 super::DeltaGradient::Gradient(g) => DeltaGradient::Gradient(g),
415 super::DeltaGradient::DeltaPlain => DeltaGradient::Delta(Delta::DeltaPlain),
416 super::DeltaGradient::DeltaVertical => DeltaGradient::Delta(Delta::DeltaVertical),
417 super::DeltaGradient::DeltaHorizontal => {
418 DeltaGradient::Delta(Delta::DeltaHorizontal)
419 }
420 }
421 }
422 }
423}