sorting_race/models/
interactive_mode.rs1use crate::models::{
4 configuration::{ConfigurationState, DistributionType},
5};
6use anyhow::{Result, anyhow};
7use std::time::Instant;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ApplicationMode {
12 Configuration,
14 Racing,
16 Paused,
18 Complete,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ConfigurationField {
25 ArraySize,
26 Distribution,
27 FairnessMode,
28 BudgetParam,
29 AlphaParam,
30 BetaParam,
31 LearningRateParam,
32}
33
34#[derive(Debug, Clone)]
36pub struct InteractiveMode {
37 pub current_mode: ApplicationMode,
39 pub config_focus: Option<ConfigurationField>,
41 pub array_view_algorithm: usize,
43 pub help_visible: bool,
45 configuration: ConfigurationState,
47 race_start_time: Option<Instant>,
49 race_timer_paused: bool,
51 error_message: Option<String>,
53 needs_update: bool,
55}
56
57impl InteractiveMode {
58 pub fn new() -> Self {
60 Self {
61 current_mode: ApplicationMode::Configuration,
62 config_focus: None,
63 array_view_algorithm: 0,
64 help_visible: false,
65 configuration: ConfigurationState::new(),
66 race_start_time: None,
67 race_timer_paused: false,
68 error_message: None,
69 needs_update: true,
70 }
71 }
72
73 pub fn get_current_config(&self) -> &ConfigurationState {
75 &self.configuration
76 }
77
78 pub fn set_config(&mut self, config: ConfigurationState) {
80 self.configuration = config;
81 self.needs_update = true;
82 }
83
84 pub fn is_configuration_mode(&self) -> bool {
86 self.current_mode == ApplicationMode::Configuration
87 }
88
89 pub fn is_race_active(&self) -> bool {
91 self.current_mode == ApplicationMode::Racing
92 }
93
94 pub fn is_race_paused(&self) -> bool {
96 self.current_mode == ApplicationMode::Paused
97 }
98
99 pub fn is_race_complete(&self) -> bool {
101 self.current_mode == ApplicationMode::Complete
102 }
103
104 pub fn is_race_timer_active(&self) -> bool {
106 self.race_start_time.is_some() && !self.race_timer_paused
107 }
108
109 pub fn is_race_timer_paused(&self) -> bool {
111 self.race_timer_paused
112 }
113
114 pub fn is_menu_visible(&self) -> bool {
116 self.config_focus.is_some()
117 }
118
119 pub fn should_show_help_overlay(&self) -> bool {
121 self.help_visible
122 }
123
124 pub fn should_update_display(&self) -> bool {
126 self.needs_update
127 }
128
129 pub fn has_selection_changed(&self) -> bool {
131 self.needs_update
132 }
133
134 pub fn is_error_message_visible(&self) -> bool {
136 self.error_message.is_some()
137 }
138
139 pub fn get_error_message(&self) -> Option<&str> {
141 self.error_message.as_deref()
142 }
143
144 pub fn clear_error_message(&mut self) {
146 if self.error_message.is_some() {
147 self.error_message = None;
148 self.needs_update = true;
149 }
150 }
151
152 pub fn set_error_message(&mut self, message: String) {
154 self.error_message = Some(message);
155 self.needs_update = true;
156 }
157
158 pub fn get_race_elapsed_time(&self) -> Option<std::time::Duration> {
160 self.race_start_time.map(|start| start.elapsed())
161 }
162
163 pub fn get_race_start_time(&self) -> Option<Instant> {
165 self.race_start_time
166 }
167
168 pub fn transition_to_configuration(&mut self) -> Result<()> {
170 match self.current_mode {
171 ApplicationMode::Complete => {
172 self.current_mode = ApplicationMode::Configuration;
173 self.race_start_time = None;
174 self.race_timer_paused = false;
175 self.clear_error_message();
176 self.needs_update = true;
177 Ok(())
178 },
179 ApplicationMode::Configuration => Ok(()), _ => Err(anyhow!("Cannot transition to Configuration from {:?}", self.current_mode)),
181 }
182 }
183
184 pub fn transition_to_racing(&mut self) -> Result<()> {
186 match self.current_mode {
187 ApplicationMode::Configuration => {
188 if let Err(e) = self.configuration.validate() {
190 self.set_error_message(format!("Configuration error: {}", e));
191 return Err(e);
192 }
193
194 if self.config_focus.is_some() {
196 return Ok(()); }
198
199 self.current_mode = ApplicationMode::Racing;
200 self.race_start_time = Some(Instant::now());
201 self.race_timer_paused = false;
202 self.clear_error_message();
203 self.needs_update = true;
204 Ok(())
205 },
206 ApplicationMode::Paused => {
207 self.current_mode = ApplicationMode::Racing;
208 self.race_timer_paused = false;
209 self.needs_update = true;
210 Ok(())
211 },
212 _ => Err(anyhow!("Cannot transition to Racing from {:?}", self.current_mode)),
213 }
214 }
215
216 pub fn transition_to_paused(&mut self) -> Result<()> {
218 match self.current_mode {
219 ApplicationMode::Racing => {
220 self.current_mode = ApplicationMode::Paused;
221 self.race_timer_paused = true;
222 self.needs_update = true;
223 Ok(())
224 },
225 _ => Err(anyhow!("Cannot transition to Paused from {:?}", self.current_mode)),
226 }
227 }
228
229 pub fn transition_to_complete(&mut self) -> Result<()> {
231 match self.current_mode {
232 ApplicationMode::Racing | ApplicationMode::Paused => {
233 self.current_mode = ApplicationMode::Complete;
234 self.race_timer_paused = true;
235 self.needs_update = true;
236 Ok(())
237 },
238 _ => Err(anyhow!("Cannot transition to Complete from {:?}", self.current_mode)),
239 }
240 }
241
242 pub fn set_config_focus(&mut self, field: ConfigurationField) -> Result<()> {
244 if self.current_mode == ApplicationMode::Racing {
245 return Err(anyhow!("Cannot change focus while racing"));
246 }
247 self.config_focus = Some(field);
248 self.clear_error_message();
249 self.needs_update = true;
250 Ok(())
251 }
252
253 pub fn clear_config_focus(&mut self) {
255 if self.config_focus.is_some() {
256 self.config_focus = None;
257 self.needs_update = true;
258 }
259 }
260
261 pub fn toggle_help(&mut self) {
263 self.help_visible = !self.help_visible;
264 self.needs_update = true;
265 }
266
267 pub fn set_array_view_algorithm(&mut self, algorithm_index: usize) {
269 if self.array_view_algorithm != algorithm_index {
270 self.array_view_algorithm = algorithm_index;
271 self.needs_update = true;
272 }
273 }
274
275 pub fn cycle_array_view_algorithm(&mut self, algorithm_count: usize) {
277 if algorithm_count > 0 {
278 self.array_view_algorithm = (self.array_view_algorithm + 1) % algorithm_count;
279 self.needs_update = true;
280 }
281 }
282
283 pub fn mark_display_updated(&mut self) {
285 self.needs_update = false;
286 }
287
288 pub fn set_array_size_interactive(&mut self, size: u32) -> Result<()> {
290 self.configuration.set_array_size(size)?;
291 self.clear_error_message();
292 self.needs_update = true;
293 Ok(())
294 }
295
296 pub fn attempt_set_array_size(&mut self, size: u32) -> bool {
298 match self.set_array_size_interactive(size) {
299 Ok(()) => true,
300 Err(e) => {
301 self.set_error_message(e.to_string());
302 false
303 }
304 }
305 }
306
307 pub fn set_distribution_interactive(&mut self, distribution: DistributionType) {
309 self.configuration.distribution = distribution;
310 self.clear_error_message();
311 self.needs_update = true;
312 }
313
314 pub fn set_fairness_mode_interactive(&mut self, mode: crate::models::config::FairnessMode) {
316 self.configuration.set_fairness_mode(mode);
317 self.clear_error_message();
318 self.needs_update = true;
319 }
320
321 pub fn set_budget_parameter(&mut self, budget: u32) -> Result<()> {
323 if budget == 0 {
324 return Err(anyhow!("Budget must be greater than 0"));
325 }
326 self.configuration.budget = Some(budget);
327 self.clear_error_message();
328 self.needs_update = true;
329 Ok(())
330 }
331
332 pub fn get_help_overlay_content(&self) -> String {
334 let mut content = String::new();
335 content.push_str("Keyboard Shortcuts:\n\n");
336 content.push_str("k - Array size configuration\n");
337 content.push_str("b - Distribution configuration\n");
338 content.push_str("f - Fairness mode configuration\n");
339 content.push_str("v - Switch array visualization\n");
340 content.push_str("Space - Start/Pause race\n");
341 content.push_str("? - Toggle help\n");
342 content.push_str("Arrow keys - Navigate menus\n");
343 content.push_str("Enter - Confirm selection\n");
344 content.push_str("q - Quit\n");
345 content
346 }
347}
348
349impl Default for InteractiveMode {
350 fn default() -> Self {
351 Self::new()
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn test_interactive_mode_creation() {
361 let mode = InteractiveMode::new();
362 assert_eq!(mode.current_mode, ApplicationMode::Configuration);
363 assert_eq!(mode.config_focus, None);
364 assert_eq!(mode.array_view_algorithm, 0);
365 assert!(!mode.help_visible);
366 assert!(!mode.is_error_message_visible());
367 }
368
369 #[test]
370 fn test_application_mode_transitions() {
371 let mut mode = InteractiveMode::new();
372
373 assert!(mode.transition_to_racing().is_ok());
375 assert_eq!(mode.current_mode, ApplicationMode::Racing);
376 assert!(mode.is_race_timer_active());
377
378 assert!(mode.transition_to_paused().is_ok());
380 assert_eq!(mode.current_mode, ApplicationMode::Paused);
381 assert!(mode.is_race_timer_paused());
382
383 assert!(mode.transition_to_racing().is_ok());
385 assert_eq!(mode.current_mode, ApplicationMode::Racing);
386 assert!(mode.is_race_timer_active());
387
388 assert!(mode.transition_to_complete().is_ok());
390 assert_eq!(mode.current_mode, ApplicationMode::Complete);
391
392 assert!(mode.transition_to_configuration().is_ok());
394 assert_eq!(mode.current_mode, ApplicationMode::Configuration);
395 }
396
397 #[test]
398 fn test_configuration_focus() {
399 let mut mode = InteractiveMode::new();
400
401 assert!(mode.set_config_focus(ConfigurationField::ArraySize).is_ok());
402 assert_eq!(mode.config_focus, Some(ConfigurationField::ArraySize));
403 assert!(mode.is_menu_visible());
404
405 mode.clear_config_focus();
406 assert_eq!(mode.config_focus, None);
407 assert!(!mode.is_menu_visible());
408 }
409
410 #[test]
411 fn test_focus_blocked_during_racing() {
412 let mut mode = InteractiveMode::new();
413
414 mode.transition_to_racing().unwrap();
415 assert!(mode.set_config_focus(ConfigurationField::ArraySize).is_err());
416 }
417
418 #[test]
419 fn test_help_toggle() {
420 let mut mode = InteractiveMode::new();
421
422 assert!(!mode.should_show_help_overlay());
423
424 mode.toggle_help();
425 assert!(mode.should_show_help_overlay());
426
427 mode.toggle_help();
428 assert!(!mode.should_show_help_overlay());
429 }
430
431 #[test]
432 fn test_array_view_cycling() {
433 let mut mode = InteractiveMode::new();
434
435 assert_eq!(mode.array_view_algorithm, 0);
436
437 mode.cycle_array_view_algorithm(7);
438 assert_eq!(mode.array_view_algorithm, 1);
439
440 mode.set_array_view_algorithm(6);
441 mode.cycle_array_view_algorithm(7);
442 assert_eq!(mode.array_view_algorithm, 0); }
444
445 #[test]
446 fn test_error_handling() {
447 let mut mode = InteractiveMode::new();
448
449 assert!(!mode.is_error_message_visible());
450
451 mode.set_error_message("Test error".to_string());
452 assert!(mode.is_error_message_visible());
453 assert_eq!(mode.get_error_message(), Some("Test error"));
454
455 mode.clear_error_message();
456 assert!(!mode.is_error_message_visible());
457 assert_eq!(mode.get_error_message(), None);
458 }
459
460 #[test]
461 fn test_array_size_validation() {
462 let mut mode = InteractiveMode::new();
463
464 assert!(mode.set_array_size_interactive(100).is_ok());
465 assert!(!mode.attempt_set_array_size(5)); assert!(mode.is_error_message_visible());
467
468 mode.clear_error_message();
469 assert!(mode.attempt_set_array_size(500)); assert!(!mode.is_error_message_visible());
471 }
472
473 #[test]
474 fn test_help_content() {
475 let mode = InteractiveMode::new();
476 let help_content = mode.get_help_overlay_content();
477
478 assert!(help_content.contains("k - Array size configuration"));
479 assert!(help_content.contains("b - Distribution configuration"));
480 assert!(help_content.contains("f - Fairness mode configuration"));
481 assert!(help_content.contains("v - Switch array visualization"));
482 assert!(help_content.contains("Space - Start/Pause race"));
483 assert!(help_content.contains("? - Toggle help"));
484 assert!(help_content.contains("q - Quit"));
485 }
486}