we compiled multi canvas, needs so much work to do now

This commit is contained in:
filipriec
2025-05-23 21:59:17 +02:00
parent 0bc88a4b7a
commit 5c87168fb9
6 changed files with 503 additions and 332 deletions

View File

@@ -3,16 +3,21 @@ use crate::config::colors::themes::Theme;
use crate::state::app::highlight::HighlightState; use crate::state::app::highlight::HighlightState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState}; use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState};
use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::canvas_state::CanvasState; // For the top 3 fields
use ratatui::{ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Modifier, Style}, style::{Modifier, Style},
text::{Line, Span, Text}, text::{Line, Span},
widgets::{Block, BorderType, Borders, Paragraph}, widgets::{Block, BorderType, Borders, Paragraph},
Frame, Frame,
}; };
use crate::components::handlers::canvas::render_canvas; use crate::components::handlers::canvas::render_canvas as render_single_line_canvas; // Alias for clarity
use crate::components::common::dialog; use crate::components::common::dialog;
// Use your existing multiline renderer
use crate::components::handlers::multi_canvas::render_multiline_editor;
// We also need the trait for the renderer's signature
use crate::state::pages::multi_canvas_state::MultilineEditorState;
pub fn render_add_logic( pub fn render_add_logic(
f: &mut Frame, f: &mut Frame,
@@ -33,7 +38,6 @@ pub fn render_add_logic(
let inner_area = main_block.inner(area); let inner_area = main_block.inner(area);
f.render_widget(main_block, area); f.render_widget(main_block, area);
// Calculate areas dynamically like add_table
let main_chunks = Layout::default() let main_chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
@@ -49,19 +53,21 @@ pub fn render_add_logic(
let script_content_area = main_chunks[2]; let script_content_area = main_chunks[2];
let buttons_area = main_chunks[3]; let buttons_area = main_chunks[3];
// Top Info Rendering (like add_table) // Top Info Rendering
let profile_text = Paragraph::new(vec![ let profile_text = Paragraph::new(vec![
Line::from(Span::styled( Line::from(Span::styled(
format!("Profile: {}", add_logic_state.profile_name), format!("Profile: {}", add_logic_state.profile_name),
theme.fg, Style::default().fg(theme.fg),
)), )),
Line::from(Span::styled( Line::from(Span::styled(
format!("Table: {}", format!(
add_logic_state.selected_table_id "Table: {}",
add_logic_state
.selected_table_id
.map(|id| format!("ID {}", id)) .map(|id| format!("ID {}", id))
.unwrap_or_else(|| "Global".to_string()) .unwrap_or_else(|| "Global".to_string())
), ),
theme.fg, Style::default().fg(theme.fg),
)), )),
]) ])
.block( .block(
@@ -71,47 +77,43 @@ pub fn render_add_logic(
); );
f.render_widget(profile_text, top_info_area); f.render_widget(profile_text, top_info_area);
// Canvas rendering for input fields (like add_table) // Single-line canvas rendering for top input fields
let focus_on_canvas_inputs = matches!( let focus_on_single_line_canvas_inputs = matches!(
add_logic_state.current_focus, add_logic_state.current_focus,
AddLogicFocus::InputLogicName AddLogicFocus::InputLogicName
| AddLogicFocus::InputTargetColumn | AddLogicFocus::InputTargetColumn
| AddLogicFocus::InputDescription | AddLogicFocus::InputDescription
); );
render_canvas( render_single_line_canvas(
f, f,
canvas_area, canvas_area,
add_logic_state, add_logic_state, // Implements CanvasState for these fields
&add_logic_state.fields(), &add_logic_state.fields(),
&add_logic_state.current_field(), &add_logic_state.current_field(),
&add_logic_state.inputs(), &add_logic_state.inputs(),
theme, theme,
is_edit_mode && focus_on_canvas_inputs, is_edit_mode && focus_on_single_line_canvas_inputs,
highlight_state, highlight_state,
); );
// Script Content Area // --- Script Content Area using your MultilineEditor ---
let script_block_border_style = if add_logic_state.current_focus == AddLogicFocus::InputScriptContent { let is_script_editor_focused =
Style::default().fg(theme.highlight) add_logic_state.current_focus == AddLogicFocus::InputScriptContent;
} else {
Style::default().fg(theme.secondary)
};
let script_block = Block::default() // Pass the script_editor field which implements MultilineEditorState
.title(" Steel Script Content ") // The `render_multiline_editor` from canvas_multi.rs takes `&impl MultilineEditorState`
.borders(Borders::ALL) render_multiline_editor(
.border_type(BorderType::Rounded) f,
.border_style(script_block_border_style); script_content_area,
&add_logic_state.script_editor, // Pass your BasicMultilineEditor instance
theme,
is_script_editor_focused && is_edit_mode, // Editor is active for input
highlight_state, // Pass the global highlight state
);
let script_text = Text::from(add_logic_state.script_content_input.as_str());
let script_paragraph = Paragraph::new(script_text)
.block(script_block)
.scroll(add_logic_state.script_content_scroll)
.style(Style::default().fg(theme.fg));
f.render_widget(script_paragraph, script_content_area);
// Button Style Helpers (same as add_table) // Button Style Helpers
let get_button_style = |button_focus: AddLogicFocus, current_focus| { let get_button_style = |button_focus: AddLogicFocus, current_focus| {
let is_focused = current_focus == button_focus; let is_focused = current_focus == button_focus;
let base_style = Style::default().fg(if is_focused { let base_style = Style::default().fg(if is_focused {
@@ -134,12 +136,12 @@ pub fn render_add_logic(
} }
}; };
// Bottom Buttons (same style as add_table) // Bottom Buttons
let button_chunks = Layout::default() let button_chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([ .constraints([
Constraint::Percentage(50), // Save Button Constraint::Percentage(50),
Constraint::Percentage(50), // Cancel Button Constraint::Percentage(50),
]) ])
.split(buttons_area); .split(buttons_area);
@@ -177,7 +179,7 @@ pub fn render_add_logic(
); );
f.render_widget(cancel_button, button_chunks[1]); f.render_widget(cancel_button, button_chunks[1]);
// Dialog rendering (same as add_table) // Dialog rendering
if app_state.ui.dialog.dialog_show { if app_state.ui.dialog.dialog_show {
dialog::render_dialog( dialog::render_dialog(
f, f,
@@ -191,4 +193,3 @@ pub fn render_add_logic(
); );
} }
} }

View File

@@ -2,7 +2,9 @@
pub mod canvas; pub mod canvas;
pub mod sidebar; pub mod sidebar;
pub mod buffer_list; pub mod buffer_list;
pub mod multi_canvas;
pub use canvas::*; pub use canvas::*;
pub use sidebar::*; pub use sidebar::*;
pub use buffer_list::*; pub use buffer_list::*;
pub use multi_canvas::*;

View File

@@ -9,7 +9,7 @@ use ratatui::{
}; };
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
// Import the new trait // Import the new trait
use crate::state::pages::multiline_editor_state::MultilineEditorState; use crate::state::pages::multi_canvas_state::MultilineEditorState;
use crate::state::app::highlight::HighlightState; // Assuming this is your global highlight state use crate::state::app::highlight::HighlightState; // Assuming this is your global highlight state
use std::cmp::{min, max}; use std::cmp::{min, max};

View File

@@ -1,8 +1,11 @@
// src/functions/modes/edit/add_logic_e.rs // src/functions/modes/edit/add_logic_e.rs
use crate::state::pages::add_logic::AddLogicState; // Changed use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState}; // Added AddLogicFocus
use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::canvas_state::CanvasState;
use crossterm::event::{KeyCode, KeyEvent}; use crossterm::event::{KeyCode, KeyEvent};
use anyhow::Result; use anyhow::Result;
// Import the trait your BasicMultilineEditor implements
use crate::state::pages::multi_canvas_state::MultilineEditorState;
// Word navigation helpers (get_char_type, find_next_word_start, etc.) // Word navigation helpers (get_char_type, find_next_word_start, etc.)
// can be kept as they are generic. // can be kept as they are generic.
@@ -69,209 +72,279 @@ fn find_prev_word_end(text: &str, current_pos: usize) -> usize {
if pos > 0 { pos - 1 } else { 0 } if pos > 0 { pos - 1 } else { 0 }
} }
/// Executes edit actions for the AddLogic view canvas.
pub async fn execute_edit_action( pub async fn execute_edit_action(
action: &str, action: &str, // This 'action' string comes from keybindings for edit mode
key: KeyEvent, key: KeyEvent, // The raw key event, useful for Char input
state: &mut AddLogicState, // Changed state: &mut AddLogicState,
ideal_cursor_column: &mut usize, ideal_cursor_column: &mut usize, // Used for single-line canvas fields
) -> Result<String> { ) -> Result<String> {
match action { // Check if the current focus is on the script content editor
"insert_char" => { if state.current_focus == AddLogicFocus::InputScriptContent {
if let KeyCode::Char(c) = key.code { // Delegate to script editor's methods based on key or mapped action
match key.code {
KeyCode::Char(c) => {
// If action is "insert_char" or if no specific action mapped, treat as char input
if action == "insert_char" || action.is_empty() { // Assuming unmapped keys might default to char insert
state.script_editor.insert_char_at_cursor(c);
state.script_editor.set_has_unsaved_changes(true);
return Ok("".to_string());
}
}
KeyCode::Enter => {
state.script_editor.insert_newline_at_cursor();
state.script_editor.set_has_unsaved_changes(true);
return Ok("".to_string());
}
KeyCode::Backspace => {
state.script_editor.delete_char_before_cursor();
state.script_editor.set_has_unsaved_changes(true);
return Ok("".to_string());
}
KeyCode::Delete => {
state.script_editor.delete_char_at_cursor();
state.script_editor.set_has_unsaved_changes(true);
return Ok("".to_string());
}
KeyCode::Left => {
state.script_editor.move_cursor_left();
return Ok("".to_string());
}
KeyCode::Right => {
state.script_editor.move_cursor_right();
return Ok("".to_string());
}
KeyCode::Up => {
state.script_editor.move_cursor_up();
return Ok("".to_string());
}
KeyCode::Down => {
state.script_editor.move_cursor_down();
return Ok("".to_string());
}
// Home, End, PageUp, PageDown could be added here for the script_editor
// KeyCode::Home => { state.script_editor.move_cursor_to_line_start(); Ok("".to_string()) }
// KeyCode::End => { state.script_editor.move_cursor_to_line_end(); Ok("".to_string()) }
_ => {} // Let it fall through to action string matching if needed
}
// Handle actions specific to multiline editor that might be bound to other keys
match action {
"move_left" => { state.script_editor.move_cursor_left(); Ok("".to_string()) }
"move_right" => { state.script_editor.move_cursor_right(); Ok("".to_string()) }
"move_up" => { state.script_editor.move_cursor_up(); Ok("".to_string()) }
"move_down" => { state.script_editor.move_cursor_down(); Ok("".to_string()) }
// "next_field" and "prev_field" in script editor context might mean nothing,
// or could exit the editor focus. This is handled by the navigation logic.
// "exit_edit_mode" is also typically handled by the caller (mode_manager).
"exit_edit_mode" | "save" | "revert" | "next_field" | "prev_field" => {
Ok(format!("Action '{}' not directly handled by script editor, caller should manage.", action))
}
_ => Ok(format!("Unknown/unhandled edit action for script: {}", action)),
}
} else {
// Original logic for single-line canvas fields
match action {
"insert_char" => {
if let KeyCode::Char(c) = key.code {
let cursor_pos = state.current_cursor_pos();
let field_value = state.get_current_input_mut(); // This uses CanvasState
let mut chars: Vec<char> = field_value.chars().collect();
if cursor_pos <= chars.len() {
chars.insert(cursor_pos, c);
*field_value = chars.into_iter().collect();
state.set_current_cursor_pos(cursor_pos + 1);
state.set_has_unsaved_changes(true); // For single-line canvas
*ideal_cursor_column = state.current_cursor_pos();
}
} else {
return Ok("Error: insert_char called without a char key.".to_string());
}
Ok("".to_string())
}
"delete_char_backward" => {
if state.current_cursor_pos() > 0 {
let cursor_pos = state.current_cursor_pos();
let field_value = state.get_current_input_mut();
let mut chars: Vec<char> = field_value.chars().collect();
if cursor_pos <= chars.len() {
chars.remove(cursor_pos - 1);
*field_value = chars.into_iter().collect();
let new_pos = cursor_pos - 1;
state.set_current_cursor_pos(new_pos);
state.set_has_unsaved_changes(true);
*ideal_cursor_column = new_pos;
}
}
Ok("".to_string())
}
"delete_char_forward" => {
let cursor_pos = state.current_cursor_pos(); let cursor_pos = state.current_cursor_pos();
let field_value = state.get_current_input_mut(); let field_value = state.get_current_input_mut();
let mut chars: Vec<char> = field_value.chars().collect(); let mut chars: Vec<char> = field_value.chars().collect();
if cursor_pos <= chars.len() { if cursor_pos < chars.len() {
chars.insert(cursor_pos, c); chars.remove(cursor_pos);
*field_value = chars.into_iter().collect(); *field_value = chars.into_iter().collect();
state.set_current_cursor_pos(cursor_pos + 1);
state.set_has_unsaved_changes(true); state.set_has_unsaved_changes(true);
*ideal_cursor_column = state.current_cursor_pos(); *ideal_cursor_column = cursor_pos;
} }
} else { Ok("".to_string())
return Ok("Error: insert_char called without a char key.".to_string());
} }
Ok("".to_string()) "next_field" => { // For single-line canvas
} let num_fields = AddLogicState::INPUT_FIELD_COUNT;
"delete_char_backward" => { if num_fields > 0 {
if state.current_cursor_pos() > 0 { let current_field = state.current_field();
let cursor_pos = state.current_cursor_pos(); let last_field_index = num_fields - 1;
let field_value = state.get_current_input_mut(); if current_field < last_field_index {
let mut chars: Vec<char> = field_value.chars().collect(); state.set_current_field(current_field + 1);
if cursor_pos <= chars.len() { }
chars.remove(cursor_pos - 1); // else { // Optionally, if at last field, Tab could move to InputScriptContent
*field_value = chars.into_iter().collect(); // state.current_focus = AddLogicFocus::InputScriptContent;
let new_pos = cursor_pos - 1; // }
state.set_current_cursor_pos(new_pos); let current_input = state.get_current_input();
state.set_has_unsaved_changes(true); let max_pos = current_input.len();
*ideal_cursor_column = new_pos; state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
} }
Ok("".to_string())
} }
Ok("".to_string()) "prev_field" => { // For single-line canvas
} let num_fields = AddLogicState::INPUT_FIELD_COUNT;
"delete_char_forward" => { if num_fields > 0 {
let cursor_pos = state.current_cursor_pos(); let current_field = state.current_field();
let field_value = state.get_current_input_mut(); if current_field > 0 {
let mut chars: Vec<char> = field_value.chars().collect(); state.set_current_field(current_field - 1);
if cursor_pos < chars.len() { }
chars.remove(cursor_pos); // else { // Optionally, if at first field, Shift-Tab could move to CancelButton or similar
*field_value = chars.into_iter().collect(); // state.current_focus = AddLogicFocus::CancelButton; // Example
state.set_has_unsaved_changes(true); // }
*ideal_cursor_column = cursor_pos; let current_input = state.get_current_input();
} let max_pos = current_input.len();
Ok("".to_string()) state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
"next_field" => {
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
if num_fields > 0 {
let current_field = state.current_field();
let last_field_index = num_fields - 1;
if current_field < last_field_index { // Prevent cycling
state.set_current_field(current_field + 1);
} }
let current_input = state.get_current_input(); Ok("".to_string())
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
} }
Ok("".to_string()) "move_left" => {
} let new_pos = state.current_cursor_pos().saturating_sub(1);
"prev_field" => {
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
if num_fields > 0 {
let current_field = state.current_field();
if current_field > 0 { // Prevent cycling
state.set_current_field(current_field - 1);
}
let current_input = state.get_current_input();
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
Ok("".to_string())
}
"move_left" => {
let new_pos = state.current_cursor_pos().saturating_sub(1);
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
Ok("".to_string())
}
"move_right" => {
let current_input = state.get_current_input();
let current_pos = state.current_cursor_pos();
if current_pos < current_input.len() {
let new_pos = current_pos + 1;
state.set_current_cursor_pos(new_pos); state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos; *ideal_cursor_column = new_pos;
Ok("".to_string())
} }
Ok("".to_string()) "move_right" => {
}
"move_up" => { // In edit mode, up/down usually means prev/next field
let current_field = state.current_field();
if current_field > 0 {
let new_field = current_field - 1;
state.set_current_field(new_field);
let current_input = state.get_current_input(); let current_input = state.get_current_input();
let max_pos = current_input.len(); let current_pos = state.current_cursor_pos();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); if current_pos < current_input.len() {
let new_pos = current_pos + 1;
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
}
Ok("".to_string())
} }
Ok("".to_string()) "move_up" => { // In edit mode for single-line canvas, up/down usually means prev/next field
}
"move_down" => { // In edit mode, up/down usually means prev/next field
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
if num_fields > 0 {
let current_field = state.current_field(); let current_field = state.current_field();
let last_field_index = num_fields - 1; if current_field > 0 {
if current_field < last_field_index { let new_field = current_field - 1;
let new_field = current_field + 1;
state.set_current_field(new_field); state.set_current_field(new_field);
let current_input = state.get_current_input(); let current_input = state.get_current_input();
let max_pos = current_input.len(); let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
} }
Ok("".to_string())
} }
Ok("".to_string()) "move_down" => { // In edit mode for single-line canvas, up/down usually means prev/next field
} let num_fields = AddLogicState::INPUT_FIELD_COUNT;
"move_line_start" => { if num_fields > 0 {
state.set_current_cursor_pos(0); let current_field = state.current_field();
*ideal_cursor_column = 0; let last_field_index = num_fields - 1;
Ok("".to_string()) if current_field < last_field_index {
} let new_field = current_field + 1;
"move_line_end" => { state.set_current_field(new_field);
let current_input = state.get_current_input(); let current_input = state.get_current_input();
let new_pos = current_input.len(); let max_pos = current_input.len();
state.set_current_cursor_pos(new_pos); state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
*ideal_cursor_column = new_pos; }
Ok("".to_string()) }
} Ok("".to_string())
"move_first_line" => { }
if AddLogicState::INPUT_FIELD_COUNT > 0 { // Changed "move_line_start" => {
state.set_current_field(0); state.set_current_cursor_pos(0);
*ideal_cursor_column = 0;
Ok("".to_string())
}
"move_line_end" => {
let current_input = state.get_current_input(); let current_input = state.get_current_input();
let max_pos = current_input.len(); let new_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
Ok("".to_string())
}
"move_last_line" => {
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
if num_fields > 0 {
let new_field = num_fields - 1;
state.set_current_field(new_field);
let current_input = state.get_current_input();
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
Ok("".to_string())
}
"move_word_next" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let new_pos = find_next_word_start(current_input, state.current_cursor_pos());
let final_pos = new_pos.min(current_input.len());
state.set_current_cursor_pos(final_pos);
*ideal_cursor_column = final_pos;
}
Ok("".to_string())
}
"move_word_end" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let current_pos = state.current_cursor_pos();
let new_pos = find_word_end(current_input, current_pos);
let final_pos = if new_pos == current_pos && current_pos < current_input.len() { // Ensure not to go past end
find_word_end(current_input, current_pos + 1)
} else {
new_pos
};
let max_valid_index = current_input.len(); // Allow cursor at end
let clamped_pos = final_pos.min(max_valid_index);
state.set_current_cursor_pos(clamped_pos);
*ideal_cursor_column = clamped_pos;
}
Ok("".to_string())
}
"move_word_prev" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let new_pos = find_prev_word_start(current_input, state.current_cursor_pos());
state.set_current_cursor_pos(new_pos); state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos; *ideal_cursor_column = new_pos;
Ok("".to_string())
} }
Ok("".to_string()) "move_first_line" => { // For single-line canvas, effectively "first field"
} if AddLogicState::INPUT_FIELD_COUNT > 0 {
"move_word_end_prev" => { state.set_current_field(0);
let current_input = state.get_current_input(); let current_input = state.get_current_input();
if !current_input.is_empty() { let max_pos = current_input.len();
let new_pos = find_prev_word_end(current_input, state.current_cursor_pos()); state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
state.set_current_cursor_pos(new_pos); }
*ideal_cursor_column = new_pos; Ok("".to_string())
} }
Ok("".to_string()) "move_last_line" => { // For single-line canvas, effectively "last field"
let num_fields = AddLogicState::INPUT_FIELD_COUNT;
if num_fields > 0 {
let new_field = num_fields - 1;
state.set_current_field(new_field);
let current_input = state.get_current_input();
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
Ok("".to_string())
}
"move_word_next" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let new_pos = find_next_word_start(current_input, state.current_cursor_pos());
let final_pos = new_pos.min(current_input.len());
state.set_current_cursor_pos(final_pos);
*ideal_cursor_column = final_pos;
}
Ok("".to_string())
}
"move_word_end" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let current_pos = state.current_cursor_pos();
let new_pos = find_word_end(current_input, current_pos);
let final_pos = if new_pos == current_pos && current_pos < current_input.len() {
find_word_end(current_input, current_pos + 1)
} else {
new_pos
};
let max_valid_index = current_input.len();
let clamped_pos = final_pos.min(max_valid_index);
state.set_current_cursor_pos(clamped_pos);
*ideal_cursor_column = clamped_pos;
}
Ok("".to_string())
}
"move_word_prev" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let new_pos = find_prev_word_start(current_input, state.current_cursor_pos());
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
}
Ok("".to_string())
}
"move_word_end_prev" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let new_pos = find_prev_word_end(current_input, state.current_cursor_pos());
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
}
Ok("".to_string())
}
"exit_edit_mode" | "save" | "revert" => {
Ok("Action handled by main loop".to_string())
}
_ => Ok(format!("Unknown or unhandled edit action for single-line: {}", action)),
} }
"exit_edit_mode" | "save" | "revert" => {
Ok("Action handled by main loop".to_string())
}
_ => Ok(format!("Unknown or unhandled edit action: {}", action)),
} }
} }

