aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock14
-rw-r--r--Cargo.toml1
-rw-r--r--src/app.rs32
-rw-r--r--src/event.rs27
-rw-r--r--src/ui.rs84
5 files changed, 136 insertions, 22 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2a21328..d0a8b3d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1009,6 +1009,7 @@ version = "0.1.0"
dependencies = [
"color-eyre",
"ratatui",
+ "ratatui-textarea",
]
[[package]]
@@ -1105,6 +1106,19 @@ dependencies = [
]
[[package]]
+name = "ratatui-textarea"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2950274c0e155944158cf766848dd87bd6db6dad27da1c23ee4a7c8de71dbf1f"
+dependencies = [
+ "ratatui-core",
+ "ratatui-crossterm",
+ "ratatui-widgets",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
name = "ratatui-widgets"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index d1d27bc..1214007 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,3 +6,4 @@ edition = "2024"
[dependencies]
color-eyre = "0.6.5"
ratatui = "0.30.0"
+ratatui-textarea = "0.9.1"
diff --git a/src/app.rs b/src/app.rs
index 8f8a209..2e9856a 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,24 +1,52 @@
use ratatui::widgets::ListState;
+use ratatui_textarea::TextArea;
use crate::gentoo::{Package, portage::Portage};
#[derive(Debug)]
#[allow(dead_code)]
-pub struct App {
+pub struct App<'a> {
portage: Portage,
pub list_state: ListState,
+ pub showing_search_window: bool,
+ pub textarea: TextArea<'a>,
+ pub search_found: Option<bool>,
}
#[allow(dead_code)]
-impl App {
+impl<'a> App<'a> {
pub fn new(portage: Portage) -> Self {
App {
portage,
list_state: ListState::default().with_selected(Some(0)),
+ showing_search_window: false,
+ textarea: TextArea::default(),
+ search_found: None,
}
}
pub fn installed_packages(&self) -> Vec<Package> {
self.portage.installed_packages.clone()
}
+
+ pub fn toggle_search_window(&mut self) {
+ self.showing_search_window = !self.showing_search_window;
+ self.textarea.clear();
+ self.search_found = None;
+ }
+
+ pub fn search(&mut self) -> bool {
+ let index = self.portage.installed_packages.iter().position(|s| {
+ s.name
+ .to_lowercase()
+ .contains(&self.textarea.lines()[0].to_lowercase())
+ });
+
+ if let Some(index) = index {
+ self.list_state.select(Some(index));
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/src/event.rs b/src/event.rs
index bfe1fa5..52013a5 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -6,11 +6,28 @@ use crate::app::App;
pub fn handle_event(app: &mut App) -> io::Result<bool> {
if let Event::Key(key) = event::read()? {
- match key.code {
- KeyCode::Char('q') => return Ok(true),
- KeyCode::Char('j') => app.list_state.select_next(),
- KeyCode::Char('k') => app.list_state.select_previous(),
- _ => return Ok(false),
+ if !app.showing_search_window {
+ match key.code {
+ KeyCode::Char('q') => return Ok(true),
+ KeyCode::Char('j') => app.list_state.select_next(),
+ KeyCode::Char('k') => app.list_state.select_previous(),
+ KeyCode::Char('/') => app.toggle_search_window(),
+ _ => return Ok(false),
+ }
+ } else {
+ match key.code {
+ KeyCode::Esc => app.toggle_search_window(),
+ KeyCode::Enter => {
+ app.search();
+ app.toggle_search_window();
+ ()
+ }
+ _ => {
+ app.textarea.input_without_shortcuts(key);
+ app.search_found = Some(app.search());
+ ()
+ }
+ }
}
}
diff --git a/src/ui.rs b/src/ui.rs
index a1c3440..47f5f37 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -1,32 +1,82 @@
use ratatui::{
Frame,
- layout::{Constraint, Layout},
+ layout::{Constraint, Direction, Layout},
prelude::Rect,
- style::{Color, Modifier, Style, Stylize},
+ style::{Color, Modifier, Style},
text::{Line, Span},
- widgets::{Block, Borders, List, ListItem, ListState},
+ widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph},
};
use crate::app::App;
pub fn ui(frame: &mut Frame, app: &mut App) {
- let constraints = [Constraint::Length(1), Constraint::Fill(1)];
+ let constraints = [Constraint::Fill(1), Constraint::Length(3)];
- let layout = Layout::vertical(constraints).spacing(1);
- let [top, first] = frame.area().layout(&layout);
+ let layout = Layout::vertical(constraints);
+ let [top, bottom] = frame.area().layout(&layout);
let split = Layout::horizontal([Constraint::Percentage(70), Constraint::Percentage(30)])
.spacing(1)
- .split(first);
-
- let title = Line::from_iter([
- Span::from("Installed Packages").bold(),
- Span::from(" (Press 'q' to quit and arrow keys to navigate)"),
- ]);
- frame.render_widget(title.centered(), top);
+ .split(top);
render_packages(frame, split[0], app);
render_use_flags(frame, split[1], app);
+
+ let key_hint = match app.showing_search_window {
+ false => Span::styled(
+ "(q) to quit | (/) to search | (j) down | (k) up",
+ Style::default().fg(Color::Yellow),
+ ),
+ true => Span::styled(
+ "(esc) to quit search | (enter) to search",
+ Style::default().fg(Color::Yellow),
+ ),
+ };
+
+ let key_notes_footer =
+ Paragraph::new(Line::from(key_hint)).block(Block::default().borders(Borders::ALL));
+ frame.render_widget(key_notes_footer, bottom);
+
+ if app.showing_search_window {
+ let mut popup_block = Block::default()
+ .title("Search")
+ .borders(Borders::ALL)
+ .border_type(ratatui::widgets::BorderType::Rounded);
+
+ if let Some(found) = app.search_found {
+ if found {
+ popup_block = popup_block.border_style(Style::default().fg(Color::Green))
+ } else {
+ popup_block = popup_block.border_style(Style::default().fg(Color::Red))
+ }
+ }
+
+ app.textarea.set_block(popup_block);
+ app.textarea.set_style(Style::default().fg(Color::Yellow));
+ app.textarea.set_cursor_line_style(Style::default());
+
+ let area = search_popup_rect(70, frame.area());
+ frame.render_widget(Clear, area);
+ frame.render_widget(&app.textarea, area);
+ }
+}
+
+fn search_popup_rect(percent_x: u16, r: Rect) -> Rect {
+ // Cut the given rectangle into three vertical pieces
+ let popup_layout = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints([Constraint::Length(10), Constraint::Length(3)])
+ .split(r);
+
+ // Then cut the middle vertical piece into three width-wise pieces
+ Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints([
+ Constraint::Percentage((100 - percent_x) / 2),
+ Constraint::Percentage(percent_x),
+ Constraint::Percentage((100 - percent_x) / 2),
+ ])
+ .split(popup_layout[1])[1] // Return the middle chunk
}
fn render_packages(frame: &mut Frame, area: Rect, app: &mut App) {
@@ -35,7 +85,11 @@ fn render_packages(frame: &mut Frame, area: Rect, app: &mut App) {
.style(Color::White)
.highlight_style(Modifier::REVERSED)
.highlight_symbol("> ")
- .block(Block::new().borders(Borders::ALL));
+ .block(
+ Block::new()
+ .borders(Borders::ALL)
+ .title("Installed packages"),
+ );
frame.render_stateful_widget(list, area, &mut app.list_state);
}
@@ -68,7 +122,7 @@ fn render_use_flags(frame: &mut Frame, area: Rect, app: &mut App) {
.style(Color::White)
.scroll_padding(1)
.direction(ratatui::widgets::ListDirection::TopToBottom)
- .block(Block::new().borders(Borders::ALL));
+ .block(Block::new().borders(Borders::ALL).title("USE Flags"));
let mut list_state = ListState::default();
list_state.select_first();