Repository: LiveSplit/livesplit-core (rust)

Issue:
Possibly implement a Segment Time Component
Unless I'm missing something, I don't think you can currently show the Segment Time of the current comparison / the comparison you selected. We have Previous Segment, which is the Segment Delta and the Delta Component, but no Segment Time component. Someone on r/speedrun suggested adding more segment times to the Detailed Timer, but that would probably get too cluttered, so having Segment Time as its own component would probably make sense.

Pull request description:
Implement a segment time component.
This draft PR contains initial work for implementing a segment time component. This is far from merge ready right now but the final plan is to have it act similarly to the segment time display on the detailed timer. 

One thing that needs to be decided on is the name of the key for display on the layout. Right now it is simply named "Segment Time" but I feel like this could be a lot more descriptive. Currently my plan is to show the comparison in parenthesis if it differs from the timer's current comparison.

Also I'm not sure if I documented everything correctly either.

Closes #259

Patch:
diff --git a/capi/src/lib.rs b/capi/src/lib.rs
index c77b1d0..8d0e4d1 100644
--- a/capi/src/lib.rs
+++ b/capi/src/lib.rs
@@ -43,6 +43,7 @@ pub mod segment;
 pub mod segment_history;
 pub mod segment_history_element;
 pub mod segment_history_iter;
+pub mod segment_time_component;
 pub mod separator_component;
 pub mod setting_value;
 pub mod shared_timer;
diff --git a/capi/src/segment_time_component.rs b/capi/src/segment_time_component.rs
new file mode 100644
index 0000000..7c28a29
--- /dev/null
+++ b/capi/src/segment_time_component.rs
@@ -0,0 +1,53 @@
+//! The Segment Time Component is a component that shows the time for the current
+//! segment in a comparison of your choosing. If no comparison is specified it
+//! uses the timer's current comparison.
+
+use super::{output_vec, Json};
+use crate::component::OwnedComponent;
+use crate::key_value_component_state::OwnedKeyValueComponentState;
+use livesplit_core::component::segment_time::Component as SegmentTimeComponent;
+use livesplit_core::Timer;
+
+/// type
+pub type OwnedSegmentTimeComponent = Box<SegmentTimeComponent>;
+
+/// Creates a new Segment Time Component.
+#[no_mangle]
+pub extern "C" fn SegmentTimeComponent_new() -> OwnedSegmentTimeComponent {
+    Box::new(SegmentTimeComponent::new())
+}
+
+/// drop
+#[no_mangle]
+pub extern "C" fn SegmentTimeComponent_drop(this: OwnedSegmentTimeComponent) {
+    drop(this);
+}
+
+/// Converts the component into a generic component suitable for using with a
+/// layout.
+#[no_mangle]
+pub extern "C" fn SegmentTimeComponent_into_generic(
+    this: OwnedSegmentTimeComponent,
+) -> OwnedComponent {
+    Box::new((*this).into())
+}
+
+/// Encodes the component's state information as JSON.
+#[no_mangle]
+pub extern "C" fn SegmentTimeComponent_state_as_json(
+    this: &SegmentTimeComponent,
+    timer: &Timer,
+) -> Json {
+    output_vec(|o| {
+        this.state(timer).write_json(o).unwrap();
+    })
+}
+
+/// Calculates the component's state based on the timer provided.
+#[no_mangle]
+pub extern "C" fn SegmentTimeComponent_state(
+    this: &SegmentTimeComponent,
+    timer: &Timer,
+) -> OwnedKeyValueComponentState {
+    Box::new(this.state(timer))
+}
diff --git a/src/analysis/state_helper.rs b/src/analysis/state_helper.rs
index 83f925a..9a94121 100644
--- a/src/analysis/state_helper.rs
+++ b/src/analysis/state_helper.rs
@@ -61,14 +61,23 @@ fn find_previous_non_empty_split_and_comparison_time(
 }
 
 /// Calculates the comparison's segment time of the segment with the timing
