overlays.rs
1 use crate::app::{App, COMMAND_PALETTE_ITEMS, CommandPaletteGroup}; 2 use ratatui::{ 3 Frame, 4 layout::{Constraint, Layout}, 5 style::{Color, Modifier, Style, Stylize}, 6 text::{Line, Span}, 7 widgets::{Block, BorderType, Borders, Clear, List, ListItem, Paragraph, Widget}, 8 }; 9 10 pub fn render_command_palette(frame: &mut Frame, app: &App) { 11 let popup_area = Layout::vertical([ 12 Constraint::Percentage(22), 13 Constraint::Length(16), 14 Constraint::Fill(1), 15 ]) 16 .split(frame.area())[1]; 17 let popup_area = Layout::horizontal([ 18 Constraint::Percentage(24), 19 Constraint::Length(66), 20 Constraint::Fill(1), 21 ]) 22 .split(popup_area)[1]; 23 24 Clear.render(popup_area, frame.buffer_mut()); 25 26 let popup_block = Block::bordered() 27 .border_type(BorderType::Rounded) 28 .border_style(Color::DarkGray) 29 .title(" Command Palette ".yellow()); 30 let popup_inner_area = popup_block.inner(popup_area); 31 popup_block.render(popup_area, frame.buffer_mut()); 32 33 let popup_layout = 34 Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).split(popup_inner_area); 35 36 let input_line = Line::from(vec![ 37 Span::styled(" 🔍 ", Style::default().fg(Color::DarkGray)), 38 Span::styled( 39 if app.command_palette_query.is_empty() { 40 "Type a command or search..." 41 } else { 42 app.command_palette_query.as_str() 43 }, 44 if app.command_palette_query.is_empty() { 45 Style::default().fg(Color::DarkGray) 46 } else { 47 Style::default().fg(Color::Yellow) 48 }, 49 ), 50 ]); 51 Paragraph::new(input_line).render(popup_layout[0], frame.buffer_mut()); 52 53 let filtered_item_indices = app.filtered_command_item_indices(); 54 let selected_index = app.command_palette_selected_index(); 55 let mut visible_item_index = 0usize; 56 let mut active_group: Option<CommandPaletteGroup> = None; 57 let mut command_items = Vec::new(); 58 59 for item_index in filtered_item_indices { 60 let command_item = &COMMAND_PALETTE_ITEMS[item_index]; 61 62 if active_group != Some(command_item.group) { 63 active_group = Some(command_item.group); 64 let heading = match command_item.group { 65 CommandPaletteGroup::Actions => "Actions", 66 CommandPaletteGroup::Navigate => "Navigate", 67 }; 68 command_items.push(ListItem::new(Line::from(vec![Span::styled( 69 format!(" {heading}"), 70 Style::default() 71 .fg(Color::DarkGray) 72 .add_modifier(Modifier::BOLD), 73 )]))); 74 } 75 76 let command_item_style = if visible_item_index == selected_index { 77 Style::default() 78 .fg(Color::Yellow) 79 .bg(Color::Rgb(50, 34, 12)) 80 .add_modifier(Modifier::BOLD) 81 } else { 82 Style::default().fg(Color::Gray) 83 }; 84 85 command_items.push(ListItem::new(Line::from(vec![ 86 Span::styled(format!(" {} ", command_item.icon), command_item_style), 87 Span::styled(command_item.label, command_item_style), 88 Span::styled(" ", command_item_style), 89 Span::styled(command_item.shortcut, Style::default().fg(Color::DarkGray)), 90 ]))); 91 92 visible_item_index += 1; 93 } 94 95 if command_items.is_empty() { 96 command_items.push(ListItem::new(Line::from(vec![Span::styled( 97 " No results found.", 98 Style::default().fg(Color::DarkGray), 99 )]))); 100 } 101 102 List::new(command_items) 103 .block(Block::default().borders(Borders::TOP)) 104 .render(popup_layout[1], frame.buffer_mut()); 105 } 106 107 pub fn render_api_base_url_editor(frame: &mut Frame, app: &App) { 108 let popup_area = Layout::vertical([ 109 Constraint::Percentage(28), 110 Constraint::Length(7), 111 Constraint::Fill(1), 112 ]) 113 .split(frame.area())[1]; 114 let popup_area = Layout::horizontal([ 115 Constraint::Percentage(22), 116 Constraint::Length(72), 117 Constraint::Fill(1), 118 ]) 119 .split(popup_area)[1]; 120 121 Clear.render(popup_area, frame.buffer_mut()); 122 123 let popup_block = Block::bordered() 124 .border_type(BorderType::Rounded) 125 .border_style(Color::Yellow) 126 .title(" Device API URL ".yellow().bold()); 127 let popup_inner_area = popup_block.inner(popup_area); 128 popup_block.render(popup_area, frame.buffer_mut()); 129 130 let lines = vec![ 131 Line::from(vec![ 132 Span::styled(" URL: ", Style::default().fg(Color::DarkGray)), 133 Span::styled( 134 app.api_base_url_editor_buffer.as_str(), 135 Style::default() 136 .fg(Color::Yellow) 137 .add_modifier(Modifier::BOLD), 138 ), 139 ]), 140 Line::from(vec![ 141 Span::styled(" Enter ", Style::default().fg(Color::DarkGray)), 142 Span::styled("apply", Style::default().fg(Color::Yellow)), 143 Span::styled(" Esc ", Style::default().fg(Color::DarkGray)), 144 Span::styled("cancel", Style::default().fg(Color::Yellow)), 145 ]), 146 ]; 147 148 Paragraph::new(lines).render(popup_inner_area, frame.buffer_mut()); 149 }