// src/pages/admin_panel/add_logic/event.rs use anyhow::Result; use crate::config::binds::config::Config; use crate::movement::{move_focus, MovementAction}; use crate::pages::admin_panel::add_logic::nav::SaveLogicResultSender; use crate::pages::admin_panel::add_logic::state::{AddLogicFocus, AddLogicFormState}; use crate::components::common::text_editor::TextEditor; use crate::services::grpc_client::GrpcClient; use crate::state::app::state::AppState; use crate::modes::handlers::event::EventOutcome; use canvas::{AppMode as CanvasMode, DataProvider}; use crossterm::event::KeyEvent; /// Focus traversal order for non-canvas navigation const ADD_LOGIC_FOCUS_ORDER: [AddLogicFocus; 6] = [ AddLogicFocus::InputLogicName, AddLogicFocus::InputTargetColumn, AddLogicFocus::InputDescription, AddLogicFocus::ScriptContentPreview, AddLogicFocus::SaveButton, AddLogicFocus::CancelButton, ]; /// Handles all AddLogic page-specific events. /// Return a non-empty Ok(message) only when the page actually consumed the key, /// otherwise return Ok("") to let global handling proceed. pub fn handle_add_logic_event( key_event: KeyEvent, movement: Option, config: &Config, app_state: &mut AppState, add_logic_page: &mut AddLogicFormState, grpc_client: GrpcClient, save_logic_sender: SaveLogicResultSender, ) -> Result { // 1) Script editor fullscreen mode if add_logic_page.state.current_focus == AddLogicFocus::InsideScriptContent { match key_event.code { crossterm::event::KeyCode::Esc => { add_logic_page.state.current_focus = AddLogicFocus::ScriptContentPreview; add_logic_page.focus_outside_canvas = true; return Ok(EventOutcome::Ok("Exited script editing.".to_string())); } _ => { let changed = { let mut editor_borrow = add_logic_page.state.script_content_editor.borrow_mut(); TextEditor::handle_input( &mut editor_borrow, key_event, &add_logic_page.state.editor_keybinding_mode, &mut add_logic_page.state.vim_state, ) }; if changed { add_logic_page.state.has_unsaved_changes = true; return Ok(EventOutcome::Ok("Script updated".to_string())); } return Ok(EventOutcome::Ok(String::new())); } } } // 2) Inside canvas: forward to FormEditor let inside_canvas_inputs = matches!( add_logic_page.state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription ); if inside_canvas_inputs { // Only allow leaving the canvas with Down/Next when the form editor // is in ReadOnly mode. In Edit mode, keep focus inside the canvas. let in_edit_mode = add_logic_page.editor.mode() == CanvasMode::Edit; if !in_edit_mode { if let Some(ma) = movement { let last_idx = add_logic_page .editor .data_provider() .field_count() .saturating_sub(1); let at_last = add_logic_page.editor.current_field() >= last_idx; if at_last && matches!(ma, MovementAction::Down | MovementAction::Next) { add_logic_page.state.last_canvas_field = last_idx; add_logic_page.state.current_focus = AddLogicFocus::ScriptContentPreview; add_logic_page.focus_outside_canvas = true; return Ok(EventOutcome::Ok("Moved to Script Preview".to_string())); } } } match add_logic_page.handle_key_event(key_event) { canvas::keymap::KeyEventOutcome::Consumed(Some(msg)) => { add_logic_page.sync_from_editor(); return Ok(EventOutcome::Ok(msg)); } canvas::keymap::KeyEventOutcome::Consumed(None) => { add_logic_page.sync_from_editor(); return Ok(EventOutcome::Ok("Input updated".into())); } canvas::keymap::KeyEventOutcome::Pending => { return Ok(EventOutcome::Ok(String::new())); } canvas::keymap::KeyEventOutcome::NotMatched => { // fall through } } } // 3) Outside canvas if let Some(ma) = movement { let mut current = add_logic_page.state.current_focus; if move_focus(&ADD_LOGIC_FOCUS_ORDER, &mut current, ma) { add_logic_page.state.current_focus = current; add_logic_page.focus_outside_canvas = !matches!( add_logic_page.state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription ); return Ok(EventOutcome::Ok(String::new())); } match ma { MovementAction::Select => match add_logic_page.state.current_focus { AddLogicFocus::ScriptContentPreview => { add_logic_page.state.current_focus = AddLogicFocus::InsideScriptContent; add_logic_page.focus_outside_canvas = false; return Ok(EventOutcome::Ok( "Fullscreen script editing. Esc to exit.".to_string(), )); } AddLogicFocus::SaveButton => { if let Some(msg) = add_logic_page.state.save_logic() { return Ok(EventOutcome::Ok(msg)); } else { return Ok(EventOutcome::Ok("Saved (no changes)".to_string())); } } AddLogicFocus::CancelButton => { return Ok(EventOutcome::Ok("Cancelled Add Logic".to_string())); } _ => {} }, MovementAction::Esc => { if add_logic_page.state.current_focus == AddLogicFocus::ScriptContentPreview { add_logic_page.state.current_focus = AddLogicFocus::InputDescription; add_logic_page.focus_outside_canvas = false; return Ok(EventOutcome::Ok("Back to Description".to_string())); } } _ => {} } } Ok(EventOutcome::Ok(String::new())) }