workig suggestion menu
This commit is contained in:
@@ -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::*;
|
||||
|
||||
116
client/src/components/common/find_file_palette.rs
Normal file
116
client/src/components/common/find_file_palette.rs
Normal file
@@ -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<usize>, // 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<ListItem> = 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);
|
||||
}
|
||||
@@ -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<usize>, // 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<ListItem> = 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<Constraint> = 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<Constraint> = 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::<Vec<_>>(),
|
||||
navigation_state.selected_index,
|
||||
);
|
||||
} else if event_handler_command_mode_active {
|
||||
// Normal command line
|
||||
render_command_line(
|
||||
f,
|
||||
area,
|
||||
|
||||
Reference in New Issue
Block a user