1use crate::{
9 platform::prelude::*,
10 settings::{
11 self, CachedImageId, Color, Field, Gradient, ImageData, ListGradient, SettingsDescription,
12 Value,
13 },
14 timing::{formatter::Accuracy, Snapshot},
15 util::{Clear, ClearVec},
16 GeneralLayoutSettings,
17};
18use core::cmp::{max, min};
19use serde::{Deserialize, Serialize};
20
21#[cfg(test)]
22mod tests;
23
24mod column;
25
26pub use column::{
27 ColumnKind, ColumnSettings, ColumnStartWith, ColumnState, ColumnUpdateTrigger,
28 ColumnUpdateWith, TimeColumn, VariableColumn,
29};
30
31const SETTINGS_BEFORE_COLUMNS: usize = 15;
32const SETTINGS_PER_TIME_COLUMN: usize = 6;
33const SETTINGS_PER_VARIABLE_COLUMN: usize = 2;
34
35#[derive(Default, Clone)]
42pub struct Component {
43 icon_ids: Vec<CachedImageId>,
44 settings: Settings,
45 current_split_index: Option<usize>,
46 scroll_offset: isize,
47}
48
49#[derive(Clone, Serialize, Deserialize)]
51#[serde(default)]
52pub struct Settings {
53 pub background: ListGradient,
55 pub visual_split_count: usize,
60 pub split_preview_count: usize,
66 pub show_thin_separators: bool,
69 pub separator_last_split: bool,
74 pub always_show_last_split: bool,
79 pub fill_with_blank_space: bool,
84 pub display_two_rows: bool,
87 pub current_split_gradient: Gradient,
90 pub split_time_accuracy: Accuracy,
92 pub segment_time_accuracy: Accuracy,
94 pub delta_time_accuracy: Accuracy,
96 pub delta_drop_decimals: bool,
99 pub show_column_labels: bool,
101 pub columns: Vec<ColumnSettings>,
105}
106
107#[derive(Debug, Serialize, Deserialize)]
109pub struct SplitState {
110 pub name: String,
112 pub columns: ClearVec<ColumnState>,
115 pub is_current_split: bool,
118 pub index: usize,
123}
124
125impl Clear for SplitState {
126 fn clear(&mut self) {
127 self.name.clear();
128 self.columns.clear();
129 }
130}
131
132#[derive(Debug, Serialize, Deserialize)]
137pub struct IconChange {
138 pub segment_index: usize,
143 pub icon: ImageData,
146}
147
148#[derive(Debug, Default, Serialize, Deserialize)]
150pub struct State {
151 pub background: ListGradient,
153 pub column_labels: Option<ClearVec<String>>,
157 pub splits: ClearVec<SplitState>,
159 pub icon_changes: Vec<IconChange>,
164 pub has_icons: bool,
169 pub show_thin_separators: bool,
172 pub show_final_separator: bool,
175 pub display_two_rows: bool,
178 pub current_split_gradient: Gradient,
181}
182
183impl Default for Settings {
184 fn default() -> Self {
185 Settings {
186 background: ListGradient::Alternating(
187 Color::transparent(),
188 Color::rgba(1.0, 1.0, 1.0, 0.04),
189 ),
190 visual_split_count: 16,
191 split_preview_count: 1,
192 show_thin_separators: true,
193 separator_last_split: true,
194 always_show_last_split: true,
195 fill_with_blank_space: true,
196 display_two_rows: false,
197 current_split_gradient: Gradient::Vertical(
198 Color::rgba(51.0 / 255.0, 115.0 / 255.0, 244.0 / 255.0, 1.0),
199 Color::rgba(21.0 / 255.0, 53.0 / 255.0, 116.0 / 255.0, 1.0),
200 ),
201 split_time_accuracy: Accuracy::Seconds,
202 segment_time_accuracy: Accuracy::Hundredths,
203 delta_time_accuracy: Accuracy::Tenths,
204 delta_drop_decimals: true,
205 show_column_labels: false,
206 columns: vec![
207 ColumnSettings {
208 name: String::from("Time"),
209 kind: ColumnKind::Time(TimeColumn {
210 start_with: ColumnStartWith::ComparisonTime,
211 update_with: ColumnUpdateWith::SplitTime,
212 update_trigger: ColumnUpdateTrigger::OnEndingSegment,
213 comparison_override: None,
214 timing_method: None,
215 }),
216 },
217 ColumnSettings {
218 name: String::from("+/−"),
219 kind: ColumnKind::Time(TimeColumn {
220 start_with: ColumnStartWith::Empty,
221 update_with: ColumnUpdateWith::Delta,
222 update_trigger: ColumnUpdateTrigger::Contextual,
223 comparison_override: None,
224 timing_method: None,
225 }),
226 },
227 ],
228 }
229 }
230}
231
232#[cfg(feature = "std")]
233impl State {
234 pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
236 where
237 W: std::io::Write,
238 {
239 serde_json::to_writer(writer, self)
240 }
241}
242
243impl Component {
244 pub fn new() -> Self {
246 Default::default()
247 }
248
249 pub fn with_settings(settings: Settings) -> Self {
251 Self {
252 settings,
253 ..Default::default()
254 }
255 }
256
257 pub const fn settings(&self) -> &Settings {
259 &self.settings
260 }
261
262 pub fn settings_mut(&mut self) -> &mut Settings {
264 &mut self.settings
265 }
266
267 pub fn scroll_up(&mut self) {
270 self.scroll_offset = self.scroll_offset.saturating_sub(1);
271 }
272
273 pub fn scroll_down(&mut self) {
276 self.scroll_offset = self.scroll_offset.saturating_add(1);
277 }
278
279 pub fn remount(&mut self) {
285 self.icon_ids.clear();
286 }
287
288 pub const fn name(&self) -> &'static str {
290 "Splits"
291 }
292
293 pub fn update_state(
296 &mut self,
297 state: &mut State,
298 timer: &Snapshot<'_>,
299 layout_settings: &GeneralLayoutSettings,
300 ) {
301 if self.current_split_index != timer.current_split_index() {
303 self.current_split_index = timer.current_split_index();
304 self.scroll_offset = 0;
305 }
306
307 let run = timer.run();
308 self.icon_ids.resize(run.len(), CachedImageId::default());
309
310 let mut visual_split_count = self.settings.visual_split_count;
311 if visual_split_count == 0 {
312 visual_split_count = run.len();
313 }
314
315 let current_split = timer.current_split_index();
316 let method = timer.current_timing_method();
317
318 let locked_last_split = isize::from(self.settings.always_show_last_split);
319 let skip_count = min(
320 current_split.map_or(0, |current_split| {
321 max(
322 0,
323 current_split as isize
324 + self.settings.split_preview_count as isize
325 + locked_last_split
326 + 1
327 - visual_split_count as isize,
328 )
329 }),
330 run.len() as isize - visual_split_count as isize,
331 );
332 self.scroll_offset = min(
333 max(self.scroll_offset, -skip_count),
334 run.len() as isize - skip_count - visual_split_count as isize,
335 );
336 let skip_count = max(0, skip_count + self.scroll_offset) as usize;
337 let take_count = visual_split_count - locked_last_split as usize;
338 let always_show_last_split = self.settings.always_show_last_split;
339
340 let show_final_separator = self.settings.separator_last_split
341 && always_show_last_split
342 && skip_count + take_count + 1 < run.len();
343
344 let Settings {
345 show_thin_separators,
346 fill_with_blank_space,
347 display_two_rows,
348 ref columns,
349 ..
350 } = self.settings;
351
352 state.background = self.settings.background;
353
354 if self.settings.show_column_labels {
355 let column_labels = state.column_labels.get_or_insert_with(Default::default);
356 column_labels.clear();
357 for c in &self.settings.columns {
358 column_labels.push().push_str(&c.name);
359 }
360 } else {
361 state.column_labels = None;
362 }
363
364 let icon_changes = &mut state.icon_changes;
365 icon_changes.clear();
366
367 state.splits.clear();
368 for ((i, segment), icon_id) in run
369 .segments()
370 .iter()
371 .enumerate()
372 .zip(self.icon_ids.iter_mut())
373 .skip(skip_count)
374 .filter(|&((i, _), _)| {
375 i - skip_count < take_count || (always_show_last_split && i + 1 == run.len())
376 })
377 {
378 let state = state.splits.push_with(|| SplitState {
379 name: String::new(),
380 columns: ClearVec::new(),
381 is_current_split: false,
382 index: 0,
383 });
384
385 if let Some(icon_change) = icon_id.update_with(Some(segment.icon())) {
386 icon_changes.push(IconChange {
387 segment_index: i,
388 icon: icon_change.into(),
389 });
390 }
391
392 state.name.push_str(segment.name());
393
394 for column in columns {
395 column::update_state(
396 state.columns.push_with(|| ColumnState {
397 value: String::new(),
398 semantic_color: Default::default(),
399 visual_color: Color::transparent(),
400 updates_frequently: false,
401 }),
402 column,
403 timer,
404 &self.settings,
405 layout_settings,
406 segment,
407 i,
408 current_split,
409 method,
410 );
411 }
412
413 state.is_current_split = Some(i) == current_split;
414 state.index = i;
415 }
416
417 if fill_with_blank_space && state.splits.len() < visual_split_count {
418 let blank_split_count = visual_split_count - state.splits.len();
419 for i in 0..blank_split_count {
420 let state = state.splits.push_with(|| SplitState {
421 name: String::new(),
422 columns: ClearVec::new(),
423 is_current_split: false,
424 index: 0,
425 });
426 state.is_current_split = false;
427 state.index = (usize::max_value() ^ 1) - 2 * i;
428 }
429 }
430
431 state.has_icons = run.segments().iter().any(|s| !s.icon().is_empty());
432 state.show_thin_separators = show_thin_separators;
433 state.show_final_separator = show_final_separator;
434 state.display_two_rows = display_two_rows;
435 state.current_split_gradient = self.settings.current_split_gradient;
436 }
437
438 pub fn state(
441 &mut self,
442 timer: &Snapshot<'_>,
443 layout_settings: &GeneralLayoutSettings,
444 ) -> State {
445 let mut state = Default::default();
446 self.update_state(&mut state, timer, layout_settings);
447 state
448 }
449
450 pub fn settings_description(&self) -> SettingsDescription {
453 let mut settings = SettingsDescription::with_fields(vec![
454 Field::new("Background".into(), self.settings.background.into()),
455 Field::new(
456 "Total Splits".into(),
457 Value::UInt(self.settings.visual_split_count as _),
458 ),
459 Field::new(
460 "Upcoming Splits".into(),
461 Value::UInt(self.settings.split_preview_count as _),
462 ),
463 Field::new(
464 "Show Thin Separators".into(),
465 self.settings.show_thin_separators.into(),
466 ),
467 Field::new(
468 "Show Separator Before Last Split".into(),
469 self.settings.separator_last_split.into(),
470 ),
471 Field::new(
472 "Always Show Last Split".into(),
473 self.settings.always_show_last_split.into(),
474 ),
475 Field::new(
476 "Fill with Blank Space if Not Enough Splits".into(),
477 self.settings.fill_with_blank_space.into(),
478 ),
479 Field::new(
480 "Display 2 Rows".into(),
481 self.settings.display_two_rows.into(),
482 ),
483 Field::new(
484 "Current Split Gradient".into(),
485 self.settings.current_split_gradient.into(),
486 ),
487 Field::new(
488 "Split Time Accuracy".into(),
489 self.settings.split_time_accuracy.into(),
490 ),
491 Field::new(
492 "Segment Time Accuracy".into(),
493 self.settings.segment_time_accuracy.into(),
494 ),
495 Field::new(
496 "Delta Time Accuracy".into(),
497 self.settings.delta_time_accuracy.into(),
498 ),
499 Field::new(
500 "Drop Delta Decimals When Showing Minutes".into(),
501 self.settings.delta_drop_decimals.into(),
502 ),
503 Field::new(
504 "Show Column Labels".into(),
505 self.settings.show_column_labels.into(),
506 ),
507 Field::new(
508 "Columns".into(),
509 Value::UInt(self.settings.columns.len() as _),
510 ),
511 ]);
512
513 settings.fields.reserve_exact(
514 self.settings
515 .columns
516 .iter()
517 .map(|column| match column.kind {
518 ColumnKind::Variable(_) => SETTINGS_PER_VARIABLE_COLUMN,
519 ColumnKind::Time(_) => SETTINGS_PER_TIME_COLUMN,
520 })
521 .sum(),
522 );
523
524 for column in &self.settings.columns {
525 settings
526 .fields
527 .push(Field::new("Column Name".into(), column.name.clone().into()));
528
529 match &column.kind {
530 ColumnKind::Variable(column) => {
531 settings.fields.push(Field::new(
532 "Column Type".into(),
533 settings::ColumnKind::Variable.into(),
534 ));
535 settings.fields.push(Field::new(
536 "Variable Name".into(),
537 column.variable_name.clone().into(),
538 ));
539 }
540 ColumnKind::Time(column) => {
541 settings.fields.push(Field::new(
542 "Column Type".into(),
543 settings::ColumnKind::Time.into(),
544 ));
545 settings
546 .fields
547 .push(Field::new("Start With".into(), column.start_with.into()));
548 settings
549 .fields
550 .push(Field::new("Update With".into(), column.update_with.into()));
551 settings.fields.push(Field::new(
552 "Update Trigger".into(),
553 column.update_trigger.into(),
554 ));
555 settings.fields.push(Field::new(
556 "Comparison".into(),
557 column.comparison_override.clone().into(),
558 ));
559 settings.fields.push(Field::new(
560 "Timing Method".into(),
561 column.timing_method.into(),
562 ));
563 }
564 }
565 }
566
567 settings
568 }
569
570 pub fn set_value(&mut self, index: usize, value: Value) {
578 match index {
579 0 => self.settings.background = value.into(),
580 1 => self.settings.visual_split_count = value.into_uint().unwrap() as _,
581 2 => self.settings.split_preview_count = value.into_uint().unwrap() as _,
582 3 => self.settings.show_thin_separators = value.into(),
583 4 => self.settings.separator_last_split = value.into(),
584 5 => self.settings.always_show_last_split = value.into(),
585 6 => self.settings.fill_with_blank_space = value.into(),
586 7 => self.settings.display_two_rows = value.into(),
587 8 => self.settings.current_split_gradient = value.into(),
588 9 => self.settings.split_time_accuracy = value.into(),
589 10 => self.settings.segment_time_accuracy = value.into(),
590 11 => self.settings.delta_time_accuracy = value.into(),
591 12 => self.settings.delta_drop_decimals = value.into(),
592 13 => self.settings.show_column_labels = value.into(),
593 14 => {
594 let new_len = value.into_uint().unwrap() as usize;
595 self.settings.columns.resize(new_len, Default::default());
596 }
597 index => {
598 let mut index = index - SETTINGS_BEFORE_COLUMNS;
599 for column in &mut self.settings.columns {
600 if index < 2 {
601 match index {
602 0 => column.name = value.into(),
603 _ => {
604 column.kind = match settings::ColumnKind::from(value) {
605 settings::ColumnKind::Time => {
606 ColumnKind::Time(Default::default())
607 }
608 settings::ColumnKind::Variable => {
609 ColumnKind::Variable(Default::default())
610 }
611 }
612 }
613 }
614 return;
615 }
616 index -= 2;
617 match &mut column.kind {
618 ColumnKind::Variable(column) => {
619 if index < 1 {
620 column.variable_name = value.into();
621 return;
622 }
623 index -= 1;
624 }
625 ColumnKind::Time(column) => {
626 if index < 5 {
627 match index {
628 0 => column.start_with = value.into(),
629 1 => column.update_with = value.into(),
630 2 => column.update_trigger = value.into(),
631 3 => column.comparison_override = value.into(),
632 _ => column.timing_method = value.into(),
633 }
634 return;
635 }
636 index -= 5;
637 }
638 }
639 }
640 panic!("Unsupported Setting Index")
641 }
642 }
643 }
644}