From b0c865ab7692d4b649458ce5f6f407d5ef402294 Mon Sep 17 00:00:00 2001 From: filipriec Date: Thu, 29 May 2025 19:46:58 +0200 Subject: [PATCH] workig suggestion menu --- client/src/components/common.rs | 2 + .../components/common/find_file_palette.rs | 116 +++++++++++++ client/src/ui/handlers/render.rs | 162 +++--------------- 3 files changed, 140 insertions(+), 140 deletions(-) create mode 100644 client/src/components/common/find_file_palette.rs diff --git a/client/src/components/common.rs b/client/src/components/common.rs index 1c26c28..57a242a 100644 --- a/client/src/components/common.rs +++ b/client/src/components/common.rs @@ -5,6 +5,7 @@ pub mod text_editor; pub mod background; pub mod dialog; pub mod autocomplete; +pub mod find_file_palette; pub use command_line::*; pub use status_line::*; @@ -12,3 +13,4 @@ pub use text_editor::*; pub use background::*; pub use dialog::*; pub use autocomplete::*; +pub use find_file_palette::*; diff --git a/client/src/components/common/find_file_palette.rs b/client/src/components/common/find_file_palette.rs new file mode 100644 index 0000000..451cdf6 --- /dev/null +++ b/client/src/components/common/find_file_palette.rs @@ -0,0 +1,116 @@ +// src/components/common/find_file_palette.rs + +use crate::config::colors::themes::Theme; +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect}, + style::Style, + widgets::{Block, List, ListItem, Paragraph}, + Frame, +}; +use unicode_width::UnicodeWidthStr; + +// Define a fixed height for the options list in the command palette +const PALETTE_MAX_VISIBLE_OPTIONS: usize = 15; +// Use a regular space character for padding. +const PADDING_CHAR: &str = " "; + +pub fn render_find_file_palette( + f: &mut Frame, + area: Rect, + theme: &Theme, + palette_input: &str, // Specific input for the palette + options: &[String], // These are already filtered options + selected_index: Option, // Index within the filtered `options` +) { + let num_total_filtered = options.len(); + let current_selected_list_idx = selected_index; + + let mut display_start_offset = 0; + if num_total_filtered > PALETTE_MAX_VISIBLE_OPTIONS { + if let Some(sel_idx) = current_selected_list_idx { + if sel_idx >= display_start_offset + PALETTE_MAX_VISIBLE_OPTIONS { + display_start_offset = sel_idx - PALETTE_MAX_VISIBLE_OPTIONS + 1; + } else if sel_idx < display_start_offset { + display_start_offset = sel_idx; + } + display_start_offset = display_start_offset + .min(num_total_filtered.saturating_sub(PALETTE_MAX_VISIBLE_OPTIONS)); + } + } + display_start_offset = display_start_offset.max(0); + + let display_end_offset = + (display_start_offset + PALETTE_MAX_VISIBLE_OPTIONS).min(num_total_filtered); + + let visible_options_slice = if num_total_filtered > 0 { + &options[display_start_offset..display_end_offset] + } else { + &[] + }; + + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), // For palette input line + Constraint::Length(PALETTE_MAX_VISIBLE_OPTIONS as u16), // For options list (fixed height) + ]) + .split(area); + + let input_area = chunks[0]; + let list_area = chunks[1]; + + // Draw the palette input line (with padding) + let base_prompt_text = format!("Find File: {}", palette_input); + let prompt_text_width = UnicodeWidthStr::width(base_prompt_text.as_str()); + let input_area_width = input_area.width as usize; + let input_padding_needed = input_area_width.saturating_sub(prompt_text_width); + + let padded_prompt_text = if input_padding_needed > 0 { + format!("{}{}", base_prompt_text, PADDING_CHAR.repeat(input_padding_needed)) + } else { + base_prompt_text + }; + + let input_paragraph = Paragraph::new(padded_prompt_text) + .style(Style::default().fg(theme.accent).bg(theme.bg)); + f.render_widget(input_paragraph, input_area); + + // --- Draw the list of options, ensuring all PALETTE_MAX_VISIBLE_OPTIONS rows are covered --- + let mut display_list_items: Vec = Vec::with_capacity(PALETTE_MAX_VISIBLE_OPTIONS); + + for (idx_in_slice, opt_str) in visible_options_slice.iter().enumerate() { + let original_list_idx = display_start_offset + idx_in_slice; + let is_selected = current_selected_list_idx == Some(original_list_idx); + + let style = if is_selected { + Style::default().fg(theme.bg).bg(theme.accent) + } else { + Style::default().fg(theme.fg).bg(theme.bg) + }; + + let opt_width = opt_str.width() as u16; + let list_item_width = list_area.width; + let padding_amount = list_item_width.saturating_sub(opt_width); + let padded_opt_str = format!( + "{}{}", + opt_str, + PADDING_CHAR.repeat(padding_amount as usize) + ); + display_list_items.push(ListItem::new(padded_opt_str).style(style)); + } + + let num_rendered_options = display_list_items.len(); + if num_rendered_options < PALETTE_MAX_VISIBLE_OPTIONS { + for _ in num_rendered_options..PALETTE_MAX_VISIBLE_OPTIONS { + let empty_padded_str = PADDING_CHAR.repeat(list_area.width as usize); + display_list_items.push( + ListItem::new(empty_padded_str) + .style(Style::default().fg(theme.bg).bg(theme.bg)), + ); + } + } + + let options_list_widget = List::new(display_list_items) + .block(Block::default().style(Style::default().bg(theme.bg))); + f.render_widget(options_list_widget, list_area); +} diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index ad8394f..4c265bc 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -11,15 +11,14 @@ use crate::components::{ admin::render_add_table, admin::add_logic::render_add_logic, auth::{login::render_login, register::render_register}, + common::find_file_palette, // Add this import }; use crate::config::colors::themes::Theme; use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::Style, - widgets::{Block, List, ListItem, Paragraph}, Frame, }; -use unicode_width::UnicodeWidthStr; // Needed for width calculation use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::form::FormState; use crate::state::pages::auth::AuthState; @@ -27,119 +26,11 @@ use crate::state::pages::auth::LoginState; use crate::state::pages::auth::RegisterState; use crate::state::pages::intro::IntroState; use crate::state::app::buffer::BufferState; -use crate::state::app::state::AppState; // AppState is needed for app_state.ui checks +use crate::state::app::state::AppState; use crate::state::pages::admin::AdminState; use crate::state::app::highlight::HighlightState; use crate::modes::general::command_navigation::NavigationState; -// Define a fixed height for the options list in the command palette -const PALETTE_MAX_VISIBLE_OPTIONS: usize = 15; - -// ++ New function to render the Find File Palette ++ -fn render_find_file_palette( - f: &mut Frame, - area: Rect, - theme: &Theme, - palette_input: &str, // Specific input for the palette - options: &[String], // These are already filtered options - selected_index: Option, // Index within the filtered `options` -) { - // Use a regular space character for padding. - const PADDING_CHAR: &str = " "; - - let num_total_filtered = options.len(); - let current_selected_list_idx = selected_index; - - let mut display_start_offset = 0; - if num_total_filtered > PALETTE_MAX_VISIBLE_OPTIONS { - if let Some(sel_idx) = current_selected_list_idx { - if sel_idx >= display_start_offset + PALETTE_MAX_VISIBLE_OPTIONS { - display_start_offset = sel_idx - PALETTE_MAX_VISIBLE_OPTIONS + 1; - } else if sel_idx < display_start_offset { - display_start_offset = sel_idx; - } - display_start_offset = display_start_offset - .min(num_total_filtered.saturating_sub(PALETTE_MAX_VISIBLE_OPTIONS)); - } - } - display_start_offset = display_start_offset.max(0); - - let display_end_offset = - (display_start_offset + PALETTE_MAX_VISIBLE_OPTIONS).min(num_total_filtered); - - let visible_options_slice = if num_total_filtered > 0 { - &options[display_start_offset..display_end_offset] - } else { - &[] - }; - - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(1), // For palette input line - Constraint::Length(PALETTE_MAX_VISIBLE_OPTIONS as u16), // For options list (fixed height) - ]) - .split(area); - - let input_area = chunks[0]; - let list_area = chunks[1]; - - // Draw the palette input line (with padding) - let base_prompt_text = format!("Find File: {}", palette_input); - let prompt_text_width = UnicodeWidthStr::width(base_prompt_text.as_str()); - let input_area_width = input_area.width as usize; - let input_padding_needed = input_area_width.saturating_sub(prompt_text_width); - - let padded_prompt_text = if input_padding_needed > 0 { - format!("{}{}", base_prompt_text, PADDING_CHAR.repeat(input_padding_needed)) - } else { - base_prompt_text // Or truncate if necessary: base_prompt_text.chars().take(input_area_width).collect() - }; - - let input_paragraph = Paragraph::new(padded_prompt_text) - .style(Style::default().fg(theme.accent).bg(theme.bg)); - f.render_widget(input_paragraph, input_area); - - // --- Draw the list of options, ensuring all PALETTE_MAX_VISIBLE_OPTIONS rows are covered --- - let mut display_list_items: Vec = Vec::with_capacity(PALETTE_MAX_VISIBLE_OPTIONS); - - for (idx_in_slice, opt_str) in visible_options_slice.iter().enumerate() { - let original_list_idx = display_start_offset + idx_in_slice; - let is_selected = current_selected_list_idx == Some(original_list_idx); - - let style = if is_selected { - Style::default().fg(theme.bg).bg(theme.accent) - } else { - Style::default().fg(theme.fg).bg(theme.bg) - }; - - let opt_width = opt_str.width() as u16; - let list_item_width = list_area.width; - let padding_amount = list_item_width.saturating_sub(opt_width); - let padded_opt_str = format!( - "{}{}", - opt_str, - PADDING_CHAR.repeat(padding_amount as usize) - ); - display_list_items.push(ListItem::new(padded_opt_str).style(style)); - } - - let num_rendered_options = display_list_items.len(); - if num_rendered_options < PALETTE_MAX_VISIBLE_OPTIONS { - for _ in num_rendered_options..PALETTE_MAX_VISIBLE_OPTIONS { - let empty_padded_str = PADDING_CHAR.repeat(list_area.width as usize); - display_list_items.push( - ListItem::new(empty_padded_str) - .style(Style::default().fg(theme.bg).bg(theme.bg)), - ); - } - } - - let options_list_widget = List::new(display_list_items) - .block(Block::default().style(Style::default().bg(theme.bg))); - f.render_widget(options_list_widget, list_area); -} - #[allow(clippy::too_many_arguments)] pub fn render_ui( f: &mut Frame, @@ -151,51 +42,47 @@ pub fn render_ui( admin_state: &mut AdminState, buffer_state: &BufferState, theme: &Theme, - // These come from EventHandler is_event_handler_edit_mode: bool, highlight_state: &HighlightState, - event_handler_command_input: &str, // For normal command line - event_handler_command_mode_active: bool, // Normal command line active? + event_handler_command_input: &str, + event_handler_command_mode_active: bool, event_handler_command_message: &str, - // Navigation state replaces find file palette specific state navigation_state: &NavigationState, - // General app state total_count: u64, current_position: u64, current_dir: &str, current_fps: f64, - app_state: &AppState, // Contains app_state.ui for layout decisions + app_state: &AppState, ) { render_background(f, f.area(), theme); - // Determine the height needed for the bottom bar (status + command/palette) - let mut bottom_area_constraints: Vec = vec![Constraint::Length(1)]; // Status line + const PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT: u16 = 15; // Matches component's internal const + + let mut bottom_area_constraints: Vec = vec![Constraint::Length(1)]; let command_palette_area_height = if navigation_state.active { - 1 + PALETTE_MAX_VISIBLE_OPTIONS as u16 // Input line + fixed height for options + 1 + PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT // Input line + fixed height for options } else if event_handler_command_mode_active { - 1 // Height for normal command line + 1 } else { - 0 // No command line or palette + 0 }; if command_palette_area_height > 0 { bottom_area_constraints.push(Constraint::Length(command_palette_area_height)); } - // Main layout: Buffer List (optional), Main Content, Bottom Area (Status + Command/Palette) - let mut main_layout_constraints = vec![Constraint::Min(1)]; // Main Content + let mut main_layout_constraints = vec![Constraint::Min(1)]; if app_state.ui.show_buffer_list { - main_layout_constraints.insert(0, Constraint::Length(1)); // Buffer List + main_layout_constraints.insert(0, Constraint::Length(1)); } - main_layout_constraints.extend(bottom_area_constraints); // Add status and command/palette + main_layout_constraints.extend(bottom_area_constraints); let root_chunks = Layout::default() .direction(Direction::Vertical) .constraints(main_layout_constraints) .split(f.area()); - // Assign areas let mut chunk_idx = 0; let buffer_list_area = if app_state.ui.show_buffer_list { chunk_idx += 1; @@ -210,7 +97,7 @@ pub fn render_ui( let status_line_area = root_chunks[chunk_idx]; chunk_idx += 1; - let mut command_render_area = None; // For normal command line or palette + let mut command_render_area = None; if command_palette_area_height > 0 { if root_chunks.len() > chunk_idx { command_render_area = Some(root_chunks[chunk_idx]); @@ -223,13 +110,13 @@ pub fn render_ui( } else if app_state.ui.show_register { render_register( f, main_content_area, theme, register_state, app_state, - register_state.current_field() < 4, // Example condition + register_state.current_field() < 4, highlight_state, ); } else if app_state.ui.show_add_table { render_add_table( f, main_content_area, theme, app_state, &mut admin_state.add_table_state, - is_event_handler_edit_mode, // Or specific logic for add_table + is_event_handler_edit_mode, highlight_state, ); } else if app_state.ui.show_add_logic { @@ -240,7 +127,7 @@ pub fn render_ui( } else if app_state.ui.show_login { render_login( f, main_content_area, theme, login_state, app_state, - login_state.current_field() < 2, // Example condition + login_state.current_field() < 2, highlight_state, ); } else if app_state.ui.show_admin { @@ -261,11 +148,11 @@ pub fn render_ui( let form_render_area = if available_width >= 80 { Layout::default().direction(Direction::Horizontal) .constraints([Constraint::Min(0), Constraint::Length(80), Constraint::Min(0)]) - .split(main_content_area)[1] // Center in main_content_area if sidebar is off + .split(main_content_area)[1] } else { Layout::default().direction(Direction::Horizontal) .constraints([Constraint::Min(0), Constraint::Length(available_width.min(80)), Constraint::Min(0)]) - .split(form_actual_area)[1] // Use form_actual_area if sidebar is on + .split(form_actual_area)[1] }; let fields_vec: Vec<&str> = form_state.fields.iter().map(AsRef::as_ref).collect(); let values_vec: Vec<&String> = form_state.values.iter().collect(); @@ -275,32 +162,27 @@ pub fn render_ui( total_count, current_position, ); } - // --- End Render main content views --- - // Render buffer list if enabled if let Some(area) = buffer_list_area { if app_state.ui.show_buffer_list { render_buffer_list(f, area, theme, buffer_state, app_state); } } - // Render status line render_status_line(f, status_line_area, current_dir, theme, is_event_handler_edit_mode, current_fps); - // Render Find File Palette OR Normal Command Line if let Some(area) = command_render_area { if navigation_state.active { - render_find_file_palette( + // Call the new component + find_file_palette::render_find_file_palette( f, area, theme, &navigation_state.input, - // Pass the full filtered options to the palette renderer &navigation_state.filtered_options.iter().map(|(_, opt)| opt.clone()).collect::>(), navigation_state.selected_index, ); } else if event_handler_command_mode_active { - // Normal command line render_command_line( f, area,