View File

@@ -7,11 +7,13 @@ use crate::state::{
app::buffer::BufferState, app::buffer::BufferState,
}; };
use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::canvas_state::CanvasState;
use crossterm::event::{KeyEvent}; use crossterm::event::{KeyEvent, KeyCode}; // Added KeyCode here
use crate::services::GrpcClient; use crate::services::GrpcClient;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use anyhow::Result; use anyhow::Result;
use common::proto::multieko2::table_script::{PostTableScriptRequest}; use common::proto::multieko2::table_script::{PostTableScriptRequest};
// Import the trait your BasicMultilineEditor implements
use crate::state::pages::multi_canvas_state::MultilineEditorState;
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>; pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
@@ -29,57 +31,81 @@ pub fn handle_add_logic_navigation(
let action = config.get_general_action(key.code, key.modifiers).map(String::from); let action = config.get_general_action(key.code, key.modifiers).map(String::from);
let mut handled = false; let mut handled = false;
// Check if focus is on canvas input fields // Check if focus is on canvas input fields (single-line ones)
let focus_on_canvas_inputs = matches!( let _focus_on_canvas_inputs = matches!( // Renamed as it's not directly used below for multiline
add_logic_state.current_focus, add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
); );
// Handle script content editing separately (multiline) // Handle script content editing separately (multiline)
if *is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent { if *is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
// Use methods from MultilineEditorState trait
match key.code { match key.code {
crossterm::event::KeyCode::Char(c) => { KeyCode::Char(c) => {
add_logic_state.script_content_input.push(c); add_logic_state.script_editor.insert_char_at_cursor(c);
add_logic_state.has_unsaved_changes = true; add_logic_state.script_editor.set_has_unsaved_changes(true); // Mark editor as changed
// add_logic_state.has_unsaved_changes = true; // Optionally mark the whole page state
handled = true; handled = true;
} }
crossterm::event::KeyCode::Enter => { KeyCode::Enter => {
add_logic_state.script_content_input.push('\n'); add_logic_state.script_editor.insert_newline_at_cursor();
add_logic_state.has_unsaved_changes = true; add_logic_state.script_editor.set_has_unsaved_changes(true);
add_logic_state.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_add(1); // Scrolling is handled internally by the editor's state or renderer,
// or by dedicated scroll commands if you add them.
// We don't directly manipulate scroll here anymore.
handled = true; handled = true;
} }
crossterm::event::KeyCode::Backspace => { KeyCode::Backspace => {
if !add_logic_state.script_content_input.is_empty() { add_logic_state.script_editor.delete_char_before_cursor();
add_logic_state.script_content_input.pop(); add_logic_state.script_editor.set_has_unsaved_changes(true);
add_logic_state.has_unsaved_changes = true; handled = true;
handled = true;
}
} }
_ => {} // Add other key handling for the multiline editor as needed:
// KeyCode::Delete => {
// add_logic_state.script_editor.delete_char_at_cursor();
// add_logic_state.script_editor.set_has_unsaved_changes(true);
// handled = true;
// }
// KeyCode::Left => {
// add_logic_state.script_editor.move_cursor_left();
// handled = true;
// }
// KeyCode::Right => {
// add_logic_state.script_editor.move_cursor_right();
// handled = true;
// }
KeyCode::Up => { // In edit mode, up in multiline editor moves cursor up a line
add_logic_state.script_editor.move_cursor_up();
handled = true;
}
KeyCode::Down => { // In edit mode, down in multiline editor moves cursor down a line
add_logic_state.script_editor.move_cursor_down();
handled = true;
}
// Potentially PageUp, PageDown, Home, End etc.
_ => {} // Let other handlers or default behavior take over
} }
} }
if !handled { if !handled {
match action.as_deref() { match action.as_deref() {
Some("exit_view") | Some("cancel_action") => { Some("exit_view") | Some("cancel_action") => {
buffer_state.update_history(AppView::Admin); // Fixed: was AdminPanel buffer_state.update_history(AppView::Admin);
app_state.ui.focus_outside_canvas = true; app_state.ui.focus_outside_canvas = true;
*command_message = "Exited Add Logic".to_string(); *command_message = "Exited Add Logic".to_string();
handled = true; handled = true;
} }
Some("next_field") => { Some("next_field") => { // Tab
let previous_focus = add_logic_state.current_focus; let previous_focus = add_logic_state.current_focus;
add_logic_state.current_focus = match add_logic_state.current_focus { add_logic_state.current_focus = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn, AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn,
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription, AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription,
AddLogicFocus::InputDescription => AddLogicFocus::InputScriptContent, AddLogicFocus::InputDescription => AddLogicFocus::InputScriptContent,
AddLogicFocus::InputScriptContent => AddLogicFocus::SaveButton, AddLogicFocus::InputScriptContent => AddLogicFocus::SaveButton, // Move from script editor to Save
AddLogicFocus::SaveButton => AddLogicFocus::CancelButton, AddLogicFocus::SaveButton => AddLogicFocus::CancelButton,
AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName, AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName,
}; };
// Update canvas field index only when moving between canvas inputs
if matches!(previous_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn) { if matches!(previous_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn) {
if matches!(add_logic_state.current_focus, AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { if matches!(add_logic_state.current_focus, AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
let new_field = match add_logic_state.current_focus { let new_field = match add_logic_state.current_focus {
@@ -91,10 +117,9 @@ pub fn handle_add_logic_navigation(
} }
} }
// Update focus outside canvas flag
app_state.ui.focus_outside_canvas = !matches!( app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus, add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent
); );
*command_message = format!("Focus: {:?}", add_logic_state.current_focus); *command_message = format!("Focus: {:?}", add_logic_state.current_focus);
@@ -103,108 +128,125 @@ pub fn handle_add_logic_navigation(
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent); AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true; handled = true;
} }
Some("prev_field") => { Some("prev_field") => { // Shift-Tab
let previous_focus = add_logic_state.current_focus; let _previous_focus = add_logic_state.current_focus; // Keep for potential future use
add_logic_state.current_focus = match add_logic_state.current_focus { add_logic_state.current_focus = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton, AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton,
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName, AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName,
AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn, AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn,
AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription, AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription, // Move from script editor to Description
AddLogicFocus::SaveButton => AddLogicFocus::InputScriptContent, AddLogicFocus::SaveButton => AddLogicFocus::InputScriptContent,
AddLogicFocus::CancelButton => AddLogicFocus::SaveButton, AddLogicFocus::CancelButton => AddLogicFocus::SaveButton,
}; };
// Update canvas field index only when moving between canvas inputs // This logic for setting single-line canvas field might need adjustment
if matches!(previous_focus, AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { // if previous_focus was InputScriptContent
if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn) { if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
let new_field_index = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1,
AddLogicFocus::InputDescription => 2,
_ => add_logic_state.current_field(), // Should not happen
};
add_logic_state.set_current_field(new_field_index);
}
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true;
}
// "next_option" and "previous_option" (likely right/left arrow in non-edit mode)
// These typically navigate between focusable elements like buttons or cycle through fields.
// If current focus is InputScriptContent and not in edit mode, these might scroll the content.
Some("next_option") => {
if !*is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
// TODO: Implement horizontal scroll for BasicMultilineEditor if needed
// add_logic_state.script_editor.scroll_right();
*command_message = "Horizontal scroll right (not implemented for script editor)".to_string();
handled = true;
} else {
// Default horizontal navigation logic (copied from next_field for now, adjust as needed)
add_logic_state.current_focus = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn,
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription,
AddLogicFocus::InputDescription => AddLogicFocus::InputScriptContent,
AddLogicFocus::InputScriptContent => AddLogicFocus::SaveButton,
AddLogicFocus::SaveButton => AddLogicFocus::CancelButton,
AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName,
};
if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
let new_field = match add_logic_state.current_focus { let new_field = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => 0, AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1, AddLogicFocus::InputTargetColumn => 1,
_ => 0, AddLogicFocus::InputDescription => 2,
_ => add_logic_state.current_field(),
}; };
add_logic_state.set_current_field(new_field); add_logic_state.set_current_field(new_field);
} }
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true;
} }
// Update focus outside canvas flag
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true;
} }
Some("next_option") => { // Horizontal next Some("previous_option") => {
let previous_focus = add_logic_state.current_focus; if !*is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
add_logic_state.current_focus = match add_logic_state.current_focus { // TODO: Implement horizontal scroll for BasicMultilineEditor if needed
AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn, // add_logic_state.script_editor.scroll_left();
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription, *command_message = "Horizontal scroll left (not implemented for script editor)".to_string();
AddLogicFocus::InputDescription => AddLogicFocus::InputScriptContent, handled = true;
AddLogicFocus::InputScriptContent => AddLogicFocus::SaveButton, } else {
AddLogicFocus::SaveButton => AddLogicFocus::CancelButton, // Default horizontal navigation logic (copied from prev_field for now, adjust as needed)
AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName, // Cycle back add_logic_state.current_focus = match add_logic_state.current_focus {
}; AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton,
// Update canvas field index if moving within canvas inputs AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName,
if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn,
let new_field = match add_logic_state.current_focus { AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription,
AddLogicFocus::InputLogicName => 0, AddLogicFocus::SaveButton => AddLogicFocus::InputScriptContent,
AddLogicFocus::InputTargetColumn => 1, AddLogicFocus::CancelButton => AddLogicFocus::SaveButton,
AddLogicFocus::InputDescription => 2,
_ => add_logic_state.current_field(), // Should not happen
}; };
add_logic_state.set_current_field(new_field); if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
let new_field = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1,
AddLogicFocus::InputDescription => 2,
_ => add_logic_state.current_field(),
};
add_logic_state.set_current_field(new_field);
}
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true;
} }
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true;
} }
Some("previous_option") => { // Horizontal previous Some("select") => { // Enter key in navigation mode
let previous_focus = add_logic_state.current_focus;
add_logic_state.current_focus = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton, // Cycle back
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName,
AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn,
AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription,
AddLogicFocus::SaveButton => AddLogicFocus::InputScriptContent,
AddLogicFocus::CancelButton => AddLogicFocus::SaveButton,
};
// Update canvas field index if moving within canvas inputs
if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
let new_field = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1,
AddLogicFocus::InputDescription => 2,
_ => add_logic_state.current_field(), // Should not happen
};
add_logic_state.set_current_field(new_field);
}
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true;
}
Some("select") => {
match add_logic_state.current_focus { match add_logic_state.current_focus {
AddLogicFocus::SaveButton => { AddLogicFocus::SaveButton => {
if let Some(table_def_id) = add_logic_state.selected_table_id { if let Some(table_def_id) = add_logic_state.selected_table_id {
// Use the new get_script_content method
let script_content = add_logic_state.get_script_content();
if add_logic_state.target_column_input.trim().is_empty() { if add_logic_state.target_column_input.trim().is_empty() {
*command_message = "Cannot save: Target Column cannot be empty.".to_string(); *command_message = "Cannot save: Target Column cannot be empty.".to_string();
} else if add_logic_state.script_content_input.trim().is_empty() { } else if script_content.trim().is_empty() { // Check content from editor
*command_message = "Cannot save: Script Content cannot be empty.".to_string(); *command_message = "Cannot save: Script Content cannot be empty.".to_string();
} else { } else {
*command_message = "Saving logic script...".to_string(); *command_message = "Saving logic script...".to_string();
@@ -213,7 +255,7 @@ pub fn handle_add_logic_navigation(
let request = PostTableScriptRequest { let request = PostTableScriptRequest {
table_definition_id: table_def_id, table_definition_id: table_def_id,
target_column: add_logic_state.target_column_input.trim().to_string(), target_column: add_logic_state.target_column_input.trim().to_string(),
script: add_logic_state.script_content_input.trim().to_string(), script: script_content.trim().to_string(), // Use content from editor
description: add_logic_state.description_input.trim().to_string(), description: add_logic_state.description_input.trim().to_string(),
}; };
@@ -233,16 +275,19 @@ pub fn handle_add_logic_navigation(
handled = true; handled = true;
} }
AddLogicFocus::CancelButton => { AddLogicFocus::CancelButton => {
buffer_state.update_history(AppView::Admin); // Fixed: was AdminPanel buffer_state.update_history(AppView::Admin);
app_state.ui.focus_outside_canvas = true; app_state.ui.focus_outside_canvas = true;
*command_message = "Cancelled Add Logic".to_string(); *command_message = "Cancelled Add Logic".to_string();
handled = true; handled = true;
} }
// If Enter is pressed on an input field (including script content), switch to edit mode
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent => { AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent => {
if !*is_edit_mode { if !*is_edit_mode {
*is_edit_mode = true; *is_edit_mode = true;
*command_message = "Edit mode: ON".to_string(); *command_message = "Edit mode: ON".to_string();
// Ensure focus_outside_canvas is false when entering edit mode for any input
app_state.ui.focus_outside_canvas = false;
} }
handled = true; handled = true;
} }
@@ -251,17 +296,34 @@ pub fn handle_add_logic_navigation(
Some("toggle_edit_mode") => { Some("toggle_edit_mode") => {
*is_edit_mode = !*is_edit_mode; *is_edit_mode = !*is_edit_mode;
*command_message = format!("Edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" }); *command_message = format!("Edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" });
// When toggling edit mode, adjust focus_outside_canvas
if *is_edit_mode {
// If entering edit mode, and focus is on an input, ensure canvas has focus
if matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent) {
app_state.ui.focus_outside_canvas = false;
}
} else {
// If exiting edit mode, and focus was on an input, it might now be considered "outside"
// This depends on your desired navigation flow after exiting edit mode.
// For now, let's assume it stays on the element but is no longer editable.
// If you want focus to jump to a button, handle that explicitly.
}
handled = true; handled = true;
} }
// Handle script content scrolling when not in edit mode // Handle script content scrolling when not in edit mode using arrow keys
_ if !*is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent => { _ if !*is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent => {
// This specific block for action.as_deref() might be redundant if
// KeyCode::Up/Down are handled directly above for edit mode.
// However, if "move_up"/"move_down" are distinct actions in your config for non-edit mode scrolling:
match action.as_deref() { match action.as_deref() {
Some("move_up") => { Some("move_up") => {
add_logic_state.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_sub(1); add_logic_state.script_editor.move_cursor_up(); // Or a dedicated scroll_up method
handled = true; handled = true;
} }
Some("move_down") => { Some("move_down") => {
add_logic_state.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_add(1); add_logic_state.script_editor.move_cursor_down(); // Or a dedicated scroll_down method
handled = true; handled = true;
} }
_ => {} _ => {}
@@ -272,4 +334,3 @@ pub fn handle_add_logic_navigation(
} }
handled handled
} }

View File

@@ -1,12 +1,17 @@
// src/state/pages/add_logic.rs // src/state/pages/add_logic.rs
use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::canvas_state::CanvasState;
// Use your existing BasicMultilineEditor
use crate::state::pages::multi_canvas_state::BasicMultilineEditor;
// You might also need the trait if you pass it around as dyn MultilineEditorState
use crate::state::pages::multi_canvas_state::MultilineEditorState;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AddLogicFocus { pub enum AddLogicFocus {
#[default] #[default]
InputLogicName, InputLogicName,
InputTargetColumn, InputTargetColumn,
InputScriptContent, InputScriptContent, // This will now refer to the BasicMultilineEditor
InputDescription, InputDescription,
SaveButton, SaveButton,
CancelButton, CancelButton,
@@ -19,14 +24,16 @@ pub struct AddLogicState {
pub selected_table_name: Option<String>, pub selected_table_name: Option<String>,
pub logic_name_input: String, pub logic_name_input: String,
pub target_column_input: String, pub target_column_input: String,
pub script_content_input: String, // pub script_content_input: String, // Remove this
pub description_input: String, pub description_input: String,
pub current_focus: AddLogicFocus, pub current_focus: AddLogicFocus,
pub logic_name_cursor_pos: usize, pub logic_name_cursor_pos: usize,
pub target_column_cursor_pos: usize, pub target_column_cursor_pos: usize,
pub script_content_scroll: (u16, u16), // (vertical, horizontal) // pub script_content_scroll: (u16, u16), // Remove this
pub description_cursor_pos: usize, pub description_cursor_pos: usize,
pub has_unsaved_changes: bool, pub has_unsaved_changes: bool, // For the single-line canvas fields
// Add this field using your existing struct
pub script_editor: BasicMultilineEditor,
} }
impl Default for AddLogicState { impl Default for AddLogicState {
@@ -37,30 +44,49 @@ impl Default for AddLogicState {
selected_table_name: None, selected_table_name: None,
logic_name_input: String::new(), logic_name_input: String::new(),
target_column_input: String::new(), target_column_input: String::new(),
script_content_input: String::new(),
description_input: String::new(), description_input: String::new(),
current_focus: AddLogicFocus::InputLogicName, current_focus: AddLogicFocus::InputLogicName,
logic_name_cursor_pos: 0, logic_name_cursor_pos: 0,
target_column_cursor_pos: 0, target_column_cursor_pos: 0,
script_content_scroll: (0, 0),
description_cursor_pos: 0, description_cursor_pos: 0,
has_unsaved_changes: false, has_unsaved_changes: false,
// Initialize your BasicMultilineEditor
script_editor: BasicMultilineEditor::new(
None, // No initial content
Some("Steel Script Content".to_string()), // Optional label
),
} }
} }
} }
impl AddLogicState { impl AddLogicState {
// Number of canvas-editable fields // Number of canvas-editable fields (for the top single-line inputs)
pub const INPUT_FIELD_COUNT: usize = 3; // Logic Name, Target Column, Description pub const INPUT_FIELD_COUNT: usize = 3; // Logic Name, Target Column, Description
// Method to get script content from the editor
pub fn get_script_content(&self) -> String {
// Assuming BasicMultilineEditor will have a method to get all lines as a single string
// If not, we'll need to add it to BasicMultilineEditor or implement it here.
// For now, let's assume it's:
self.script_editor.lines().join("\n")
}
// Method to set script content in the editor (e.g., when loading to edit)
pub fn set_script_content(&mut self, content: &str) {
let lines: Vec<String> = content.lines().map(String::from).collect();
self.script_editor.set_lines(lines);
// self.script_editor.set_has_unsaved_changes(false); // If this is initial population
}
} }
// The CanvasState implementation remains for the single-line inputs.
impl CanvasState for AddLogicState { impl CanvasState for AddLogicState {
fn current_field(&self) -> usize { fn current_field(&self) -> usize {
match self.current_focus { match self.current_focus {
AddLogicFocus::InputLogicName => 0, AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1, AddLogicFocus::InputTargetColumn => 1,
AddLogicFocus::InputDescription => 2, AddLogicFocus::InputDescription => 2,
_ => 0, // Default or non-input focus _ => 0,
} }
} }
@@ -74,6 +100,10 @@ impl CanvasState for AddLogicState {
} }
fn has_unsaved_changes(&self) -> bool { fn has_unsaved_changes(&self) -> bool {
// This refers to the single-line fields.
// The BasicMultilineEditor has its own `has_unsaved_changes`.
// You might want a combined status:
// self.has_unsaved_changes || self.script_editor.has_unsaved_changes()
self.has_unsaved_changes self.has_unsaved_changes
} }
@@ -95,11 +125,12 @@ impl CanvasState for AddLogicState {
} }
fn get_current_input_mut(&mut self) -> &mut String { fn get_current_input_mut(&mut self) -> &mut String {
self.has_unsaved_changes = true;
match self.current_focus { match self.current_focus {
AddLogicFocus::InputLogicName => &mut self.logic_name_input, AddLogicFocus::InputLogicName => &mut self.logic_name_input,
AddLogicFocus::InputTargetColumn => &mut self.target_column_input, AddLogicFocus::InputTargetColumn => &mut self.target_column_input,
AddLogicFocus::InputDescription => &mut self.description_input, AddLogicFocus::InputDescription => &mut self.description_input,
_ => &mut self.logic_name_input, // Placeholder, should not be hit if focus is correct _ => &mut self.logic_name_input,
} }
} }
@@ -112,20 +143,23 @@ impl CanvasState for AddLogicState {
0 => AddLogicFocus::InputLogicName, 0 => AddLogicFocus::InputLogicName,
1 => AddLogicFocus::InputTargetColumn, 1 => AddLogicFocus::InputTargetColumn,
2 => AddLogicFocus::InputDescription, 2 => AddLogicFocus::InputDescription,
_ => self.current_focus, // Stay if out of bounds _ => self.current_focus,
}; };
} }
fn set_current_cursor_pos(&mut self, pos: usize) { fn set_current_cursor_pos(&mut self, pos: usize) {
match self.current_focus { match self.current_focus {
AddLogicFocus::InputLogicName => { AddLogicFocus::InputLogicName => {
self.logic_name_cursor_pos = pos.min(self.logic_name_input.len()); self.logic_name_cursor_pos =
pos.min(self.logic_name_input.chars().count());
} }
AddLogicFocus::InputTargetColumn => { AddLogicFocus::InputTargetColumn => {
self.target_column_cursor_pos = pos.min(self.target_column_input.len()); self.target_column_cursor_pos =
pos.min(self.target_column_input.chars().count());
} }
AddLogicFocus::InputDescription => { AddLogicFocus::InputDescription => {
self.description_cursor_pos = pos.min(self.description_input.len()); self.description_cursor_pos =
pos.min(self.description_input.chars().count());
} }
_ => {} _ => {}
} }