// src/pages/admin_panel/add_logic/ui.rs use crate::config::colors::themes::Theme; use crate::state::app::state::AppState; use crate::pages::admin_panel::add_logic::state::{AddLogicFocus, AddLogicState, AddLogicFormState}; use canvas::{render_canvas, render_suggestions_dropdown, DefaultCanvasTheme, FormEditor}; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Modifier, Style}, text::{Line, Span}, widgets::{Block, BorderType, Borders, Paragraph}, Frame, }; use crate::components::common::autocomplete; use crate::dialog; use crate::config::binds::config::EditorKeybindingMode; pub fn render_add_logic( f: &mut Frame, area: Rect, theme: &Theme, app_state: &AppState, add_logic_state: &mut AddLogicFormState, ) { let main_block = Block::default() .title(" Add New Logic Script ") .title_alignment(Alignment::Center) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(theme.border)) .style(Style::default().bg(theme.bg)); let inner_area = main_block.inner(area); f.render_widget(main_block, area); // Handle full-screen script editing if add_logic_state.current_focus() == AddLogicFocus::InsideScriptContent { let mut editor_ref = add_logic_state .state .script_content_editor .borrow_mut(); let border_style_color = if crate::components::common::text_editor::TextEditor::is_vim_insert_mode(add_logic_state.vim_state()) { theme.highlight } else { theme.secondary }; let border_style = Style::default().fg(border_style_color); editor_ref.set_cursor_line_style(Style::default()); editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); let script_title_hint = match add_logic_state.editor_keybinding_mode() { EditorKeybindingMode::Vim => { let vim_mode_status = crate::components::common::text_editor::TextEditor::get_vim_mode_status(add_logic_state.vim_state()); format!("Script {}", vim_mode_status) } EditorKeybindingMode::Emacs | EditorKeybindingMode::Default => { if crate::components::common::text_editor::TextEditor::is_vim_insert_mode(add_logic_state.vim_state()) { "Script (Editing)".to_string() } else { "Script".to_string() } } }; editor_ref.set_block( Block::default() .title(Span::styled(script_title_hint, Style::default().fg(theme.fg))) .title_alignment(Alignment::Center) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(border_style), ); f.render_widget(&*editor_ref, inner_area); // Drop the editor borrow before accessing autocomplete state drop(editor_ref); // === SCRIPT EDITOR AUTOCOMPLETE RENDERING === if add_logic_state.script_editor_autocomplete_active() && !add_logic_state.script_editor_suggestions().is_empty() { // Get the current cursor position from textarea let current_cursor = { let editor_borrow = add_logic_state.script_content_editor().borrow(); editor_borrow.cursor() // Returns (row, col) as (usize, usize) }; let (cursor_line, cursor_col) = current_cursor; // Account for TextArea's block borders (1 for each side) let block_offset_x = 1; let block_offset_y = 1; // Position autocomplete at current cursor position // Add 1 to column to position dropdown right after the cursor let autocomplete_x = cursor_col + 1; let autocomplete_y = cursor_line; let input_rect = Rect { x: (inner_area.x + block_offset_x + autocomplete_x as u16).min(inner_area.right().saturating_sub(20)), y: (inner_area.y + block_offset_y + autocomplete_y as u16).min(inner_area.bottom().saturating_sub(5)), width: 1, // Minimum width for positioning height: 1, }; // Render autocomplete dropdown autocomplete::render_autocomplete_dropdown( f, input_rect, f.area(), // Full frame area for clamping theme, add_logic_state.script_editor_suggestions(), add_logic_state.script_editor_selected_suggestion_index(), ); } return; // Exit early for fullscreen mode } // Regular layout with preview let main_chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(3), // Top info Constraint::Length(9), // Canvas for 3 inputs (each 1 line + 1 padding = 2 lines * 3 + 2 border = 8, +1 for good measure) Constraint::Min(5), // Script preview Constraint::Length(3), // Buttons ]) .split(inner_area); let top_info_area = main_chunks[0]; let canvas_area = main_chunks[1]; let script_content_area = main_chunks[2]; let buttons_area = main_chunks[3]; // Top info let table_label = if let Some(name) = add_logic_state.selected_table_name() { name.clone() } else if let Some(id) = add_logic_state.selected_table_id() { format!("ID {}", id) } else { "Global (Not Selected)".to_string() }; let profile_text = Paragraph::new(vec![ Line::from(Span::styled( format!("Profile: {}", add_logic_state.profile_name()), Style::default().fg(theme.fg), )), Line::from(Span::styled( format!("Table: {}", table_label), Style::default().fg(theme.fg), )), ]) .block( Block::default() .borders(Borders::BOTTOM) .border_style(Style::default().fg(theme.secondary)), ); f.render_widget(profile_text, top_info_area); // Canvas - USING CANVAS LIBRARY let focus_on_canvas_inputs = matches!( add_logic_state.current_focus(), AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription ); let editor = &add_logic_state.editor; let active_field_rect = render_canvas(f, canvas_area, editor, theme); // --- Canvas suggestions dropdown (Target Column, etc.) --- if editor.mode() == canvas::AppMode::Edit { if let Some(input_rect) = active_field_rect { render_suggestions_dropdown( f, f.area(), input_rect, &DefaultCanvasTheme, editor, ); } } // Script content preview { let mut editor_ref = add_logic_state.script_content_editor().borrow_mut(); editor_ref.set_cursor_line_style(Style::default()); let is_script_preview_focused = add_logic_state.current_focus() == AddLogicFocus::ScriptContentPreview; if is_script_preview_focused { editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); } else { let underscore_cursor_style = Style::default() .add_modifier(Modifier::UNDERLINED) .fg(theme.secondary); editor_ref.set_cursor_style(underscore_cursor_style); } let border_style_color = if is_script_preview_focused { theme.highlight } else { theme.secondary }; let title_text = "Script Preview"; // Title doesn't need to change based on focus here let title_style = if is_script_preview_focused { Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD) } else { Style::default().fg(theme.fg) }; editor_ref.set_block( Block::default() .title(Span::styled(title_text, title_style)) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(border_style_color)), ); f.render_widget(&*editor_ref, script_content_area); } // Buttons let get_button_style = |button_focus: AddLogicFocus, current_focus_state: AddLogicFocus| { let is_focused = current_focus_state == button_focus; let base_style = Style::default().fg(if is_focused { theme.highlight } else { theme.secondary }); if is_focused { base_style.add_modifier(Modifier::BOLD) } else { base_style } }; let get_button_border_style = |is_focused: bool, current_theme: &Theme| { if is_focused { Style::default().fg(current_theme.highlight) } else { Style::default().fg(current_theme.secondary) } }; let button_chunks = Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Percentage(50), Constraint::Percentage(50), ]) .split(buttons_area); let save_button = Paragraph::new(" Save Logic ") .style(get_button_style( AddLogicFocus::SaveButton, add_logic_state.current_focus(), )) .alignment(Alignment::Center) .block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(get_button_border_style( add_logic_state.current_focus() == AddLogicFocus::SaveButton, theme, )), ); f.render_widget(save_button, button_chunks[0]); let cancel_button = Paragraph::new(" Cancel ") .style(get_button_style( AddLogicFocus::CancelButton, add_logic_state.current_focus(), )) .alignment(Alignment::Center) .block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(get_button_border_style( add_logic_state.current_focus() == AddLogicFocus::CancelButton, theme, )), ); f.render_widget(cancel_button, button_chunks[1]); // Dialog if app_state.ui.dialog.dialog_show { dialog::render_dialog( f, f.area(), theme, &app_state.ui.dialog.dialog_title, &app_state.ui.dialog.dialog_message, &app_state.ui.dialog.dialog_buttons, app_state.ui.dialog.dialog_active_button_index, app_state.ui.dialog.is_loading, ); } }