diff options
| author | pablu <me@pablu.de> | 2026-06-02 19:55:57 +0200 |
|---|---|---|
| committer | pablu <me@pablu.de> | 2026-06-02 19:55:57 +0200 |
| commit | 0f9492c0da3ac6d391b7b0d32ea2a391b94a18f4 (patch) | |
| tree | a1fb59a02068ad49ea40d055e3b5e00da3db63a3 /src/screens | |
| parent | b547e6758c635682c94794e5c13070a73c918203 (diff) | |
near feature parity with previous architecture
Diffstat (limited to 'src/screens')
| -rw-r--r-- | src/screens/dashboard.rs | 144 | ||||
| -rw-r--r-- | src/screens/installed_packages.rs | 148 | ||||
| -rw-r--r-- | src/screens/mod.rs | 1 | ||||
| -rw-r--r-- | src/screens/screen.rs | 7 |
4 files changed, 262 insertions, 38 deletions
diff --git a/src/screens/dashboard.rs b/src/screens/dashboard.rs new file mode 100644 index 0000000..e03624d --- /dev/null +++ b/src/screens/dashboard.rs @@ -0,0 +1,144 @@ +use ratatui::{ + crossterm::event::KeyCode, + layout::{Alignment, Constraint, Layout, Spacing}, + text::{Line, Span, Text}, + widgets::{Block, Borders, Paragraph}, +}; + +use crate::{actions::Signal, screens::screen::Screen, theme::Theme, widgets::helpers::human_size}; + +#[derive(Debug, Default)] +pub struct DashboardScreen {} + +fn create_stats<'a>(title: &'a str, value: &'a str) -> Paragraph<'a> { + Paragraph::new(vec![ + Line::from(Span::styled(title, Theme::title())), + Line::from(""), + Line::from(value), + ]) + .block( + Block::default() + .borders(Borders::ALL) + .border_style(Theme::block()) + .merge_borders(ratatui::symbols::merge::MergeStrategy::Exact), + ) + .style(Theme::text()) + .alignment(Alignment::Center) +} + +impl Screen for DashboardScreen { + fn draw( + &mut self, + frame: &mut ratatui::Frame, + area: ratatui::prelude::Rect, + repo: &crate::gentoo::Portage, + ) { + let logo = " + ██████╗ ██╗ ██╗██╗███████╗██╗ ██╗ + ██╔═══██╗██║ ██║██║██╔════╝██║ ██║ + ██║ ██║██║ ██║██║█████╗ ██║ █╗ ██║ + ██║▄▄ ██║╚██╗ ██╔╝██║██╔══╝ ██║███╗██║ + ╚██████╔╝ ╚████╔╝ ██║███████╗╚███╔███╔╝ + ╚══▀▀═╝ ╚═══╝ ╚═╝╚══════╝ ╚══╝╚══╝ + gentoo portage dashboard + "; + + let constraints = [ + Constraint::Length(9), + Constraint::Min(15), + Constraint::Fill(1), + Constraint::Length(3), + ]; + + let layout = Layout::vertical(constraints); + + let [logo_top, stats, _fill, key_hints] = area.layout(&layout); + + let [stats_top, stats_middle, stats_bottom] = Layout::vertical([ + Constraint::Fill(1), + Constraint::Fill(1), + Constraint::Fill(1), + ]) + .spacing(Spacing::Overlap(1)) + .areas(stats); + + let splits = Layout::horizontal([ + Constraint::Fill(1), + Constraint::Fill(1), + Constraint::Fill(1), + ]) + .spacing(Spacing::Overlap(1)) + .split(stats_top); + + let logo = Paragraph::new(Text::styled(logo, Theme::title())); + frame.render_widget(logo, logo_top); + + let keys = Paragraph::new(Span::styled( + "(q) to quit | (tab) to switch tabs", + Theme::muted(), + )) + .block( + Block::default() + .borders(Borders::ALL) + .border_style(Theme::block()), + ); + frame.render_widget(keys, key_hints); + + let installed_packages_len = repo.installed_packages.len().to_string(); + let installed_packages = + create_stats("Installed Packages", installed_packages_len.as_str()); + let global_use_flags = create_stats("Global USE Flags", "COMING SOON"); + let repository_state = create_stats("Repository state", "COMING SOON"); + frame.render_widget(installed_packages, splits[0]); + frame.render_widget(global_use_flags, splits[1]); + frame.render_widget(repository_state, splits[2]); + + let splits = Layout::horizontal([ + Constraint::Fill(1), + Constraint::Fill(1), + Constraint::Fill(1), + ]) + .spacing(Spacing::Overlap(1)) + .split(stats_middle); + + let world_package_count = repo.world_packages.len().to_string(); + let world_packages = create_stats("World Packages", &world_package_count); + let pending_updates = create_stats("Pending Updates", "COMING SOON"); + let last_emerge = create_stats("Last Emerge", "COMING SOON"); + + frame.render_widget(world_packages, splits[0]); + frame.render_widget(pending_updates, splits[1]); + frame.render_widget(last_emerge, splits[2]); + + let splits = Layout::horizontal([ + Constraint::Fill(1), + Constraint::Fill(1), + Constraint::Fill(1), + ]) + .spacing(Spacing::Overlap(1)) + .split(stats_bottom); + + let installed_size = human_size(repo.total_installed_size()); + + let installed_size = create_stats("Installed Size", &installed_size); + let distfiles_cache = create_stats("Distfiles Cache", "COMING SOON"); + let portage_news = create_stats("Portage News", "COMING SOON"); + + frame.render_widget(installed_size, splits[0]); + frame.render_widget(distfiles_cache, splits[1]); + frame.render_widget(portage_news, splits[2]); + } + + fn update( + &mut self, + key: ratatui::crossterm::event::KeyEvent, + _: &crate::gentoo::Portage, + ) -> Option<crate::actions::Signal> { + match key.code { + KeyCode::Char('q') => Some(Signal::Quit), + KeyCode::Tab => Some(Signal::CycleTab), + + _ => None, + } + } +} diff --git a/src/screens/installed_packages.rs b/src/screens/installed_packages.rs index 7e65fd5..48b0513 100644 --- a/src/screens/installed_packages.rs +++ b/src/screens/installed_packages.rs @@ -6,11 +6,11 @@ use ratatui::{ text::{Line, Span}, widgets::{Block, Borders, Clear, List, ListState, Paragraph}, }; -use ratatui_textarea::{Key, TextArea}; +use ratatui_textarea::TextArea; use crate::{ actions::Signal, - gentoo::Portage, + gentoo::{InstalledPackage, Portage}, screens::screen::Screen, theme::Theme, widgets::{ @@ -19,13 +19,61 @@ use crate::{ }, }; -#[derive(Debug, Default)] +#[derive(Debug)] struct SearchPopup { pub(crate) visible: bool, pub(crate) textarea: TextArea<'static>, + pub(crate) result: Option<usize>, } -#[derive(Debug, Default)] +impl Default for SearchPopup { + fn default() -> Self { + let mut search_popup = Self { + visible: bool::default(), + textarea: TextArea::default(), + result: Option::default(), + }; + + search_popup.textarea.set_style(Theme::muted()); + search_popup + .textarea + .set_cursor_line_style(Style::default()); + + return search_popup; + } +} + +impl SearchPopup { + pub fn toggle(&mut self) { + self.visible = !self.visible; + self.textarea.clear(); + } + + pub fn search(&mut self, repo: &Portage) -> Option<usize> { + let indexes: Vec<usize> = repo + .installed_packages + .iter() + .enumerate() + .filter(|(_, s)| { + s.atom + .qualified_name() + .to_lowercase() + .contains(&self.textarea.lines()[0].to_lowercase()) + }) + .map(|(i, _)| i) + .collect(); + + if indexes.len() >= 1 { + self.result = Some(indexes[0]); + return Some(indexes[0]); + } else { + self.result = None; + return None; + } + } +} + +#[derive(Debug)] pub struct InstalledPackagesScreen { list_state: ListState, @@ -34,12 +82,12 @@ pub struct InstalledPackagesScreen { impl InstalledPackagesScreen { fn render_packages(&mut self, frame: &mut Frame, area: Rect, repo: &Portage) { - // let items: Vec<String> = repo - // .installed_packages() - // .iter() - // .map(|x| x.atom.qualified_name()) - // .collect(); - let items: Vec<String> = vec![]; + // TODO: Put this into portage method + let items: Vec<String> = repo + .installed_packages + .iter() + .map(|x| x.atom.qualified_name()) + .collect(); let list = List::new(items) .style(Color::White) @@ -56,6 +104,17 @@ impl InstalledPackagesScreen { } } +impl Default for InstalledPackagesScreen { + fn default() -> Self { + let mut list_state = ListState::default(); + list_state.select(Some(0)); + Self { + list_state: list_state, + search_popup: Default::default(), + } + } +} + impl Screen for InstalledPackagesScreen { fn draw(&mut self, frame: &mut Frame, area: Rect, repo: &Portage) { let constraints = [Constraint::Fill(1), Constraint::Length(3)]; @@ -69,9 +128,16 @@ impl Screen for InstalledPackagesScreen { let split_vert = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(split[1]); + // TODO: Atleast this doesnt crash but its still weird, it should show some Info that this + // package is completly unkown or placeholder data, or at best a warning + let mut pkg: Option<&InstalledPackage> = None; + if repo.installed_packages.len() > self.list_state.selected().unwrap_or(0) { + pkg = Some(&repo.installed_packages[self.list_state.selected().unwrap_or(0)]); + } + self.render_packages(frame, split[0], repo); - render_use_flags(frame, split_vert[0], &repo.installed_packages[0]); - render_package_metadata(frame, split_vert[1], &repo.installed_packages[0]); + render_use_flags(frame, split_vert[0], pkg); + render_package_metadata(frame, split_vert[1], pkg); let text = if self.search_popup.visible { "(esc) to quit search | (enter) to search".to_string() @@ -107,20 +173,13 @@ impl Screen for InstalledPackagesScreen { .border_style(Theme::block()) .style(Style::default().bg(Theme::BG)); - // if let Some(found) = app.search_found { - // if found { - // popup_block = popup_block.border_style(Theme::success()); - // } else { - // popup_block = popup_block.border_style(Theme::error()); - // } - // } + if let Some(_) = self.search_popup.result { + popup_block = popup_block.border_style(Theme::success()); + } else { + popup_block = popup_block.border_style(Theme::error()); + } - // TODO: Only do this in init, not every time self.search_popup.textarea.set_block(popup_block); - self.search_popup.textarea.set_style(Theme::muted()); - self.search_popup - .textarea - .set_cursor_line_style(Style::default()); let area = search_popup_rect(70, area); @@ -134,20 +193,43 @@ impl Screen for InstalledPackagesScreen { } } - fn update(&mut self, key: KeyEvent) -> Option<Signal> { + fn update(&mut self, key: KeyEvent, repo: &Portage) -> Option<Signal> { if self.search_popup.visible { match key.code { - KeyCode::Esc => todo!("Toggle search window"), - KeyCode::Enter => todo!("Send search, get back matches"), - _ => self.search_popup.textarea.input_without_shortcuts(key), //TODO: Run "search - //logic" to get - //jumping while typing, + KeyCode::Esc => self.search_popup.toggle(), + KeyCode::Enter => { + let result = self.search_popup.search(repo); + self.search_popup.toggle(); + + if let Some(index) = result { + self.list_state.select(Some(index)); + } + } + _ => { + self.search_popup.textarea.input_without_shortcuts(key); + let result = self.search_popup.search(repo); + + if let Some(index) = result { + self.list_state.select(Some(index)); + } + } }; } else { match key.modifiers { KeyModifiers::CONTROL => match key.code { - KeyCode::Char('d') => todo!("next page"), - KeyCode::Char('u') => todo!("prev page"), + KeyCode::Char('d') => self + .list_state + .select(Some(self.list_state.selected().unwrap_or(0) + 30)), + KeyCode::Char('u') => { + if let Some(selected) = self.list_state.selected() { + if selected < 30 { + self.list_state.select(Some(0)); + } else { + self.list_state.select(Some(selected - 30)); + } + } + } + _ => {} }, @@ -156,7 +238,7 @@ impl Screen for InstalledPackagesScreen { KeyCode::Tab => return Some(Signal::CycleTab), KeyCode::Char('j') => self.list_state.select_next(), KeyCode::Char('k') => self.list_state.select_previous(), - KeyCode::Char('/') => todo!("Toggle search window"), + KeyCode::Char('/') => self.search_popup.toggle(), KeyCode::Char('n') => todo!("Next search"), KeyCode::Char('N') => todo!("prev search"), diff --git a/src/screens/mod.rs b/src/screens/mod.rs index db3be11..45ca33f 100644 --- a/src/screens/mod.rs +++ b/src/screens/mod.rs @@ -1,2 +1,3 @@ +pub mod dashboard; pub mod installed_packages; pub mod screen; diff --git a/src/screens/screen.rs b/src/screens/screen.rs index a87329b..f299578 100644 --- a/src/screens/screen.rs +++ b/src/screens/screen.rs @@ -1,11 +1,8 @@ use ratatui::{Frame, crossterm::event::KeyEvent, layout::Rect}; -use crate::{ - actions::{Action, Signal}, - gentoo::Portage, -}; +use crate::{actions::Signal, gentoo::Portage}; pub(crate) trait Screen { fn draw(&mut self, frame: &mut Frame, area: Rect, repo: &Portage); - fn update(&mut self, action: KeyEvent) -> Option<Signal>; + fn update(&mut self, key: KeyEvent, repo: &Portage) -> Option<Signal>; } |
