diff --git a/client/src/modes/handlers.rs b/client/src/modes/handlers.rs index 09942a9..8210779 100644 --- a/client/src/modes/handlers.rs +++ b/client/src/modes/handlers.rs @@ -1,2 +1,3 @@ // src/client/modes/handlers.rs pub mod event; +pub mod mode_manager; diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index e21208d..7dfa5a3 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -1,5 +1,5 @@ // src/modes/handlers/event.rs -use crossterm::event::Event; +use crossterm::event::{Event, KeyEvent}; use crossterm::cursor::SetCursorStyle; use crate::tui::terminal::{ core::TerminalCore, @@ -15,6 +15,7 @@ use crate::modes::{ }; use crate::modes::navigation; use crate::config::binds::key_sequences::KeySequenceTracker; +use crate::modes::handlers::mode_manager::{ModeManager, AppMode}; pub struct EventHandler { pub command_mode: bool, @@ -51,78 +52,102 @@ impl EventHandler { total_count: u64, current_position: &mut u64, ) -> Result<(bool, String), Box> { + // Determine current mode based on app state and event handler state + let current_mode = ModeManager::derive_mode(app_state, self); + app_state.update_mode(current_mode); + if let Event::Key(key) = event { let key_code = key.code; let modifiers = key.modifiers; - // Handle general mode (replaces intro and admin) - if app_state.ui.show_intro || app_state.ui.show_admin { - if let Some(action) = config.get_general_action(key_code, modifiers) { - match action { - "move_up" => { - navigation::move_up(app_state); - return Ok((false, String::new())); - } - "move_down" => { - let item_count = if app_state.ui.show_intro { - 2 // Intro options count - } else { - app_state.profile_tree.profiles.len() // Admin panel items - }; - navigation::move_down(app_state, item_count); - return Ok((false, String::new())); - } - "next_option" => { - navigation::next_option(app_state, 2); // Intro has 2 options - return Ok((false, String::new())); - } - "previous_option" => { - navigation::previous_option(app_state); - return Ok((false, String::new())); - } - "select" => { - navigation::select(app_state); - return Ok((false, "Selected".to_string())); - } - "toggle_sidebar" => { - navigation::toggle_sidebar(app_state); - return Ok((false, format!("Sidebar {}", - if app_state.ui.show_sidebar { "shown" } else { "hidden" } - ))); - } - "next_field" => { - navigation::next_field(form_state); - return Ok((false, String::new())); - } - "prev_field" => { - navigation::prev_field(form_state); - return Ok((false, String::new())); - } - "enter_command_mode" => { - navigation::handle_enter_command_mode(self); - return Ok((false, String::new())); - } - _ => {} - } - } - return Ok((false, String::new())); - } - - // The rest of the function handles other modes as before - // Handle toggling sidebar which is common across modes - if UiStateHandler::toggle_sidebar( - &mut app_state.ui, - config, - key_code, - modifiers, - ) { - return Ok((false, format!("Sidebar {}", + // Handle common actions across all modes + if UiStateHandler::toggle_sidebar(&mut app_state.ui, config, key_code, modifiers) { + return Ok((false, format!("Sidebar {}", if app_state.ui.show_sidebar { "shown" } else { "hidden" } ))); } - // Handle edit mode first to allow normal character input - if self.is_edit_mode { + // Mode-specific handling + match current_mode { + AppMode::General => { + return self.handle_general_mode( + key, + config, + form_state, + app_state, + ); + }, + + AppMode::ReadOnly => { + // Check for mode transitions first + if config.is_enter_edit_mode_before(key_code, modifiers) && + ModeManager::can_enter_edit_mode(current_mode) { + self.is_edit_mode = true; + self.edit_mode_cooldown = true; + self.command_message = "Edit mode".to_string(); + terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; + return Ok((false, self.command_message.clone())); + } + + if config.is_enter_edit_mode_after(key_code, modifiers) && + ModeManager::can_enter_edit_mode(current_mode) { + let current_input = form_state.get_current_input(); + if !current_input.is_empty() && form_state.current_cursor_pos < current_input.len() { + form_state.current_cursor_pos += 1; + self.ideal_cursor_column = form_state.current_cursor_pos; + } + self.is_edit_mode = true; + self.edit_mode_cooldown = true; + self.command_message = "Edit mode (after cursor)".to_string(); + terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; + return Ok((false, self.command_message.clone())); + } + + // Check for entering command mode + if let Some(action) = config.get_read_only_action_for_key(key_code, modifiers) { + if action == "enter_command_mode" && ModeManager::can_enter_command_mode(current_mode) { + self.command_mode = true; + self.command_input.clear(); + self.command_message.clear(); + return Ok((false, String::new())); + } + } + + // Handle common actions + if let Some(action) = config.get_action_for_key_in_mode( + &config.keybindings.common, + key_code, + modifiers + ) { + return self.handle_common_action( + action, + form_state, + grpc_client, + command_handler, + terminal, + app_state, + current_position, + total_count, + ).await; + } + + // Handle read-only mode specific actions + return read_only::handle_read_only_event( + key, + config, + form_state, + &mut self.key_sequence_tracker, + current_position, + total_count, + grpc_client, + &mut self.command_message, + &mut self.edit_mode_cooldown, + &mut self.ideal_cursor_column, + ).await; + }, + + AppMode::Edit => { + // Check for exiting edit mode if config.is_exit_edit_mode(key_code, modifiers) { if form_state.has_unsaved_changes { self.command_message = "Unsaved changes! Use :w to save or :q! to discard".to_string(); @@ -140,7 +165,26 @@ impl EventHandler { } return Ok((false, self.command_message.clone())); } + + // Handle common actions + if let Some(action) = config.get_action_for_key_in_mode( + &config.keybindings.common, + key_code, + modifiers + ) { + return self.handle_common_action( + action, + form_state, + grpc_client, + command_handler, + terminal, + app_state, + current_position, + total_count, + ).await; + } + // Handle edit mode actions let result = edit::handle_edit_event_internal( key, config, @@ -155,64 +199,9 @@ impl EventHandler { self.key_sequence_tracker.reset(); return Ok((false, result)); - } - - // Global command mode activation - let context_action = config.get_action_for_current_context( - self.is_edit_mode, - self.command_mode, - key_code, - modifiers - ); - - // Block command mode entry from edit mode - if let Some("enter_command_mode") = context_action { - if !self.is_edit_mode { - self.command_mode = true; - self.command_input.clear(); - self.command_message.clear(); - return Ok((false, String::new())); - } - } - - if let Some(action) = config.get_action_for_key_in_mode( - &config.keybindings.common, - key_code, - modifiers - ) { - match action { - "save" => { - let message = common::save( - form_state, - grpc_client, - &mut app_state.ui.is_saved, - current_position, - total_count, - ).await?; - return Ok((false, message)); - }, - "force_quit" => { - let (should_exit, message) = command_handler.handle_command("force_quit", terminal).await?; - return Ok((should_exit, message)); - }, - "save_and_quit" => { - let (should_exit, message) = command_handler.handle_command("save_and_quit", terminal).await?; - return Ok((should_exit, message)); - }, - "revert" => { - let message = common::revert( - form_state, - grpc_client, - current_position, - total_count, - ).await?; - return Ok((false, message)); - }, - _ => {} - } - } - - if self.command_mode { + }, + + AppMode::Command => { let (should_exit, message, exit_command_mode) = command_mode::handle_command_event( key, config, @@ -231,52 +220,114 @@ impl EventHandler { return Ok((should_exit, message)); } - - if let Some(action) = config.get_read_only_action_for_key(key_code, modifiers) { - if action == "enter_command_mode" { - self.command_mode = true; - self.command_input.clear(); - self.command_message.clear(); - return Ok((false, String::new())); - } - } - - if config.is_enter_edit_mode_before(key_code, modifiers) { - self.is_edit_mode = true; - self.edit_mode_cooldown = true; - self.command_message = "Edit mode".to_string(); - terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; - return Ok((false, self.command_message.clone())); - } - - if config.is_enter_edit_mode_after(key_code, modifiers) { - let current_input = form_state.get_current_input(); - if !current_input.is_empty() && form_state.current_cursor_pos < current_input.len() { - form_state.current_cursor_pos += 1; - self.ideal_cursor_column = form_state.current_cursor_pos; - } - self.is_edit_mode = true; - self.edit_mode_cooldown = true; - self.command_message = "Edit mode (after cursor)".to_string(); - terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; - return Ok((false, self.command_message.clone())); - } - - return read_only::handle_read_only_event( - key, - config, - form_state, - &mut self.key_sequence_tracker, - current_position, - total_count, - grpc_client, - &mut self.command_message, - &mut self.edit_mode_cooldown, - &mut self.ideal_cursor_column, - ).await; } + } + // Non-key events or if no specific handler was matched self.edit_mode_cooldown = false; Ok((false, self.command_message.clone())) } + + // Helper method for handling general mode actions + fn handle_general_mode( + &mut self, + key: KeyEvent, + config: &Config, + form_state: &mut FormState, + app_state: &mut crate::state::state::AppState, + ) -> Result<(bool, String), Box> { + if let Some(action) = config.get_general_action(key.code, key.modifiers) { + match action { + "move_up" => { + navigation::move_up(app_state); + return Ok((false, String::new())); + } + "move_down" => { + let item_count = if app_state.ui.show_intro { + 2 // Intro options count + } else { + app_state.profile_tree.profiles.len() // Admin panel items + }; + navigation::move_down(app_state, item_count); + return Ok((false, String::new())); + } + "next_option" => { + navigation::next_option(app_state, 2); // Intro has 2 options + return Ok((false, String::new())); + } + "previous_option" => { + navigation::previous_option(app_state); + return Ok((false, String::new())); + } + "select" => { + navigation::select(app_state); + return Ok((false, "Selected".to_string())); + } + "toggle_sidebar" => { + navigation::toggle_sidebar(app_state); + return Ok((false, format!("Sidebar {}", + if app_state.ui.show_sidebar { "shown" } else { "hidden" } + ))); + } + "next_field" => { + navigation::next_field(form_state); + return Ok((false, String::new())); + } + "prev_field" => { + navigation::prev_field(form_state); + return Ok((false, String::new())); + } + "enter_command_mode" => { + navigation::handle_enter_command_mode(self); + return Ok((false, String::new())); + } + _ => {} + } + } + Ok((false, String::new())) + } + + // Helper method for handling common actions across modes + async fn handle_common_action( + &mut self, + action: &str, + form_state: &mut FormState, + grpc_client: &mut GrpcClient, + command_handler: &mut CommandHandler, + terminal: &mut TerminalCore, + app_state: &mut crate::state::state::AppState, + current_position: &mut u64, + total_count: u64, + ) -> Result<(bool, String), Box> { + match action { + "save" => { + let message = common::save( + form_state, + grpc_client, + &mut app_state.ui.is_saved, + current_position, + total_count, + ).await?; + Ok((false, message)) + }, + "force_quit" => { + let (should_exit, message) = command_handler.handle_command("force_quit", terminal).await?; + Ok((should_exit, message)) + }, + "save_and_quit" => { + let (should_exit, message) = command_handler.handle_command("save_and_quit", terminal).await?; + Ok((should_exit, message)) + }, + "revert" => { + let message = common::revert( + form_state, + grpc_client, + current_position, + total_count, + ).await?; + Ok((false, message)) + }, + _ => Ok((false, format!("Unknown common action: {}", action))), + } + } } diff --git a/client/src/modes/handlers/mode_manager.rs b/client/src/modes/handlers/mode_manager.rs new file mode 100644 index 0000000..4ec80aa --- /dev/null +++ b/client/src/modes/handlers/mode_manager.rs @@ -0,0 +1,50 @@ +// src/modes/handlers/mode_manager.rs +use crate::state::state::AppState; +use crate::modes::handlers::event::EventHandler; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AppMode { + General, // For intro and admin screens + ReadOnly, // Canvas read-only mode + Edit, // Canvas edit mode + Command, // Command mode overlay +} + +pub struct ModeManager; + +impl ModeManager { + // Determine current mode based on app state + pub fn derive_mode(app_state: &AppState, event_handler: &EventHandler) -> AppMode { + // Command mode takes precedence if active + if event_handler.command_mode { + return AppMode::Command; + } + + // Check UI state flags + if app_state.ui.show_intro || app_state.ui.show_admin { + AppMode::General + } else if app_state.ui.show_form { + if event_handler.is_edit_mode { + AppMode::Edit + } else { + AppMode::ReadOnly + } + } else { + // Fallback + AppMode::General + } + } + + // Mode transition rules + pub fn can_enter_command_mode(current_mode: AppMode) -> bool { + !matches!(current_mode, AppMode::Edit) // Can't enter from Edit mode + } + + pub fn can_enter_edit_mode(current_mode: AppMode) -> bool { + matches!(current_mode, AppMode::ReadOnly) // Only from ReadOnly + } + + pub fn can_enter_read_only_mode(current_mode: AppMode) -> bool { + matches!(current_mode, AppMode::Edit | AppMode::Command) + } +} diff --git a/client/src/state/state.rs b/client/src/state/state.rs index d6a0897..ce520ed 100644 --- a/client/src/state/state.rs +++ b/client/src/state/state.rs @@ -3,6 +3,7 @@ use std::env; use common::proto::multieko2::table_definition::ProfileTreeResponse; use crate::components::IntroState; +use crate::modes::handlers::mode_manager::AppMode; pub struct UiState { pub show_sidebar: bool, @@ -25,6 +26,7 @@ pub struct AppState { pub current_position: u64, pub profile_tree: ProfileTreeResponse, pub selected_profile: Option, + pub current_mode: AppMode, // UI preferences pub ui: UiState, @@ -42,6 +44,7 @@ impl AppState { current_position: 0, profile_tree: ProfileTreeResponse::default(), selected_profile: None, + current_mode: AppMode::General, ui: UiState::default(), general: GeneralState { selected_item: 0, @@ -58,6 +61,10 @@ impl AppState { pub fn update_current_position(&mut self, current_position: u64) { self.current_position = current_position; } + + pub fn update_mode(&mut self, mode: AppMode) { + self.current_mode = mode; + } } impl Default for UiState {