livesplit_core/run/editor/
state.rs1use super::{Editor, SegmentRow, TimingMethod};
2use crate::{
3 comparison::personal_best,
4 platform::prelude::*,
5 run::RunMetadata,
6 settings::{CachedImageId, ImageData},
7 timing::formatter::{none_wrapper::EmptyWrapper, Accuracy, SegmentTime, TimeFormatter},
8};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Serialize, Deserialize)]
14pub struct State {
15 pub icon_change: Option<ImageData>,
19 pub game: String,
21 pub category: String,
23 pub offset: String,
26 pub attempts: u32,
29 pub timing_method: TimingMethod,
32 pub segments: Vec<Segment>,
34 pub comparison_names: Vec<String>,
36 pub buttons: Buttons,
38 pub metadata: RunMetadata,
41}
42
43#[derive(Debug, Serialize, Deserialize)]
47pub struct Buttons {
48 pub can_remove: bool,
51 pub can_move_up: bool,
55 pub can_move_down: bool,
59}
60
61#[derive(Debug, Serialize, Deserialize)]
63pub struct Segment {
64 pub icon_change: Option<ImageData>,
68 pub name: String,
70 pub split_time: String,
72 pub segment_time: String,
74 pub best_segment_time: String,
76 pub comparison_times: Vec<String>,
80 pub selected: SelectionState,
82}
83
84#[derive(Debug, Serialize, Deserialize)]
86pub enum SelectionState {
87 NotSelected,
89 Selected,
91 Active,
94}
95
96impl SelectionState {
97 pub const fn is_selected_or_active(&self) -> bool {
99 matches!(self, Self::Selected | Self::Active)
100 }
101}
102
103#[cfg(feature = "std")]
104impl State {
105 pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
107 where
108 W: std::io::Write,
109 {
110 serde_json::to_writer(writer, self)
111 }
112}
113
114impl Editor {
115 pub fn state(&mut self) -> State {
117 let formatter = EmptyWrapper::new(SegmentTime::with_accuracy(Accuracy::Hundredths));
118
119 let icon_change = self
120 .game_icon_id
121 .update_with(self.run.game_icon().into())
122 .map(Into::into);
123 let game = self.game_name().to_string();
124 let category = self.category_name().to_string();
125 let offset = formatter.format(self.offset()).to_string();
126 let attempts = self.attempt_count();
127 let timing_method = self.selected_timing_method();
128 let comparison_names = self
129 .custom_comparisons()
130 .iter()
131 .filter(|&n| n != personal_best::NAME)
132 .cloned()
133 .collect::<Vec<_>>();
134
135 let buttons = Buttons {
136 can_remove: self.can_remove_segments(),
137 can_move_up: self.can_move_segments_up(),
138 can_move_down: self.can_move_segments_down(),
139 };
140 let mut segments = Vec::with_capacity(self.run.len());
141
142 self.segment_icon_ids
143 .resize(self.run.len(), CachedImageId::default());
144
145 for segment_index in 0..self.run.len() {
146 let (name, split_time, segment_time, best_segment_time, comparison_times);
147 {
148 let row = SegmentRow::new(segment_index, self);
149 name = row.name().to_string();
150 split_time = formatter.format(row.split_time()).to_string();
151 segment_time = formatter.format(row.segment_time()).to_string();
152 best_segment_time = formatter.format(row.best_segment_time()).to_string();
153 comparison_times = comparison_names
154 .iter()
155 .map(|c| formatter.format(row.comparison_time(c)).to_string())
156 .collect();
157 }
158
159 let icon_change = self.segment_icon_ids[segment_index]
160 .update_with(self.run.segment(segment_index).icon().into())
161 .map(Into::into);
162
163 let selected = if self.active_segment_index() == segment_index {
164 SelectionState::Active
165 } else if self.selected_segments.contains(&segment_index) {
166 SelectionState::Selected
167 } else {
168 SelectionState::NotSelected
169 };
170
171 segments.push(Segment {
172 icon_change,
173 name,
174 split_time,
175 segment_time,
176 best_segment_time,
177 comparison_times,
178 selected,
179 });
180 }
181
182 State {
183 icon_change,
184 game,
185 category,
186 offset,
187 attempts,
188 timing_method,
189 segments,
190 comparison_names,
191 buttons,
192 metadata: self.run.metadata().clone(),
193 }
194 }
195}