use crate::{
analysis::split_color,
platform::prelude::*,
settings::{Color, Field, Gradient, SemanticColor, SettingsDescription, Value},
timing::{
formatter::{timer as formatter, Accuracy, DigitsFormat, TimeFormatter},
Snapshot,
},
GeneralLayoutSettings, TimeSpan, TimerPhase, TimingMethod,
};
use core::fmt::Write;
use serde::{Deserialize, Serialize};
#[derive(Default, Clone)]
pub struct Component {
settings: Settings,
}
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
#[serde(from = "serialize::DeltaGradient", into = "serialize::DeltaGradient")]
pub enum DeltaGradient {
Gradient(Gradient),
DeltaPlain,
DeltaVertical,
DeltaHorizontal,
}
impl From<Gradient> for DeltaGradient {
fn from(g: Gradient) -> Self {
Self::Gradient(g)
}
}
impl Default for DeltaGradient {
fn default() -> Self {
Self::Gradient(Gradient::default())
}
}
impl DeltaGradient {
pub fn gradient(&self, delta: Color) -> Gradient {
let [h, s, v, a] = delta.to_hsva();
match self {
DeltaGradient::Gradient(g) => *g,
DeltaGradient::DeltaVertical => {
let color_a = Color::hsva(h, s * 0.5, v * 0.25, a * (1.0 / 6.0));
let color_b = Color::hsva(h, s * 0.5, v * 0.25, a);
Gradient::Vertical(color_a, color_b)
}
DeltaGradient::DeltaPlain => {
Gradient::Plain(Color::hsva(h, s * 0.5, v * 0.25, a * (7.0 / 12.0)))
}
DeltaGradient::DeltaHorizontal => {
let color_a = Color::hsva(h, s * 0.5, v * 0.25, a * (1.0 / 6.0));
let color_b = Color::hsva(h, s * 0.5, v * 0.25, a);
Gradient::Horizontal(color_a, color_b)
}
}
}
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Settings {
pub background: DeltaGradient,
pub timing_method: Option<TimingMethod>,
pub height: u32,
pub color_override: Option<Color>,
pub show_gradient: bool,
pub digits_format: DigitsFormat,
pub accuracy: Accuracy,
pub is_segment_timer: bool,
}
impl Default for Settings {
fn default() -> Self {
Self {
background: DeltaGradient::default(),
timing_method: None,
height: 60,
color_override: None,
show_gradient: true,
digits_format: DigitsFormat::SingleDigitSeconds,
accuracy: Accuracy::Hundredths,
is_segment_timer: false,
}
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct State {
pub background: Gradient,
pub time: String,
pub fraction: String,
pub semantic_color: SemanticColor,
pub top_color: Color,
pub bottom_color: Color,
pub height: u32,
pub updates_frequently: bool,
}
#[cfg(feature = "std")]
impl State {
pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
where
W: std::io::Write,
{
serde_json::to_writer(writer, self)
}
}
impl Component {
pub fn new() -> Self {
Default::default()
}
pub const fn with_settings(settings: Settings) -> Self {
Self { settings }
}
pub const fn settings(&self) -> &Settings {
&self.settings
}
pub fn settings_mut(&mut self) -> &mut Settings {
&mut self.settings
}
pub const fn name(&self) -> &'static str {
if self.settings.is_segment_timer {
"Segment Timer"
} else {
"Timer"
}
}
pub fn update_state(
&self,
state: &mut State,
timer: &Snapshot<'_>,
layout_settings: &GeneralLayoutSettings,
) {
let method = self
.settings
.timing_method
.unwrap_or_else(|| timer.current_timing_method());
let phase = timer.current_phase();
let (time, semantic_color) = if self.settings.is_segment_timer {
let last_split_index = if phase == TimerPhase::Ended {
timer.run().len() - 1
} else {
timer.current_split_index().unwrap_or_default()
};
let mut segment_time = calculate_live_segment_time(timer, method, last_split_index);
if segment_time.is_none() && method == TimingMethod::GameTime {
segment_time =
calculate_live_segment_time(timer, TimingMethod::RealTime, last_split_index);
}
(segment_time, SemanticColor::Default)
} else {
let time = timer.current_time();
let time = time[method].or(time.real_time).unwrap_or_default();
let current_comparison = timer.current_comparison();
let semantic_color = match phase {
TimerPhase::Running if time >= TimeSpan::zero() => {
let pb_split_time = timer
.current_split()
.unwrap()
.comparison(current_comparison)[method];
if let Some(pb_split_time) = pb_split_time {
split_color(
timer,
Some(time - pb_split_time),
timer.current_split_index().unwrap(),
true,
false,
current_comparison,
method,
)
.or(SemanticColor::AheadGainingTime)
} else {
SemanticColor::AheadGainingTime
}
}
TimerPhase::Paused => SemanticColor::Paused,
TimerPhase::Ended => {
let pb_time = timer
.run()
.segments()
.last()
.unwrap()
.comparison(current_comparison)[method];
if pb_time.map_or(true, |t| time < t) {
SemanticColor::PersonalBest
} else {
SemanticColor::BehindLosingTime
}
}
_ => SemanticColor::NotRunning,
};
(Some(time), semantic_color)
};
let not_overwritten_visual_color = semantic_color.visualize(layout_settings);
let visual_color = if let Some(color) = self.settings.color_override {
color
} else {
not_overwritten_visual_color
};
(state.top_color, state.bottom_color) = if self.settings.show_gradient {
top_and_bottom_color(visual_color)
} else {
(visual_color, visual_color)
};
state.background = self
.settings
.background
.gradient(not_overwritten_visual_color);
state.time.clear();
let _ = write!(
state.time,
"{}",
formatter::Time::with_digits_format(self.settings.digits_format).format(time),
);
state.fraction.clear();
let _ = write!(
state.fraction,
"{}",
formatter::Fraction::with_accuracy(self.settings.accuracy).format(time),
);
state.updates_frequently = phase.is_running() && time.is_some();
state.semantic_color = semantic_color;
state.height = self.settings.height;
}
pub fn state(&self, timer: &Snapshot<'_>, layout_settings: &GeneralLayoutSettings) -> State {
let mut state = Default::default();
self.update_state(&mut state, timer, layout_settings);
state
}
pub fn settings_description(&self) -> SettingsDescription {
SettingsDescription::with_fields(vec![
Field::new("Background".into(), self.settings.background.into()),
Field::new(
"Segment Timer".into(),
self.settings.is_segment_timer.into(),
),
Field::new("Timing Method".into(), self.settings.timing_method.into()),
Field::new("Height".into(), u64::from(self.settings.height).into()),
Field::new("Text Color".into(), self.settings.color_override.into()),
Field::new("Show Gradient".into(), self.settings.show_gradient.into()),
Field::new("Digits Format".into(), self.settings.digits_format.into()),
Field::new("Accuracy".into(), self.settings.accuracy.into()),
])
}
pub fn set_value(&mut self, index: usize, value: Value) {
match index {
0 => self.settings.background = value.into(),
1 => self.settings.is_segment_timer = value.into(),
2 => self.settings.timing_method = value.into(),
3 => self.settings.height = value.into_uint().unwrap() as _,
4 => self.settings.color_override = value.into(),
5 => self.settings.show_gradient = value.into(),
6 => self.settings.digits_format = value.into(),
7 => self.settings.accuracy = value.into(),
_ => panic!("Unsupported Setting Index"),
}
}
}
pub fn top_and_bottom_color(color: Color) -> (Color, Color) {
let [h, s, v, a] = color.to_hsva();
let top_color = Color::hsva(h, 0.5 * s, (1.5 * v + 0.1).min(1.0), a);
let bottom_color = Color::hsva(h, s, 0.8 * v, a);
(top_color, bottom_color)
}
fn calculate_live_segment_time(
timer: &Snapshot<'_>,
timing_method: TimingMethod,
last_split_index: usize,
) -> Option<TimeSpan> {
let last_split = if last_split_index > 0 {
timer.run().segment(last_split_index - 1).split_time()[timing_method]
} else {
Some(TimeSpan::zero())
};
if timer.current_phase() == TimerPhase::NotRunning {
Some(timer.run().offset())
} else {
Some(timer.current_time()[timing_method]? - last_split?)
}
}
mod serialize {
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum DeltaGradient {
Gradient(super::Gradient),
Delta(Delta),
}
#[derive(serde::Serialize, serde::Deserialize)]
#[allow(clippy::enum_variant_names)]
pub enum Delta {
DeltaPlain,
DeltaVertical,
DeltaHorizontal,
}
impl From<DeltaGradient> for super::DeltaGradient {
fn from(v: DeltaGradient) -> Self {
match v {
DeltaGradient::Gradient(g) => super::DeltaGradient::Gradient(g),
DeltaGradient::Delta(d) => match d {
Delta::DeltaPlain => super::DeltaGradient::DeltaPlain,
Delta::DeltaVertical => super::DeltaGradient::DeltaVertical,
Delta::DeltaHorizontal => super::DeltaGradient::DeltaHorizontal,
},
}
}
}
impl From<super::DeltaGradient> for DeltaGradient {
fn from(v: super::DeltaGradient) -> Self {
match v {
super::DeltaGradient::Gradient(g) => DeltaGradient::Gradient(g),
super::DeltaGradient::DeltaPlain => DeltaGradient::Delta(Delta::DeltaPlain),
super::DeltaGradient::DeltaVertical => DeltaGradient::Delta(Delta::DeltaVertical),
super::DeltaGradient::DeltaHorizontal => {
DeltaGradient::Delta(Delta::DeltaHorizontal)
}
}
}
}
}