|
| 1 | +use crate::app::{App, InputMode, Source}; |
| 2 | +use ratatui::{ |
| 3 | + layout::{Constraint, Direction, Layout}, |
| 4 | + style::{Color, Modifier, Style}, |
| 5 | + text::{Line, Span}, |
| 6 | + widgets::{Block, Borders, List, ListItem, Paragraph, Wrap}, |
| 7 | + Frame, |
| 8 | +}; |
1 | 9 |
|
| 10 | +pub fn ui(f: &mut Frame, app: &mut App) { |
| 11 | + let chunks = Layout::default() |
| 12 | + .direction(Direction::Vertical) |
| 13 | + .constraints([Constraint::Length(3), Constraint::Min(1), Constraint::Length(5)]) |
| 14 | + .split(f.area()); |
| 15 | + let (msg, style) = match app.input_mode { |
| 16 | + InputMode::Normal => ( |
| 17 | + vec![ |
| 18 | + Span::raw("Press "), |
| 19 | + Span::styled("q", Style::default().add_modifier(Modifier::BOLD)), |
| 20 | + Span::raw(" to exit, "), |
| 21 | + Span::styled("e", Style::default().add_modifier(Modifier::BOLD)), |
| 22 | + Span::raw(" to edit query, "), |
| 23 | + Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), |
| 24 | + Span::raw(" to search, "), |
| 25 | + Span::styled("a/s/f/l", Style::default().add_modifier(Modifier::BOLD)), |
| 26 | + Span::raw(" to switch source (APT/SNAP/FLATPAK/ALL), "), |
| 27 | + Span::styled("i", Style::default().add_modifier(Modifier::BOLD)), |
| 28 | + Span::raw(" to install, "), |
| 29 | + Span::styled("r", Style::default().add_modifier(Modifier::BOLD)), |
| 30 | + Span::raw(" to remove, "), |
| 31 | + Span::styled("j/k", Style::default().add_modifier(Modifier::BOLD)), |
| 32 | + Span::raw(" or arrows to navigate."), |
| 33 | + ], |
| 34 | + Style::default(), |
| 35 | + ), |
| 36 | + InputMode::Editing => ( |
| 37 | + vec![ |
| 38 | + Span::raw("Press "), |
| 39 | + Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)), |
| 40 | + Span::raw(" to cancel, "), |
| 41 | + Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), |
| 42 | + Span::raw(" to confirm editing."), |
| 43 | + ], |
| 44 | + Style::default(), |
| 45 | + ), |
| 46 | + }; |
| 47 | + let mut text = vec![Line::from(msg)]; |
| 48 | + if !app.message.is_empty() { |
| 49 | + text.push(Line::from("")); |
| 50 | + text.push(Line::from(Span::styled( |
| 51 | + app.message.clone(), |
| 52 | + Style::default().fg(Color::Yellow), |
| 53 | + ))); |
| 54 | + } |
| 55 | + let help_message = Paragraph::new(text).style(style).wrap(Wrap::default()); |
| 56 | + f.render_widget(help_message, chunks[2]); |
| 57 | + let input = Paragraph::new(app.input.as_str()) |
| 58 | + .style(match app.input_mode { |
| 59 | + InputMode::Normal => Style::default(), |
| 60 | + InputMode::Editing => Style::default().fg(Color::Yellow), |
| 61 | + }) |
| 62 | + .block(Block::default().borders(Borders::ALL).title("Search Query")); |
| 63 | + f.render_widget(input, chunks[0]); |
| 64 | + if let InputMode::Editing = app.input_mode { |
| 65 | + let cursor_x = chunks[0].x + app.input.len() as u16 + 1; |
| 66 | + let cursor_y = chunks[0].y + 1; |
| 67 | + f.set_cursor_position((cursor_x, cursor_y)); |
| 68 | + } |
| 69 | + let filtered_packages = app.get_filtered_packages(); |
| 70 | + let items: Vec<ListItem> = filtered_packages |
| 71 | + .iter() |
| 72 | + .map(|p| { |
| 73 | + ListItem::new(Line::from(vec![ |
| 74 | + Span::styled(&p.name, Style::default().fg(Color::Green)), |
| 75 | + Span::raw(" ("), |
| 76 | + Span::styled(p.source.as_str(), Style::default().fg(Color::Blue)), |
| 77 | + Span::raw(") - "), |
| 78 | + Span::raw(&p.description), |
| 79 | + ])) |
| 80 | + }) |
| 81 | + .collect(); |
| 82 | + let list = List::new(items) |
| 83 | + .block( |
| 84 | + Block::default() |
| 85 | + .borders(Borders::ALL) |
| 86 | + .title(format!( |
| 87 | + "Packages [{}] (a/s/f/l to switch, i:install, r:remove)", |
| 88 | + app.selected_source.as_str() |
| 89 | + )), |
| 90 | + ) |
| 91 | + .highlight_style(Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD)) |
| 92 | + .highlight_symbol(">> "); |
| 93 | + f.render_stateful_widget(list, chunks[1], &mut app.package_list_state); |
| 94 | +} |
0 commit comments