use crate::{
analysis::{self, possible_time_save, split_color},
comparison,
component::splits::Settings as SplitsSettings,
platform::prelude::*,
settings::{Color, SemanticColor},
timing::{
formatter::{Delta, Regular, SegmentTime, TimeFormatter},
Snapshot,
},
util::Clear,
GeneralLayoutSettings, Segment, TimeSpan, TimingMethod,
};
use core::fmt::Write;
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ColumnSettings {
pub name: String,
#[serde(flatten)]
pub kind: ColumnKind,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ColumnKind {
Variable(VariableColumn),
Time(TimeColumn),
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct TimeColumn {
pub start_with: ColumnStartWith,
pub update_with: ColumnUpdateWith,
pub update_trigger: ColumnUpdateTrigger,
pub comparison_override: Option<String>,
pub timing_method: Option<TimingMethod>,
}
#[derive(Default, Clone, Serialize, Deserialize)]
pub struct VariableColumn {
pub variable_name: String,
}
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ColumnStartWith {
Empty,
ComparisonTime,
ComparisonSegmentTime,
PossibleTimeSave,
}
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ColumnUpdateWith {
DontUpdate,
SplitTime,
Delta,
DeltaWithFallback,
SegmentTime,
SegmentDelta,
SegmentDeltaWithFallback,
}
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ColumnUpdateTrigger {
OnStartingSegment,
Contextual,
OnEndingSegment,
}
impl Default for ColumnSettings {
fn default() -> Self {
ColumnSettings {
name: String::from("Column"),
kind: ColumnKind::Time(TimeColumn::default()),
}
}
}
impl Default for TimeColumn {
fn default() -> Self {
TimeColumn {
start_with: ColumnStartWith::Empty,
update_with: ColumnUpdateWith::DontUpdate,
update_trigger: ColumnUpdateTrigger::Contextual,
comparison_override: None,
timing_method: None,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ColumnState {
pub value: String,
pub semantic_color: SemanticColor,
pub visual_color: Color,
pub updates_frequently: bool,
}
impl Clear for ColumnState {
fn clear(&mut self) {
self.value.clear();
}
}
enum ColumnFormatter {
Time,
Delta,
SegmentTime,
}
pub fn update_state(
state: &mut ColumnState,
column_settings: &ColumnSettings,
timer: &Snapshot<'_>,
splits_settings: &SplitsSettings,
layout_settings: &GeneralLayoutSettings,
segment: &Segment,
segment_index: usize,
current_split: Option<usize>,
method: TimingMethod,
) {
match &column_settings.kind {
ColumnKind::Variable(column) => {
state.value.clear();
if let Some(value) = segment.variables().get(column.variable_name.as_str()) {
state.value.push_str(value);
} else if Some(segment_index) == current_split {
if let Some(value) = timer
.run()
.metadata()
.custom_variable_value(column.variable_name.as_str())
{
state.value.push_str(value);
}
}
state.semantic_color = SemanticColor::Default;
state.visual_color = layout_settings.text_color;
state.updates_frequently = false;
}
ColumnKind::Time(column) => {
update_time_column(
state,
column,
timer,
splits_settings,
layout_settings,
segment,
segment_index,
current_split,
method,
);
}
}
}
fn update_time_column(
state: &mut ColumnState,
column_settings: &TimeColumn,
timer: &Snapshot<'_>,
splits_settings: &SplitsSettings,
layout_settings: &GeneralLayoutSettings,
segment: &Segment,
segment_index: usize,
current_split: Option<usize>,
method: TimingMethod,
) {
let method = column_settings.timing_method.unwrap_or(method);
let resolved_comparison = comparison::resolve(&column_settings.comparison_override, timer);
let comparison = comparison::or_current(resolved_comparison, timer);
let update_value = time_column_update_value(
column_settings,
timer,
segment,
segment_index,
current_split,
method,
comparison,
);
let updated = update_value.is_some();
let ((column_value, semantic_color, formatter), is_live) = update_value.unwrap_or_else(|| {
(
match column_settings.start_with {
ColumnStartWith::Empty => (None, SemanticColor::Default, ColumnFormatter::Time),
ColumnStartWith::ComparisonTime => (
segment.comparison(comparison)[method],
SemanticColor::Default,
ColumnFormatter::Time,
),
ColumnStartWith::ComparisonSegmentTime => (
analysis::comparison_combined_segment_time(
timer.run(),
segment_index,
comparison,
method,
),
SemanticColor::Default,
ColumnFormatter::SegmentTime,
),
ColumnStartWith::PossibleTimeSave => (
possible_time_save::calculate(timer, segment_index, comparison, false).0,
SemanticColor::Default,
ColumnFormatter::SegmentTime,
),
},
false,
)
});
let is_empty = column_settings.start_with == ColumnStartWith::Empty && !updated;
state.updates_frequently = is_live && column_value.is_some();
state.value.clear();
if !is_empty {
let _ = match formatter {
ColumnFormatter::Time => write!(
state.value,
"{}",
Regular::with_accuracy(splits_settings.split_time_accuracy).format(column_value)
),
ColumnFormatter::Delta => write!(
state.value,
"{}",
Delta::custom(
splits_settings.delta_drop_decimals,
splits_settings.delta_time_accuracy,
)
.format(column_value)
),
ColumnFormatter::SegmentTime => {
write!(
state.value,
"{}",
SegmentTime::with_accuracy(splits_settings.segment_time_accuracy)
.format(column_value)
)
}
};
}
state.semantic_color = semantic_color;
state.visual_color = semantic_color.visualize(layout_settings);
}
fn time_column_update_value(
column: &TimeColumn,
timer: &Snapshot<'_>,
segment: &Segment,
segment_index: usize,
current_split: Option<usize>,
method: TimingMethod,
comparison: &str,
) -> Option<((Option<TimeSpan>, SemanticColor, ColumnFormatter), bool)> {
use self::{ColumnUpdateTrigger::*, ColumnUpdateWith::*};
if current_split < Some(segment_index) {
return None;
}
let is_current_split = current_split == Some(segment_index);
if is_current_split {
if column.update_trigger == OnEndingSegment {
return None;
}
if column.update_trigger == Contextual
&& analysis::check_live_delta(
timer,
!column.update_with.is_segment_based(),
comparison,
method,
)
.is_none()
{
return None;
}
}
let is_live = is_current_split;
let value = match (column.update_with, is_live) {
(DontUpdate, _) => return None,
(SplitTime, false) => (
segment.split_time()[method],
SemanticColor::Default,
ColumnFormatter::Time,
),
(SplitTime, true) => (
timer.current_time()[method],
SemanticColor::Default,
ColumnFormatter::Time,
),
(Delta | DeltaWithFallback, false) => {
let split_time = segment.split_time()[method];
let delta = catch! {
split_time? -
segment.comparison(comparison)[method]?
};
let (value, formatter) = if delta.is_none() && column.update_with.has_fallback() {
(split_time, ColumnFormatter::Time)
} else {
(delta, ColumnFormatter::Delta)
};
(
value,
split_color(timer, delta, segment_index, true, true, comparison, method),
formatter,
)
}
(Delta | DeltaWithFallback, true) => (
catch! {
timer.current_time()[method]? -
segment.comparison(comparison)[method]?
},
SemanticColor::Default,
ColumnFormatter::Delta,
),
(SegmentTime, false) => (
analysis::previous_segment_time(timer, segment_index, method),
SemanticColor::Default,
ColumnFormatter::SegmentTime,
),
(SegmentTime, true) => (
analysis::live_segment_time(timer, segment_index, method),
SemanticColor::Default,
ColumnFormatter::SegmentTime,
),
(SegmentDelta | SegmentDeltaWithFallback, false) => {
let delta = analysis::previous_segment_delta(timer, segment_index, comparison, method);
let (value, formatter) = if delta.is_none() && column.update_with.has_fallback() {
(
analysis::previous_segment_time(timer, segment_index, method),
ColumnFormatter::SegmentTime,
)
} else {
(delta, ColumnFormatter::Delta)
};
(
value,
split_color(timer, delta, segment_index, false, true, comparison, method),
formatter,
)
}
(SegmentDelta | SegmentDeltaWithFallback, true) => (
analysis::live_segment_delta(timer, segment_index, comparison, method),
SemanticColor::Default,
ColumnFormatter::Delta,
),
};
Some((value, is_live))
}
impl ColumnUpdateWith {
const fn is_segment_based(self) -> bool {
use ColumnUpdateWith::*;
matches!(self, SegmentDelta | SegmentTime | SegmentDeltaWithFallback)
}
const fn has_fallback(self) -> bool {
use ColumnUpdateWith::*;
matches!(self, DeltaWithFallback | SegmentDeltaWithFallback)
}
}