diff options
| -rw-r--r-- | src/app.rs | 13 | ||||
| -rw-r--r-- | src/gentoo/package.rs | 24 | ||||
| -rw-r--r-- | src/gentoo/portage.rs | 19 | ||||
| -rw-r--r-- | src/screens/available_packages.rs | 196 | ||||
| -rw-r--r-- | src/screens/installed_packages.rs | 21 | ||||
| -rw-r--r-- | src/screens/mod.rs | 1 | ||||
| -rw-r--r-- | src/widgets/helpers.rs | 6 | ||||
| -rw-r--r-- | src/widgets/package_metadata.rs | 66 | ||||
| -rw-r--r-- | src/widgets/use_flags.rs | 25 |
9 files changed, 314 insertions, 57 deletions
@@ -11,7 +11,8 @@ use crate::{ background_loader::PackageLoader, gentoo::portage::Portage, screens::{ - dashboard::DashboardScreen, installed_packages::InstalledPackagesScreen, screen::Screen, + available_packages::AvailablePackagesScreen, dashboard::DashboardScreen, + installed_packages::InstalledPackagesScreen, screen::Screen, }, signal::Signal, theme::Theme, @@ -37,8 +38,9 @@ pub enum LoadingState { pub struct App { portage: Portage, - installed_package_screen: InstalledPackagesScreen, dashboard_screen: DashboardScreen, + installed_package_screen: InstalledPackagesScreen, + available_package_screen: AvailablePackagesScreen, pub view: ViewState, @@ -54,6 +56,7 @@ impl App { dashboard_screen: DashboardScreen::default(), installed_package_screen: InstalledPackagesScreen::default(), + available_package_screen: AvailablePackagesScreen::default(), view: ViewState::Dashboard, @@ -81,7 +84,8 @@ impl App { .draw(frame, rest, &self.portage, &self.loading_state); } ViewState::AvailablePackages => { - frame.render_widget(Text::from("NOT IMPLEMENTED"), rest); + self.available_package_screen + .draw(frame, rest, &self.portage, &self.loading_state); } } } @@ -92,6 +96,9 @@ impl App { crate::app::ViewState::InstalledPackages => { self.installed_package_screen.update(key, &self.portage) } + ViewState::AvailablePackages => { + self.available_package_screen.update(key, &self.portage) + } // Fallback, primarly while implementing _ => match key.code { diff --git a/src/gentoo/package.rs b/src/gentoo/package.rs index 1d41b8c..fcd86eb 100644 --- a/src/gentoo/package.rs +++ b/src/gentoo/package.rs @@ -4,12 +4,34 @@ use chrono::{DateTime, Utc}; use crate::gentoo::UseFlag; -#[derive(Debug, Clone, Hash, Eq, PartialEq)] +#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct PackageKey { + // Field declaration matters because Ord, and PartialOrd check in order pub category: String, pub name: String, } +// impl Ord for PackageKey { +// fn cmp(&self, other: &Self) -> std::cmp::Ordering { +// let cat_ord = self.category.cmp(&other.category); +// if cat_ord == Ordering::Equal { +// return self.name.cmp(&other.name); +// } +// +// cat_ord +// } +// } +// +// impl PartialOrd for PackageKey { +// fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { +// match self.category.partial_cmp(&other.category) { +// Some(core::cmp::Ordering::Equal) => {} +// ord => return ord, +// } +// self.name.partial_cmp(&other.name) +// } +// } + impl PackageKey { pub fn from_atom(s: &str) -> Option<Self> { let (category, name) = s.split_once('/')?; diff --git a/src/gentoo/portage.rs b/src/gentoo/portage.rs index 935c1ce..6a1b797 100644 --- a/src/gentoo/portage.rs +++ b/src/gentoo/portage.rs @@ -41,16 +41,25 @@ impl Portage { .collect() } - pub fn get_installed_package(&self, index: usize) -> Option<&InstalledPackage> { - if self.installed.len() > index && index > 0 { - return Some(&self.installed[index]); + pub fn get_installed_package(&self, key: &PackageKey) -> Option<&InstalledPackage> { + let pkg = self.installed.iter().find(|pkg| pkg.atom == *key); + if let Some(pkg) = pkg { + return Some(pkg); } None } - pub fn get_installed_package_key(&self, key: &PackageKey) -> Option<&InstalledPackage> { - let pkg = self.installed.iter().find(|pkg| pkg.atom == *key); + pub fn available_packages(&self) -> Vec<&Package> { + let mut pkg: Vec<&Package> = self.available.iter().collect(); + + pkg.sort_by_key(|p| &p.atom); + + pkg + } + + pub fn get_available_package(&self, key: &PackageKey) -> Option<&Package> { + let pkg = self.available.iter().find(|pkg| pkg.atom == *key); if let Some(pkg) = pkg { return Some(pkg); } diff --git a/src/screens/available_packages.rs b/src/screens/available_packages.rs new file mode 100644 index 0000000..1f61982 --- /dev/null +++ b/src/screens/available_packages.rs @@ -0,0 +1,196 @@ +use std::collections::HashSet; + +use ratatui::{ + Frame, + crossterm::event::KeyCode, + layout::{Constraint, Layout, Rect}, + style::Color, + text::Text, + widgets::{Block, Borders, List, ListState}, +}; + +use crate::{ + app::LoadingState, + gentoo::package::{Package, PackageKey, PackageVersion}, + screens::screen::Screen, + signal::Signal, + theme::Theme, + widgets::{package_metadata::render_package_metadata, use_flags::render_use_flags}, +}; + +#[derive(Debug)] +enum CurrentList { + PackageList, + VariantList, +} + +#[derive(Debug)] +pub struct AvailablePackagesScreen { + chosen_list: CurrentList, + + pkg_list_state: ListState, + variant_list_state: ListState, +} + +fn version_to_variant(pkg_version: &PackageVersion) -> String { + format!( + "{}::{}", + pkg_version.version, pkg_version.metadata.repository + ) +} + +impl AvailablePackagesScreen { + // TODO: this is the same as InstalledPackagesScreen, replace this with a widget function + fn render_packages(&mut self, frame: &mut Frame, area: Rect, package_keys: Vec<String>) { + // TODO: Put this into screen, instead of rebuilding the list every frame + let list = List::new(package_keys) + .style(Color::White) + .highlight_style(Theme::selected()) + .highlight_symbol("> ") + .block( + Block::default() + .borders(Borders::ALL) + .border_style(Theme::block()) + .title("Available packages"), + ); + + frame.render_stateful_widget(list, area, &mut self.pkg_list_state); + } + + fn render_package_variants( + &mut self, + frame: &mut Frame, + area: Rect, + variants: Vec<&PackageVersion>, + ) { + // TODO: Put this into screen, instead of rebuilding the list every frame + let list = List::new(variants.iter().map(|v| version_to_variant(v))) + .style(Color::White) + .highlight_style(Theme::selected()) + .highlight_symbol("> ") + .block( + Block::default() + .borders(Borders::ALL) + .border_style(Theme::block()) + .title("Variants"), + ); + + frame.render_stateful_widget(list, area, &mut self.variant_list_state); + } + + fn cycle_list(&mut self) { + self.variant_list_state.select(Some(0)); + + self.chosen_list = match self.chosen_list { + CurrentList::PackageList => CurrentList::VariantList, + CurrentList::VariantList => CurrentList::PackageList, + } + } +} + +impl Default for AvailablePackagesScreen { + fn default() -> Self { + let mut list_state = ListState::default(); + list_state.select(Some(0)); + + let mut variant_state = ListState::default(); + variant_state.select(Some(0)); + + Self { + chosen_list: CurrentList::PackageList, + pkg_list_state: list_state, + variant_list_state: variant_state, + } + } +} + +impl Screen for AvailablePackagesScreen { + fn draw( + &mut self, + frame: &mut ratatui::Frame, + area: ratatui::prelude::Rect, + repo: &crate::gentoo::Portage, + loading_state: &crate::app::LoadingState, + ) { + if !matches!(loading_state, LoadingState::Complete) { + // TODO: Implement loading screen + frame.render_widget(Text::styled("Not implemented yet :P", Theme::text()), area); + return; + } + + let constraints = [Constraint::Percentage(50), Constraint::Percentage(50)]; + let [left, right] = area.layout(&Layout::horizontal(constraints)); + + let [package_list, use_flags] = left.layout(&Layout::vertical(constraints)); + let [package_variants, metadata] = right.layout(&Layout::vertical(constraints)); + + let packages: Vec<&PackageKey> = + repo.available_packages().iter().map(|x| &x.atom).collect(); + + let selected_package_key = packages.get(self.pkg_list_state.selected().unwrap_or(0)); + let pkg: Option<&Package> = match selected_package_key { + Some(pkg_key) => repo.get_available_package(pkg_key), + None => None, + }; + + self.render_packages( + frame, + package_list, + packages.iter().map(|x| x.qualified_name()).collect(), + ); + + if let Some(pkg) = pkg { + self.render_package_variants(frame, package_variants, pkg.versions.iter().collect()); + + let selected_version = &pkg.versions[self.variant_list_state.selected().unwrap_or(0)]; + + render_use_flags( + frame, + use_flags, + selected_version.iuse.iter().collect(), + // Get default active use flags for package, and globally defines ones + HashSet::new(), + ); + + render_package_metadata( + frame, + metadata, + &selected_version.version, + &selected_version.metadata, + None, + None, + ); + } + } + + fn update( + &mut self, + key: ratatui::crossterm::event::KeyEvent, + _repo: &crate::gentoo::Portage, + ) -> Option<crate::signal::Signal> { + match key.code { + KeyCode::Char('q') => return Some(Signal::Quit), + KeyCode::Tab => return Some(Signal::CycleTab), + + // TODO: This should cycle, also space isnt a good key for this + KeyCode::Char(' ') => self.cycle_list(), + + _ => match self.chosen_list { + CurrentList::PackageList => match key.code { + KeyCode::Char('j') => self.pkg_list_state.select_next(), + KeyCode::Char('k') => self.pkg_list_state.select_previous(), + + _ => {} + }, + CurrentList::VariantList => match key.code { + KeyCode::Char('j') => self.variant_list_state.select_next(), + KeyCode::Char('k') => self.variant_list_state.select_previous(), + + _ => {} + }, + }, + } + + None + } +} diff --git a/src/screens/installed_packages.rs b/src/screens/installed_packages.rs index 4421837..8f0cc5d 100644 --- a/src/screens/installed_packages.rs +++ b/src/screens/installed_packages.rs @@ -162,7 +162,7 @@ impl Screen for InstalledPackagesScreen { // (this) package installed let selected_package_key = items.get(self.list_state.selected().unwrap_or(0)); let pkg: Option<&InstalledPackage> = match selected_package_key { - Some(selected_package_key) => repo.get_installed_package_key(selected_package_key), + Some(selected_package_key) => repo.get_installed_package(selected_package_key), None => None, }; @@ -171,8 +171,23 @@ impl Screen for InstalledPackagesScreen { split[0], items.iter().map(|x| x.qualified_name()).collect(), ); - render_use_flags(frame, split_vert[0], pkg); - render_package_metadata(frame, split_vert[1], pkg); + if let Some(pkg) = pkg { + render_use_flags( + frame, + split_vert[0], + pkg.iuse.iter().collect(), + // TODO: Dont use clone here if possible + pkg.enabled_use_flags.clone(), + ); + render_package_metadata( + frame, + split_vert[1], + &pkg.version, + &pkg.metadata, + Some(pkg.size), + Some(pkg.build_time), + ); + } let text = if self.search_popup.visible { "(esc) to quit search | (enter) to search".to_string() diff --git a/src/screens/mod.rs b/src/screens/mod.rs index 45ca33f..8e39692 100644 --- a/src/screens/mod.rs +++ b/src/screens/mod.rs @@ -1,3 +1,4 @@ +pub mod available_packages; pub mod dashboard; pub mod installed_packages; pub mod screen; diff --git a/src/widgets/helpers.rs b/src/widgets/helpers.rs index 0bde326..1e76b5f 100644 --- a/src/widgets/helpers.rs +++ b/src/widgets/helpers.rs @@ -4,7 +4,7 @@ use ratatui::{ text::{Line, Span}, }; -use crate::gentoo::InstalledPackage; +use crate::gentoo::{InstalledPackage, package::Metadata}; pub fn search_popup_rect(percent_x: u16, r: Rect) -> Rect { // Cut the given rectangle into three vertical pieces @@ -24,14 +24,14 @@ pub fn search_popup_rect(percent_x: u16, r: Rect) -> Rect { .split(popup_layout[1])[1] // Return the middle chunk } -pub fn homepage_lines(pkg: &InstalledPackage, bold_style: Style) -> Vec<Line<'_>> { +pub fn homepage_lines(metadata: &Metadata, bold_style: Style) -> Vec<Line<'_>> { let mut lines = Vec::new(); lines.push(Line::from(vec![ Span::styled("Homepage: ", bold_style), Span::raw(""), ])); - for url in pkg.metadata.homepage.iter().filter(|s| !s.is_empty()) { + for url in metadata.homepage.iter().filter(|s| !s.is_empty()) { lines.push(Line::from(vec![ Span::raw(" - "), // indentation Span::styled( diff --git a/src/widgets/package_metadata.rs b/src/widgets/package_metadata.rs index ee46f62..d6c82cd 100644 --- a/src/widgets/package_metadata.rs +++ b/src/widgets/package_metadata.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use ratatui::{ Frame, layout::Rect, @@ -6,64 +7,69 @@ use ratatui::{ }; use crate::{ - gentoo::InstalledPackage, + gentoo::{ + InstalledPackage, + package::{Metadata, PackageKey}, + }, theme::Theme, widgets::helpers::{homepage_lines, human_size}, }; -pub fn render_package_metadata(frame: &mut Frame, area: Rect, pkg: Option<&InstalledPackage>) { - let Some(pkg) = pkg else { - return; - }; - +pub fn render_package_metadata( + frame: &mut Frame, + area: Rect, + pkg_version: &String, + metadata: &Metadata, + size: Option<usize>, + build_time: Option<DateTime<Utc>>, +) { let bold_style = Theme::title(); //Style::default().add_modifier(Modifier::BOLD); let maintainer = Line::from_iter([ Span::styled("Maintainer: ", bold_style), - Span::raw(pkg.metadata.maintainer.as_deref().unwrap_or("Unknown")), + Span::raw(metadata.maintainer.as_deref().unwrap_or("Unknown")), ]); let version = Line::from_iter([ Span::styled("Version: ", bold_style), - Span::raw(&pkg.version), + Span::raw(pkg_version), ]); let repository = Line::from_iter([ Span::styled("Repository: ", bold_style), - Span::raw(&pkg.metadata.repository), + Span::raw(&metadata.repository), ]); let license = Line::from_iter([ Span::styled("License: ", bold_style), - Span::raw(pkg.metadata.license.as_deref().unwrap_or("Unknown")), + Span::raw(metadata.license.as_deref().unwrap_or("Unknown")), ]); let description = Line::from_iter([ Span::styled("Description: ", bold_style), - Span::raw(pkg.metadata.description.as_deref().unwrap_or("Unknown")), + Span::raw(metadata.description.as_deref().unwrap_or("Unknown")), ]); - let size = Line::from_iter([ - Span::styled("Size: ", bold_style), - Span::raw(human_size(pkg.size)), - ]); + let mut lines = vec![maintainer, version, repository, license, description]; - let build_time = Line::from_iter([ - Span::styled("Build time: ", bold_style), - Span::raw(pkg.build_time.to_string()), - ]); + let mut homepage = homepage_lines(metadata, bold_style); + lines.append(&mut homepage); - let mut lines = vec![ - maintainer, - version, - repository, - build_time, - license, - description, - size, - ]; + if let Some(size) = size { + let size = Line::from_iter([ + Span::styled("Size: ", bold_style), + Span::raw(human_size(size)), + ]); + lines.push(size); + } - let mut homepage = homepage_lines(pkg, bold_style); - lines.append(&mut homepage); + if let Some(build_time) = build_time { + let build_time = Line::from_iter([ + Span::styled("Build time: ", bold_style), + Span::raw(build_time.to_string()), + ]); + + lines.push(build_time); + } let paragraph = Paragraph::new(lines) .block( diff --git a/src/widgets/use_flags.rs b/src/widgets/use_flags.rs index 5017160..76e314b 100644 --- a/src/widgets/use_flags.rs +++ b/src/widgets/use_flags.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use ratatui::{ Frame, layout::Rect, @@ -6,22 +8,21 @@ use ratatui::{ widgets::{Block, Borders, Paragraph}, }; -use crate::{gentoo::InstalledPackage, theme::Theme}; - -pub fn render_use_flags(frame: &mut Frame, area: Rect, package: Option<&InstalledPackage>) { - let Some(package) = package else { - return; - }; - - let flags: Vec<_> = package.iuse.iter().collect(); +use crate::{gentoo::UseFlag, theme::Theme}; - let max_len = flags.iter().map(|f| f.name.len()).max().unwrap_or(1); +pub fn render_use_flags( + frame: &mut Frame, + area: Rect, + use_flags: Vec<&UseFlag>, + enabled_use_flags: HashSet<String>, +) { + let max_len = use_flags.iter().map(|f| f.name.len()).max().unwrap_or(1); // width per column (+ padding between columns) let col_width = max_len as u16 + 3; let columns = (area.width / col_width).max(1) as usize; - let rows = flags.len().div_ceil(columns); + let rows = use_flags.len().div_ceil(columns); let mut lines = Vec::new(); @@ -31,8 +32,8 @@ pub fn render_use_flags(frame: &mut Frame, area: Rect, package: Option<&Installe for col in 0..columns { let idx = row * columns + col; - if let Some(flag) = flags.get(idx) { - let mut style = if package.enabled_use_flags.contains(&flag.name) { + if let Some(flag) = use_flags.get(idx) { + let mut style = if enabled_use_flags.contains(&flag.name) { Theme::success() } else if flag.default { Theme::info() |