-/// method specified. This is not calculating the current attempt's segment
-/// times.
-pub fn comparison_segment_time(
+/// method specified, combining segments if the segment before it is empty.
+/// This is not calculating the current attempt's segment times.
+///
+/// # Panics
+///
+/// Panics if the provided `segment_index` is greater than or equal to
+/// `run.len()`.
+pub fn comparison_combined_segment_time(
     run: &Run,
     segment_index: usize,
     comparison: &str,
     method: TimingMethod,
 ) -> Option<TimeSpan> {
+    if comparison == best_segments::NAME {
+        return run.segment(segment_index).best_segment_time()[method];
+    }
+
     let current_comparison_time = run.segment(segment_index).comparison(comparison)[method]?;
 
     let previous_comparison_time = find_previous_non_empty_comparison_time(
@@ -81,6 +90,36 @@ pub fn comparison_segment_time(
     Some(current_comparison_time - previous_comparison_time)
 }
 
+/// Calculates the comparison's segment time of the segment with the timing
+/// method specified. This is not calculating the current attempt's segment
+/// times.
+///
+/// # Panics
+///
+/// Panics if the provided `segment_index` is greater than or equal to
+/// `run.len()`.
+pub fn comparison_single_segment_time(
+    run: &Run,
+    segment_index: usize,
+    comparison: &str,
+    method: TimingMethod,
+) -> Option<TimeSpan> {
+    if comparison == best_segments::NAME {
+        return run.segment(segment_index).best_segment_time()[method];
+    }
+
+    if segment_index == 0 {
+        run.segment(segment_index).comparison(comparison)[method]
+    } else {
+        let current_comparison_time = run.segment(segment_index).comparison(comparison)[method]?;
+
+        let previous_comparison_time =
+            run.segment(segment_index - 1).comparison(comparison)[method]?;
+
+        Some(current_comparison_time - previous_comparison_time)
+    }
+}
+
 fn segment_delta(
     run: &Run,
     segment_index: usize,
diff --git a/src/component/detailed_timer/mod.rs b/src/component/detailed_timer/mod.rs
index 0d15ac4..c47faed 100644
--- a/src/component/detailed_timer/mod.rs
+++ b/src/component/detailed_timer/mod.rs
@@ -5,14 +5,14 @@
 //! comparisons, the segment icon, and the segment's name, can also be shown.
 
 use super::timer;
+use crate::analysis::comparison_single_segment_time;
 use crate::comparison::{self, best_segments, none};
 use crate::platform::prelude::*;
 use crate::settings::{
     CachedImageId, Field, Gradient, ImageData, SemanticColor, SettingsDescription, Value,
 };
 use crate::timing::formatter::{
-    none_wrapper::DashWrapper, timer as formatter, Accuracy, DigitsFormat, Short, TimeFormatter,
-    DASH,
+    timer as formatter, Accuracy, DigitsFormat, SegmentTime, TimeFormatter, DASH,
 };
 use crate::{GeneralLayoutSettings, Segment, TimeSpan, Timer, TimerPhase, TimingMethod};
 use serde::{Deserialize, Serialize};
@@ -207,13 +207,18 @@ impl Component {
 
             let comparison1 = Some((
                 comparison::shorten(comparison1),
-                calculate_comparison_time(timer, timing_method, comparison1, last_split_index),
+                comparison_single_segment_time(run, last_split_index, comparison1, timing_method),
             ));
 
             let comparison2 = if !hide_comparison {
                 Some((
                     comparison::shorten(comparison2),
-                    calculate_comparison_time(timer, timing_method, comparison2, last_split_index),
+                    comparison_single_segment_time(
+                        run,
+                        last_split_index,
+                        comparison2,
+                        timing_method,
+                    ),
                 ))
             } else {
                 None
@@ -225,10 +230,11 @@ impl Component {
         };
 
         let timer_state = self.timer.state(timer, layout_settings);
-        let mut segment_time = calculate_segment_time(timer, timing_method, last_split_index);
+        let mut segment_time = calculate_live_segment_time(timer, timing_method, last_split_index);
 
         if segment_time.is_none() && timing_method == TimingMethod::GameTime {
-            segment_time = calculate_segment_time(timer, TimingMethod::RealTime, last_split_index);
+            segment_time =
+                calculate_live_segment_time(timer, TimingMethod::RealTime, last_split_index);
         }
 
         let (top_color, bottom_color) =
@@ -263,7 +269,7 @@ impl Component {
             },
         };
 
-        let formatter = DashWrapper::new(Short::new());
+        let formatter = SegmentTime::new();
 
         let comparison1 = comparison1.map(|(name, time)| ComparisonState {
             name: name.to_string(),
@@ -396,36 +402,7 @@ impl Component {
     }
 }
 
-fn calculate_comparison_time(
-    timer: &Timer,
-    timing_method: TimingMethod,
-    comparison: &str,
-    last_split_index: usize,
-) -> Option<TimeSpan> {
-    if comparison == best_segments::NAME {
-        timer.run().segment(last_split_index).best_segment_time()[timing_method]
-    } else if last_split_index == 0 {
-        timer
-            .run()
-            .segment(0)
-            .comparison_timing_method(comparison, timing_method)
-    } else if timer.current_split_index() > Some(0) {
-        Some(
-            timer
-                .run()
-                .segment(last_split_index)
-                .comparison_timing_method(comparison, timing_method)?
-                - timer
-                    .run()
-                    .segment(last_split_index - 1)
-                    .comparison_timing_method(comparison, timing_method)?,
-        )
-    } else {
-        None
-    }
-}
-
-fn calculate_segment_time(
+fn calculate_live_segment_time(
     timer: &Timer,
     timing_method: TimingMethod,
     last_split_index: usize,
diff --git a/src/component/mod.rs b/src/component/mod.rs
index 5868585..56aa785 100644
--- a/src/component/mod.rs
+++ b/src/component/mod.rs
@@ -12,6 +12,7 @@ pub mod graph;
 pub mod pb_chance;
 pub mod possible_time_save;
 pub mod previous_segment;
+pub mod segment_time;
 pub mod separator;
 pub mod splits;
 pub mod sum_of_best;
@@ -31,6 +32,7 @@ pub use graph::Component as Graph;
 pub use pb_chance::Component as PbChance;
 pub use possible_time_save::Component as PossibleTimeSave;
 pub use previous_segment::Component as PreviousSegment;
+pub use segment_time::Component as SegmentTime;
 pub use separator::Component as Separator;
 pub use splits::Component as Splits;
 pub use sum_of_best::Component as SumOfBest;
diff --git a/src/component/possible_time_save.rs b/src/component/possible_time_save.rs
index 8509347..9db0fcb 100644
--- a/src/component/possible_time_save.rs
+++ b/src/component/possible_time_save.rs
@@ -8,7 +8,7 @@ use super::key_value;
 use crate::analysis::possible_time_save;
 use crate::platform::prelude::*;
 use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
-use crate::timing::formatter::{Accuracy, PossibleTimeSave, TimeFormatter};
+use crate::timing::formatter::{Accuracy, SegmentTime, TimeFormatter};
 use crate::{comparison, Timer, TimerPhase};
 use alloc::borrow::Cow;
 use core::fmt::Write as FmtWrite;
@@ -148,7 +148,7 @@ impl Component {
             value_color: self.settings.value_color,
             semantic_color: Default::default(),
             key: text.into_owned().into(),
-            value: PossibleTimeSave::with_accuracy(self.settings.accuracy)
+            value: SegmentTime::with_accuracy(self.settings.accuracy)
                 .format(time)
                 .to_string()
                 .into(),
diff --git a/src/component/previous_segment.rs b/src/component/previous_segment.rs
index f65523b..2d59adc 100644
--- a/src/component/previous_segment.rs
+++ b/src/component/previous_segment.rs
@@ -8,7 +8,7 @@
 use super::key_value;
 use crate::platform::prelude::*;
 use crate::settings::{Color, Field, Gradient, SemanticColor, SettingsDescription, Value};
-use crate::timing::formatter::{Accuracy, Delta, PossibleTimeSave, TimeFormatter};
+use crate::timing::formatter::{Accuracy, Delta, SegmentTime, TimeFormatter};
 use crate::{analysis, comparison, GeneralLayoutSettings, Timer, TimerPhase};
 use alloc::borrow::Cow;
 use core::fmt::Write as FmtWrite;
@@ -201,7 +201,7 @@ impl Component {
             write!(
                 time,
                 " / {}",
-                PossibleTimeSave::with_accuracy(self.settings.accuracy).format(previous_possible)
+                SegmentTime::with_accuracy(self.settings.accuracy).format(previous_possible)
             )
             .unwrap();
         }
diff --git a/src/component/segment_time/mod.rs b/src/component/segment_time/mod.rs
new file mode 100644
index 0000000..4611db5
--- /dev/null
+++ b/src/component/segment_time/mod.rs
@@ -0,0 +1,197 @@
+//! Provides the Segment Time Component. The Segment Time Component is a
+//! component that shows the time for the current segment in a comparison of
+//! your choosing. If no comparison is specified it uses the timer's current
+//! comparison.
+
+use super::key_value;
+use crate::analysis::state_helper::comparison_single_segment_time;
+use crate::platform::prelude::*;
+use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
+use crate::timing::formatter::{Accuracy, SegmentTime, TimeFormatter};
+use crate::{comparison, Timer, TimerPhase};
+use alloc::borrow::Cow;
+use serde::{Deserialize, Serialize};
+
+#[cfg(test)]
+mod tests;
+
+/// The Segment Time Component is a component that shows the time for the current
+/// segment in a comparison of your choosing. If no comparison is specified it
+/// uses the timer's current comparison.
+#[derive(Default, Clone)]
+pub struct Component {
+    settings: Settings,
+}
+
+/// The Settings for this component.
+#[derive(Clone, Serialize, Deserialize)]
+#[serde(default)]
+pub struct Settings {
+    /// The background shown behind the component.
+    pub background: Gradient,
+    /// The comparison chosen. Uses the Timer's current comparison if set to
+    /// `None`.
+    pub comparison_override: Option<String>,
+    /// Specifies whether to display the name of the component and its value in
+    /// two separate rows.
+    pub display_two_rows: bool,
+    /// The color of the label. If `None` is specified, the color is taken from
+    /// the layout.
+    pub label_color: Option<Color>,
+    /// The color of the value. If `None` is specified, the color is taken from
+    /// the layout.
+    pub value_color: Option<Color>,
+    /// The accuracy of the time shown.
+    pub accuracy: Accuracy,
+}
+
+impl Default for Settings {
+    fn default() -> Self {
+        Self {
+            background: key_value::DEFAULT_GRADIENT,
+            comparison_override: None,
+            display_two_rows: false,
+            label_color: None,
+            value_color: None,
+            accuracy: Accuracy::Hundredths,
+        }
+    }
+}
+
+impl Component {
+    /// Creates a new Segment Time Component.
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    /// Creates a new Segment Time Component with the given settings.
+    pub fn with_settings(settings: Settings) -> Self {
+        Self { settings }
+    }
+
+    /// Accesses the settings of the component.
+    pub fn settings(&self) -> &Settings {
+        &self.settings
+    }
+
+    /// Grants mutable access to the settings of the component.
+    pub fn settings_mut(&mut self) -> &mut Settings {
+        &mut self.settings
+    }
+
+    /// Accesses the name of the component.
+    pub fn name(&self) -> Cow<'static, str> {
+        self.text(
+            self.settings
+                .comparison_override
+                .as_ref()
+                .map(String::as_str),
+        )
+    }
+
+    fn text(&self, comparison: Option<&str>) -> Cow<'static, str> {
+        if let Some(comparison) = comparison {
+            match comparison {
+                comparison::best_segments::NAME => "Best Segment Time".into(),
+                comparison::worst_segments::NAME => "Worst Segment Time".into(),
+                comparison::average_segments::NAME => "Average Segment Time".into(),
+                comparison::median_segments::NAME => "Median Segment Time".into(),
+                comparison::latest_run::NAME => "Latest Segment Time".into(),
+                comparison => format!("Segment Time ({})", comparison::shorten(comparison)).into(),
+            }
+        } else {
+            "Segment Time".into()
+        }
+    }
+
+    /// Calculates the component's state based on the timer provided.
+    pub fn state(&self, timer: &Timer) -> key_value::State {
+        let resolved_comparison = comparison::resolve(&self.settings.comparison_override, timer);
+        let comparison = comparison::or_current(resolved_comparison, timer);
+        let key = self.text(resolved_comparison).into_owned();
+
+        let key_abbreviations = match &*key {
+            "Best Segment Time" => Box::new(["Best Seg. Time".into(), "Best Segment".into()]) as _,
+            "Worst Segment Time" => {
+                Box::new(["Worst Seg. Time".into(), "Worst Segment".into()]) as _
+            }
+            "Average Segment Time" => {
+                Box::new(["Average Seg. Time".into(), "Average Segment".into()]) as _
+            }
+            "Median Segment Time" => {
+                Box::new(["Median Seg. Time".into(), "Median Segment".into()]) as _
+            }
+            "Latest Segment Time" => {
+                Box::new(["Latest Seg. Time".into(), "Latest Segment".into()]) as _
+            }
+            "Segment Time" => Box::new(["Seg. Time".into()]) as _,
+            _ => Box::new(["Segment Time".into(), "Seg. Time".into()]) as _,
+        };
+
+        let time = catch! {
+            // FIXME: We shouldn't need to manually do this "bounds check".
+            if timer.current_phase() == TimerPhase::Ended {
+                return None;
+            }
+
+            comparison_single_segment_time(
+                timer.run(),
+                timer.current_split_index()?,
+                comparison,
+                timer.current_timing_method(),
+            )?
+        };
+
+        key_value::State {
+            background: self.settings.background,
+            key_color: self.settings.label_color,
+            value_color: self.settings.value_color,
+            semantic_color: Default::default(),
+            key: key.into(),
+            value: SegmentTime::with_accuracy(self.settings.accuracy)
+                .format(time)
+                .to_string()
+                .into(),
+            key_abbreviations,
+            display_two_rows: self.settings.display_two_rows,
+        }
+    }
+
+    /// Accesses a generic description of the settings available for this
+    /// component and their current values.
+    pub fn settings_description(&self) -> SettingsDescription {
+        SettingsDescription::with_fields(vec![
+            Field::new("Background".into(), self.settings.background.into()),
+            Field::new(
+                "Comparison".into(),
+                self.settings.comparison_override.clone().into(),
+            ),
+            Field::new(
+                "Display 2 Rows".into(),
+                self.settings.display_two_rows.into(),
+            ),
+            Field::new("Label Color".into(), self.settings.label_color.into()),
+            Field::new("Value Color".into(), self.settings.value_color.into()),
+            Field::new("Accuracy".into(), self.settings.accuracy.into()),
+        ])
+    }
+
+    /// Sets a setting's value by its index to the given value.
+    ///
+    /// # Panics
+    ///
+    /// This panics if the type of the value to be set is not compatible with
+    /// the type of the setting's value. A panic can also occur if the index of
+    /// the setting provided is out of bounds.
+    pub fn set_value(&mut self, index: usize, value: Value) {
+        match index {
+            0 => self.settings.background = value.into(),
+            1 => self.settings.comparison_override = value.into(),
+            2 => self.settings.display_two_rows = value.into(),
+            3 => self.settings.label_color = value.into(),
+            4 => self.settings.value_color = value.into(),
+            5 => self.settings.accuracy = value.into(),
+            _ => panic!("Unsupported Setting Index"),
+        }
+    }
+}
diff --git a/src/component/splits/column.rs b/src/component/splits/column.rs
index 308edbf..d88f329 100644
--- a/src/component/splits/column.rs
+++ b/src/component/splits/column.rs
@@ -3,7 +3,7 @@ use crate::{
     analysis::{self, possible_time_save, split_color},
     comparison,
     settings::{Color, SemanticColor},
-    timing::formatter::{Delta, PossibleTimeSave, Regular, TimeFormatter},
+    timing::formatter::{Delta, Regular, SegmentTime, TimeFormatter},
     GeneralLayoutSettings, Segment, TimeSpan, Timer, TimingMethod,
 };
 use serde::{Deserialize, Serialize};
@@ -161,7 +161,12 @@ pub fn state(
                 ColumnFormatter::Time,
             ),
             ColumnStartWith::ComparisonSegmentTime => (
-                analysis::comparison_segment_time(timer.run(), segment_index, comparison, method),
+                analysis::comparison_combined_segment_time(
+                    timer.run(),
+                    segment_index,
+                    comparison,
+                    method,
+                ),
                 SemanticColor::Default,
                 ColumnFormatter::Time,
             ),
@@ -183,7 +188,7 @@ pub fn state(
                 .format(column_value)
                 .to_string(),
             ColumnFormatter::PossibleTimeSave => {
-                PossibleTimeSave::new().format(column_value).to_string()
+                SegmentTime::new().format(column_value).to_string()
             }
         }
     };
diff --git a/src/layout/component.rs b/src/layout/component.rs
index 8a49841..46bcdbf 100644
--- a/src/layout/component.rs
+++ b/src/layout/component.rs
@@ -1,8 +1,8 @@
 use super::{ComponentSettings, ComponentState, GeneralSettings};
 use crate::component::{
     blank_space, current_comparison, current_pace, delta, detailed_timer, graph, pb_chance,
-    possible_time_save, previous_segment, separator, splits, sum_of_best, text, timer, title,
-    total_playtime,
+    possible_time_save, previous_segment, segment_time, separator, splits, sum_of_best, text,
+    timer, title, total_playtime,
 };
 use crate::platform::prelude::*;
 use crate::settings::{SettingsDescription, Value};
@@ -31,6 +31,8 @@ pub enum Component {
     PossibleTimeSave(possible_time_save::Component),
     /// The Previous Segment Component.
     PreviousSegment(previous_segment::Component),
+    /// The Segment Time Component.
+    SegmentTime(segment_time::Component),
     /// The Separator Component.
     Separator(separator::Component),
     /// The Splits Component.
@@ -75,6 +77,7 @@ impl Component {
             Component::PreviousSegment(component) => {
                 ComponentState::KeyValue(component.state(timer, layout_settings))
             }
+            Component::SegmentTime(component) => ComponentState::KeyValue(component.state(timer)),
             Component::Separator(component) => ComponentState::Separator(component.state(timer)),
             Component::Splits(component) => {
                 ComponentState::Splits(component.state(timer, layout_settings))
@@ -118,6 +121,9 @@ impl Component {
             Component::PreviousSegment(component) => {
                 ComponentSettings::PreviousSegment(component.settings().clone())
             }
+            Component::SegmentTime(component) => {
+                ComponentSettings::SegmentTime(component.settings().clone())
+            }
             Component::Separator(_) => ComponentSettings::Separator,
             Component::Splits(component) => ComponentSettings::Splits(component.settings().clone()),
             Component::SumOfBest(component) => {
@@ -144,6 +150,7 @@ impl Component {
             Component::PbChance(component) => component.name().into(),
             Component::PossibleTimeSave(component) => component.name(),
             Component::PreviousSegment(component) => component.name(),
+            Component::SegmentTime(component) => component.name(),
             Component::Separator(component) => component.name().into(),
             Component::Splits(component) => component.name().into(),
             Component::SumOfBest(component) => component.name().into(),
@@ -198,6 +205,7 @@ impl Component {
             Component::PbChance(component) => component.settings_description(),
             Component::PossibleTimeSave(component) => component.settings_description(),
             Component::PreviousSegment(component) => component.settings_description(),
+            Component::SegmentTime(component) => component.settings_description(),
             Component::Separator(component) => component.settings_description(),
             Component::Splits(component) => component.settings_description(),
             Component::SumOfBest(component) => component.settings_description(),
@@ -227,6 +235,7 @@ impl Component {
             Component::PbChance(component) => component.set_value(index, value),
             Component::PossibleTimeSave(component) => component.set_value(index, value),
             Component::PreviousSegment(component) => component.set_value(index, value),
+            Component::SegmentTime(component) => component.set_value(index, value),
             Component::Separator(component) => component.set_value(index, value),
             Component::Splits(component) => component.set_value(index, value),
             Component::SumOfBest(component) => component.set_value(index, value),
diff --git a/src/layout/component_settings.rs b/src/layout/component_settings.rs
index 08a3c1d..7387c5d 100644
--- a/src/layout/component_settings.rs
+++ b/src/layout/component_settings.rs
@@ -1,8 +1,8 @@
 use super::Component;
 use crate::component::{
     blank_space, current_comparison, current_pace, delta, detailed_timer, graph, pb_chance,
-    possible_time_save, previous_segment, separator, splits, sum_of_best, text, timer, title,
-    total_playtime,
+    possible_time_save, previous_segment, segment_time, separator, splits, sum_of_best, text,
+    timer, title, total_playtime,
 };
 use crate::platform::prelude::*;
 use serde::{Deserialize, Serialize};
@@ -28,6 +28,8 @@ pub enum ComponentSettings {
     PossibleTimeSave(possible_time_save::Settings),
     /// The Settings for the Previous Segment Component.
     PreviousSegment(previous_segment::Settings),
+    /// The Settings for the Segment Time Component.
+    SegmentTime(segment_time::Settings),
     /// The Settings for the Separator Component.
     Separator,
     /// The Settings for the Splits Component.
@@ -74,6 +76,9 @@ impl From<ComponentSettings> for Component {
             ComponentSettings::PreviousSegment(settings) => {
                 Component::PreviousSegment(previous_segment::Component::with_settings(settings))
             }
+            ComponentSettings::SegmentTime(settings) => {
+                Component::SegmentTime(segment_time::Component::with_settings(settings))
+            }
             ComponentSettings::Separator => Component::Separator(separator::Component::new()),
             ComponentSettings::Splits(settings) => {
                 Component::Splits(splits::Component::with_settings(settings))
diff --git a/src/layout/parser.rs b/src/layout/parser.rs
index b6b6063..700e6d5 100644
--- a/src/layout/parser.rs
+++ b/src/layout/parser.rs
@@ -390,6 +390,7 @@ where
                     Component::PreviousSegment(c) => {
                         previous_segment::settings(reader, tag.into_buf(), c)
                     }
+                    Component::SegmentTime(_) => end_tag(reader, tag.into_buf()),
                     Component::Separator(_) => end_tag(reader, tag.into_buf()),
                     Component::Splits(c) => splits::settings(reader, tag.into_buf(), c),
                     Component::SumOfBest(c) => sum_of_best::settings(reader, tag.into_buf(), c),
diff --git a/src/timing/formatter/mod.rs b/src/timing/formatter/mod.rs
index 8ce06d5..f9153e8 100644
--- a/src/timing/formatter/mod.rs
+++ b/src/timing/formatter/mod.rs
@@ -26,8 +26,8 @@ mod days;
 mod delta;
 mod digits_format;
 pub mod none_wrapper;
-mod possible_time_save;
 mod regular;
+mod segment_time;
 mod short;
 pub mod timer;
 
@@ -36,8 +36,8 @@ pub use self::complete::Complete;
 pub use self::days::Days;
 pub use self::delta::Delta;
 pub use self::digits_format::DigitsFormat;
-pub use self::possible_time_save::PossibleTimeSave;
 pub use self::regular::Regular;
+pub use self::segment_time::SegmentTime;
 pub use self::short::Short;
 
 use crate::platform::math::f64::{abs, floor};
diff --git a/src/timing/formatter/possible_time_save.rs b/src/timing/formatter/segment_time.rs
similarity index 70%
rename from src/timing/formatter/possible_time_save.rs
rename to src/timing/formatter/segment_time.rs
index eb1a075..c29430c 100644
--- a/src/timing/formatter/possible_time_save.rs
+++ b/src/timing/formatter/segment_time.rs
@@ -7,12 +7,12 @@ pub struct Inner {
     accuracy: Accuracy,
 }
 
-/// The Possible Time Save Time Formatter formats Time Spans for them to be
-/// shown as Time Saves. This specifically means that the fractional part of the
-/// time is always shown and the minutes and hours are only shown when
-/// necessary. The default accuracy is to show 2 digits of the fractional part,
-/// but this can be configured. Unlike the Short Time Formatter, the Possible
-/// Time Save Formatter shows a dash when there's an empty time.
+/// The Segment Time Formatter formats Time Spans for them to be shown as
+/// Segment Times. This specifically means that the fractional part of the time
+/// is always shown and the minutes and hours are only shown when necessary. The
+/// default accuracy is to show 2 digits of the fractional part, but this can be
+/// configured. Unlike the Short Time Formatter, the Segment Time Formatter
+/// shows a dash when there's an empty time.
 ///
 /// # Example Formatting
 ///
@@ -21,33 +21,33 @@ pub struct Inner {
 /// * Minutes `12:34.98`
 /// * Hours `12:34:56.12`
 /// * Negative Times `−23.12`
-pub struct PossibleTimeSave {
+pub struct SegmentTime {
     accuracy: Accuracy,
 }
 
-impl PossibleTimeSave {
-    /// Creates a new Possible Time Save Time Formatter that uses hundredths for
-    /// showing the fractional part.
+impl SegmentTime {
+    /// Creates a new Segment Time Formatter that uses hundredths for showing
+    /// the fractional part.
     pub fn new() -> Self {
         Default::default()
     }
 
-    /// Creates a new Possible Time Save Time Formatter that uses the accuracy
-    /// provided for showing the fractional part.
+    /// Creates a new Segment Time Formatter that uses the accuracy provided for
+    /// showing the fractional part.
     pub fn with_accuracy(accuracy: Accuracy) -> Self {
-        PossibleTimeSave { accuracy }
+        SegmentTime { accuracy }
     }
 }
 
-impl Default for PossibleTimeSave {
+impl Default for SegmentTime {
     fn default() -> Self {
-        PossibleTimeSave {
+        SegmentTime {
             accuracy: Accuracy::Hundredths,
         }
     }
 }
 
-impl TimeFormatter<'_> for PossibleTimeSave {
+impl TimeFormatter<'_> for SegmentTime {
     type Inner = Inner;
 
     fn format<T>(&self, time: T) -> Self::Inner
diff --git a/src/timing/timer/mod.rs b/src/timing/timer/mod.rs
index 266cc81..82cc7c7 100644
--- a/src/timing/timer/mod.rs
+++ b/src/timing/timer/mod.rs
@@ -250,7 +250,10 @@ impl Timer {
     }
 
     /// Accesses the index of the split the attempt is currently on. If there's
-    /// no attempt in progress, `None` is returned instead.
+    /// no attempt in progress, `None` is returned instead. This returns an
+    /// index that is equal to the amount of segments when the attempt is
+    /// finished, but has not been reset. So you need to be careful when using
+    /// this value for indexing.
     #[inline]
     pub fn current_split_index(&self) -> Option<usize> {
         self.current_split_index


Test patch:
diff --git a/src/component/segment_time/tests.rs b/src/component/segment_time/tests.rs
new file mode 100644
index 0000000..6dded05
--- /dev/null
+++ b/src/component/segment_time/tests.rs
@@ -0,0 +1,50 @@
+use super::Component;
+use crate::{
+    tests_helper::{self, make_progress_run_with_splits_opt, run_with_splits, start_run},
+    Timer,
+};
+
+fn create_timer() -> Timer {
+    let mut timer = tests_helper::create_timer(&["A", "B"]);
+    run_with_splits(&mut timer, &[12.3, 45.6]);
+    timer
+}
+
+#[test]
+fn is_empty_when_no_attempt_is_started() {
+    let component = Component::new();
+    let timer = create_timer();
+    let state = component.state(&timer);
+    assert_eq!(&*state.value, "—");
+}
+
+#[test]
+fn is_not_empty_when_attempt_is_started() {
+    let component = Component::new();
+    let mut timer = create_timer();
+    start_run(&mut timer);
+    make_progress_run_with_splits_opt(&mut timer, &[Some(467.23)]);
+    let state = component.state(&timer);
+    assert_eq!(&*state.value, "33.30");
+}
+
+#[test]
+fn is_not_empty_when_attempt_is_paused() {
+    let component = Component::new();
+    let mut timer = create_timer();
+    start_run(&mut timer);
+    make_progress_run_with_splits_opt(&mut timer, &[Some(467.23)]);
+    timer.pause();
+    let state = component.state(&timer);
+    assert_eq!(&*state.value, "33.30");
+}
+
+#[test]
+fn is_empty_when_attempt_is_finished() {
+    let component = Component::new();
+    let mut timer = create_timer();
+    start_run(&mut timer);
+    make_progress_run_with_splits_opt(&mut timer, &[Some(467.23), Some(742.65)]);
+    let state = component.state(&timer);
+    assert_eq!(&*state.value, "—");
+}