// src/suggestions/gui.rs //! Suggestions dropdown GUI (not inline autocomplete) updated to work with FormEditor #[cfg(feature = "gui")] use ratatui::{ layout::{Alignment, Rect}, style::{Modifier, Style}, widgets::{Block, List, ListItem, ListState, Paragraph}, // Removed Borders Frame, }; #[cfg(feature = "gui")] use crate::canvas::theme::CanvasTheme; use crate::data_provider::{DataProvider, SuggestionItem}; use crate::editor::FormEditor; #[cfg(feature = "gui")] use unicode_width::UnicodeWidthStr; /// Render suggestions dropdown for FormEditor - call this AFTER rendering canvas #[cfg(feature = "gui")] pub fn render_suggestions_dropdown( f: &mut Frame, frame_area: Rect, input_rect: Rect, theme: &T, editor: &FormEditor, ) { let ui_state = editor.ui_state(); if !ui_state.is_suggestions_active() { return; } if ui_state.suggestions.is_loading { render_loading_indicator(f, frame_area, input_rect, theme); } else if !editor.suggestions().is_empty() { render_suggestions_dropdown_list(f, frame_area, input_rect, theme, editor.suggestions(), ui_state.suggestions.selected_index); } } /// Show loading spinner/text #[cfg(feature = "gui")] fn render_loading_indicator( f: &mut Frame, frame_area: Rect, input_rect: Rect, theme: &T, ) { let loading_text = "Loading suggestions..."; let loading_width = loading_text.width() as u16 + 4; // +4 for borders and padding let loading_height = 3; let dropdown_area = calculate_dropdown_position( input_rect, frame_area, loading_width, loading_height, ); let loading_block = Block::default() .style(Style::default().bg(theme.bg())); let loading_paragraph = Paragraph::new(loading_text) .block(loading_block) .style(Style::default().fg(theme.fg())) .alignment(Alignment::Center); f.render_widget(loading_paragraph, dropdown_area); } /// Show actual suggestions list #[cfg(feature = "gui")] fn render_suggestions_dropdown_list( f: &mut Frame, frame_area: Rect, input_rect: Rect, theme: &T, suggestions: &[SuggestionItem], // Fixed: Removed generic parameter selected_index: Option, ) { let display_texts: Vec<&str> = suggestions .iter() .map(|item| item.display_text.as_str()) .collect(); let dropdown_dimensions = calculate_dropdown_dimensions(&display_texts); let dropdown_area = calculate_dropdown_position( input_rect, frame_area, dropdown_dimensions.width, dropdown_dimensions.height, ); // Background let dropdown_block = Block::default() .style(Style::default().bg(theme.bg())); // List items let items = create_suggestion_list_items( &display_texts, selected_index, dropdown_dimensions.width, theme, ); let list = List::new(items).block(dropdown_block); let mut list_state = ListState::default(); list_state.select(selected_index); f.render_stateful_widget(list, dropdown_area, &mut list_state); } /// Calculate dropdown size based on suggestions #[cfg(feature = "gui")] fn calculate_dropdown_dimensions(display_texts: &[&str]) -> DropdownDimensions { let max_width = display_texts .iter() .map(|text| text.width()) .max() .unwrap_or(0) as u16; let horizontal_padding = 2; let width = (max_width + horizontal_padding).max(10); let height = (display_texts.len() as u16).min(5); DropdownDimensions { width, height } } /// Position dropdown to stay in bounds #[cfg(feature = "gui")] fn calculate_dropdown_position( input_rect: Rect, frame_area: Rect, dropdown_width: u16, dropdown_height: u16, ) -> Rect { let mut dropdown_area = Rect { x: input_rect.x, y: input_rect.y + 1, // below input field width: dropdown_width, height: dropdown_height, }; // Keep in bounds if dropdown_area.bottom() > frame_area.height { dropdown_area.y = input_rect.y.saturating_sub(dropdown_height); } if dropdown_area.right() > frame_area.width { dropdown_area.x = frame_area.width.saturating_sub(dropdown_width); } dropdown_area.x = dropdown_area.x.max(0); dropdown_area.y = dropdown_area.y.max(0); dropdown_area } /// Create styled list items #[cfg(feature = "gui")] fn create_suggestion_list_items<'a, T: CanvasTheme>( display_texts: &'a [&'a str], selected_index: Option, dropdown_width: u16, theme: &T, ) -> Vec> { let available_width = dropdown_width; display_texts .iter() .enumerate() .map(|(i, text)| { let is_selected = selected_index == Some(i); let text_width = text.width() as u16; let padding_needed = available_width.saturating_sub(text_width); let padded_text = format!("{}{}", text, " ".repeat(padding_needed as usize)); ListItem::new(padded_text).style(if is_selected { Style::default() .fg(theme.bg()) .bg(theme.highlight()) .add_modifier(Modifier::BOLD) } else { Style::default().fg(theme.fg()).bg(theme.bg()) }) }) .collect() } /// Helper struct for dropdown dimensions #[cfg(feature = "gui")] struct DropdownDimensions { width: u16, height: u16, }