1use crate::{
2 analysis::{self, possible_time_save, split_color},
3 comparison,
4 component::splits::Settings as SplitsSettings,
5 platform::prelude::*,
6 settings::{Color, SemanticColor},
7 timing::{
8 formatter::{Delta, Regular, SegmentTime, TimeFormatter},
9 Snapshot,
10 },
11 util::Clear,
12 GeneralLayoutSettings, Segment, TimeSpan, TimingMethod,
13};
14use core::fmt::Write;
15use serde::{Deserialize, Serialize};
16
17#[derive(Clone, Serialize, Deserialize)]
20#[serde(default)]
21pub struct ColumnSettings {
22 pub name: String,
24 #[serde(flatten)]
26 pub kind: ColumnKind,
27}
28
29#[derive(Clone, Serialize, Deserialize)]
32#[serde(untagged)]
33pub enum ColumnKind {
34 Variable(VariableColumn),
36 Time(TimeColumn),
38}
39
40#[derive(Clone, Serialize, Deserialize)]
42#[serde(default)]
43pub struct TimeColumn {
44 pub start_with: ColumnStartWith,
47 pub update_with: ColumnUpdateWith,
51 pub update_trigger: ColumnUpdateTrigger,
53 pub comparison_override: Option<String>,
56 pub timing_method: Option<TimingMethod>,
60}
61
62#[derive(Default, Clone, Serialize, Deserialize)]
64pub struct VariableColumn {
65 pub variable_name: String,
67}
68
69#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
72pub enum ColumnStartWith {
73 Empty,
75 ComparisonTime,
78 ComparisonSegmentTime,
81 PossibleTimeSave,
84}
85
86#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
90pub enum ColumnUpdateWith {
91 DontUpdate,
94 SplitTime,
96 Delta,
99 DeltaWithFallback,
103 SegmentTime,
105 SegmentDelta,
110 SegmentDeltaWithFallback,
116}
117
118#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
120pub enum ColumnUpdateTrigger {
121 OnStartingSegment,
124 Contextual,
129 OnEndingSegment,
131}
132
133impl Default for ColumnSettings {
134 fn default() -> Self {
135 ColumnSettings {
136 name: String::from("Column"),
137 kind: ColumnKind::Time(TimeColumn::default()),
138 }
139 }
140}
141
142impl Default for TimeColumn {
143 fn default() -> Self {
144 TimeColumn {
145 start_with: ColumnStartWith::Empty,
146 update_with: ColumnUpdateWith::DontUpdate,
147 update_trigger: ColumnUpdateTrigger::Contextual,
148 comparison_override: None,
149 timing_method: None,
150 }
151 }
152}
153
154#[derive(Debug, Serialize, Deserialize)]
156pub struct ColumnState {
157 pub value: String,
159 pub semantic_color: SemanticColor,
161 pub visual_color: Color,
163 pub updates_frequently: bool,
166}
167
168impl Clear for ColumnState {
169 fn clear(&mut self) {
170 self.value.clear();
171 }
172}
173
174enum ColumnFormatter {
175 Time,
176 Delta,
177 SegmentTime,
178}
179
180pub fn update_state(
181 state: &mut ColumnState,
182 column_settings: &ColumnSettings,
183 timer: &Snapshot<'_>,
184 splits_settings: &SplitsSettings,
185 layout_settings: &GeneralLayoutSettings,
186 segment: &Segment,
187 segment_index: usize,
188 current_split: Option<usize>,
189 method: TimingMethod,
190) {
191 match &column_settings.kind {
192 ColumnKind::Variable(column) => {
193 state.value.clear();
194 if let Some(value) = segment.variables().get(column.variable_name.as_str()) {
195 state.value.push_str(value);
196 } else if Some(segment_index) == current_split {
197 if let Some(value) = timer
198 .run()
199 .metadata()
200 .custom_variable_value(column.variable_name.as_str())
201 {
202 state.value.push_str(value);
209 }
210 }
211 state.semantic_color = SemanticColor::Default;
212 state.visual_color = layout_settings.text_color;
213 state.updates_frequently = false;
214 }
215 ColumnKind::Time(column) => {
216 update_time_column(
217 state,
218 column,
219 timer,
220 splits_settings,
221 layout_settings,
222 segment,
223 segment_index,
224 current_split,
225 method,
226 );
227 }
228 }
229}
230
231fn update_time_column(
232 state: &mut ColumnState,
233 column_settings: &TimeColumn,
234 timer: &Snapshot<'_>,
235 splits_settings: &SplitsSettings,
236 layout_settings: &GeneralLayoutSettings,
237 segment: &Segment,
238 segment_index: usize,
239 current_split: Option<usize>,
240 method: TimingMethod,
241) {
242 let method = column_settings.timing_method.unwrap_or(method);
243 let resolved_comparison = comparison::resolve(&column_settings.comparison_override, timer);
244 let comparison = comparison::or_current(resolved_comparison, timer);
245 let update_value = time_column_update_value(
246 column_settings,
247 timer,
248 segment,
249 segment_index,
250 current_split,
251 method,
252 comparison,
253 );
254 let updated = update_value.is_some();
255 let ((column_value, semantic_color, formatter), is_live) = update_value.unwrap_or_else(|| {
256 (
257 match column_settings.start_with {
258 ColumnStartWith::Empty => (None, SemanticColor::Default, ColumnFormatter::Time),
259 ColumnStartWith::ComparisonTime => (
260 segment.comparison(comparison)[method],
261 SemanticColor::Default,
262 ColumnFormatter::Time,
263 ),
264 ColumnStartWith::ComparisonSegmentTime => (
265 analysis::comparison_combined_segment_time(
266 timer.run(),
267 segment_index,
268 comparison,
269 method,
270 ),
271 SemanticColor::Default,
272 ColumnFormatter::SegmentTime,
273 ),
274 ColumnStartWith::PossibleTimeSave => (
275 possible_time_save::calculate(timer, segment_index, comparison, false).0,
276 SemanticColor::Default,
277 ColumnFormatter::SegmentTime,
278 ),
279 },
280 false,
281 )
282 });
283 let is_empty = column_settings.start_with == ColumnStartWith::Empty && !updated;
284 state.updates_frequently = is_live && column_value.is_some();
285 state.value.clear();
286 if !is_empty {
287 let _ = match formatter {
288 ColumnFormatter::Time => write!(
289 state.value,
290 "{}",
291 Regular::with_accuracy(splits_settings.split_time_accuracy).format(column_value)
292 ),
293 ColumnFormatter::Delta => write!(
294 state.value,
295 "{}",
296 Delta::custom(
297 splits_settings.delta_drop_decimals,
298 splits_settings.delta_time_accuracy,
299 )
300 .format(column_value)
301 ),
302 ColumnFormatter::SegmentTime => {
303 write!(
304 state.value,
305 "{}",
306 SegmentTime::with_accuracy(splits_settings.segment_time_accuracy)
307 .format(column_value)
308 )
309 }
310 };
311 }
312 state.semantic_color = semantic_color;
313 state.visual_color = semantic_color.visualize(layout_settings);
314}
315
316fn time_column_update_value(
317 column: &TimeColumn,
318 timer: &Snapshot<'_>,
319 segment: &Segment,
320 segment_index: usize,
321 current_split: Option<usize>,
322 method: TimingMethod,
323 comparison: &str,
324) -> Option<((Option<TimeSpan>, SemanticColor, ColumnFormatter), bool)> {
325 use self::{ColumnUpdateTrigger::*, ColumnUpdateWith::*};
326
327 if current_split < Some(segment_index) {
328 return None;
330 }
331
332 let is_current_split = current_split == Some(segment_index);
333
334 if is_current_split {
335 if column.update_trigger == OnEndingSegment {
336 return None;
338 }
339
340 if column.update_trigger == Contextual
341 && analysis::check_live_delta(
342 timer,
343 !column.update_with.is_segment_based(),
344 comparison,
345 method,
346 )
347 .is_none()
348 {
349 return None;
351 }
352 }
353
354 let is_live = is_current_split;
355
356 let value = match (column.update_with, is_live) {
357 (DontUpdate, _) => return None,
358
359 (SplitTime, false) => (
360 segment.split_time()[method],
361 SemanticColor::Default,
362 ColumnFormatter::Time,
363 ),
364 (SplitTime, true) => (
365 timer.current_time()[method],
366 SemanticColor::Default,
367 ColumnFormatter::Time,
368 ),
369
370 (Delta | DeltaWithFallback, false) => {
371 let split_time = segment.split_time()[method];
372 let delta = catch! {
373 split_time? -
374 segment.comparison(comparison)[method]?
375 };
376 let (value, formatter) = if delta.is_none() && column.update_with.has_fallback() {
377 (split_time, ColumnFormatter::Time)
378 } else {
379 (delta, ColumnFormatter::Delta)
380 };
381 (
382 value,
383 split_color(timer, delta, segment_index, true, true, comparison, method),
384 formatter,
385 )
386 }
387 (Delta | DeltaWithFallback, true) => (
388 catch! {
389 timer.current_time()[method]? -
390 segment.comparison(comparison)[method]?
391 },
392 SemanticColor::Default,
393 ColumnFormatter::Delta,
394 ),
395
396 (SegmentTime, false) => (
397 analysis::previous_segment_time(timer, segment_index, method),
398 SemanticColor::Default,
399 ColumnFormatter::SegmentTime,
400 ),
401 (SegmentTime, true) => (
402 analysis::live_segment_time(timer, segment_index, method),
403 SemanticColor::Default,
404 ColumnFormatter::SegmentTime,
405 ),
406
407 (SegmentDelta | SegmentDeltaWithFallback, false) => {
408 let delta = analysis::previous_segment_delta(timer, segment_index, comparison, method);
409 let (value, formatter) = if delta.is_none() && column.update_with.has_fallback() {
410 (
411 analysis::previous_segment_time(timer, segment_index, method),
412 ColumnFormatter::SegmentTime,
413 )
414 } else {
415 (delta, ColumnFormatter::Delta)
416 };
417 (
418 value,
419 split_color(timer, delta, segment_index, false, true, comparison, method),
420 formatter,
421 )
422 }
423 (SegmentDelta | SegmentDeltaWithFallback, true) => (
424 analysis::live_segment_delta(timer, segment_index, comparison, method),
425 SemanticColor::Default,
426 ColumnFormatter::Delta,
427 ),
428 };
429
430 Some((value, is_live))
431}
432
433impl ColumnUpdateWith {
434 const fn is_segment_based(self) -> bool {
435 use ColumnUpdateWith::*;
436 matches!(self, SegmentDelta | SegmentTime | SegmentDeltaWithFallback)
437 }
438
439 const fn has_fallback(self) -> bool {
440 use ColumnUpdateWith::*;
441 matches!(self, DeltaWithFallback | SegmentDeltaWithFallback)
442 }
443}