From b547e6758c635682c94794e5c13070a73c918203 Mon Sep 17 00:00:00 2001 From: pablu Date: Tue, 2 Jun 2026 18:49:48 +0200 Subject: wip: refactor architecture --- src/actions/action.rs | 12 ++ src/actions/mod.rs | 5 + src/actions/signal.rs | 4 + src/app.rs | 245 ++++++++++++++++++++++---------------- src/event.rs | 110 ++++++++--------- src/gentoo/portage.rs | 2 +- src/main.rs | 25 ++-- src/screens/installed_packages.rs | 170 ++++++++++++++++++++++++++ src/screens/mod.rs | 2 + src/screens/screen.rs | 11 ++ src/widgets/helpers.rs | 65 ++++++++++ src/widgets/mod.rs | 3 + src/widgets/package_metadata.rs | 75 ++++++++++++ src/widgets/use_flags.rs | 43 +++++++ 14 files changed, 602 insertions(+), 170 deletions(-) create mode 100644 src/actions/action.rs create mode 100644 src/actions/mod.rs create mode 100644 src/actions/signal.rs create mode 100644 src/screens/installed_packages.rs create mode 100644 src/screens/mod.rs create mode 100644 src/screens/screen.rs create mode 100644 src/widgets/helpers.rs create mode 100644 src/widgets/mod.rs create mode 100644 src/widgets/package_metadata.rs create mode 100644 src/widgets/use_flags.rs (limited to 'src') diff --git a/src/actions/action.rs b/src/actions/action.rs new file mode 100644 index 0000000..b2070d6 --- /dev/null +++ b/src/actions/action.rs @@ -0,0 +1,12 @@ +#[derive(Debug, Clone)] +pub enum Action { + Quit, + MoveUp, + MoveDown, + OpenPopup, + ClosePopup, + Select, + NextSearchResult, + PrevSearchResult, + ChangeTab, +} diff --git a/src/actions/mod.rs b/src/actions/mod.rs new file mode 100644 index 0000000..ed1dfa9 --- /dev/null +++ b/src/actions/mod.rs @@ -0,0 +1,5 @@ +pub mod action; +pub mod signal; + +pub use action::Action; +pub use signal::Signal; diff --git a/src/actions/signal.rs b/src/actions/signal.rs new file mode 100644 index 0000000..1210e17 --- /dev/null +++ b/src/actions/signal.rs @@ -0,0 +1,4 @@ +pub enum Signal { + Quit, + CycleTab, +} diff --git a/src/app.rs b/src/app.rs index 6e9e8ab..c014a49 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,19 @@ -use ratatui::widgets::ListState; -use ratatui_textarea::TextArea; - -use crate::gentoo::{InstalledPackage, portage::Portage}; +use color_eyre::eyre::Ok; +use ratatui::{ + Frame, + crossterm::event::{self, Event}, + layout::{Constraint, Layout}, + style::Stylize, + text::Text, + widgets::Block, +}; + +use crate::{ + actions::{Signal, signal}, + gentoo::{InstalledPackage, portage::Portage}, + screens::{installed_packages::InstalledPackagesScreen, screen::Screen}, + theme::Theme, +}; #[derive(Debug)] pub enum ViewState { @@ -11,34 +23,67 @@ pub enum ViewState { } #[derive(Debug)] -pub struct App<'a> { +pub struct App { portage: Portage, - pub list_state: ListState, - pub showing_search_window: bool, - pub textarea: TextArea<'a>, - pub search_found: Option, - search_indexes: Option>, - pub search_indexes_len: Option, - pub current_search_index: Option, + installed_package_screen: InstalledPackagesScreen, + + // pub search_found: Option, + // search_indexes: Option>, + // pub search_indexes_len: Option, + // pub current_search_index: Option, pub view: ViewState, } -impl App<'_> { +impl App { 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, - search_indexes: None, - current_search_index: None, - search_indexes_len: None, - view: ViewState::Dashboard, + + installed_package_screen: InstalledPackagesScreen::default(), + + view: ViewState::InstalledPackages, + } + } + + pub fn draw(&mut self, frame: &mut Frame) { + frame.render_widget(Block::default().bg(Theme::BG), frame.area()); + + let layout = Layout::vertical(vec![Constraint::Length(3), Constraint::Fill(1)]); + let [tab_bar, rest] = frame.area().layout(&layout); + + // render_tab(frame, tab_bar, &app.view); + + match self.view { + crate::app::ViewState::InstalledPackages => { + self.installed_package_screen + .draw(frame, rest, &self.portage) + } + _ => frame.render_widget(Text::from("NOT IMPLEMENTED"), rest), } } + pub fn update(&mut self) -> color_eyre::Result { + if let Event::Key(key) = event::read()? { + let signal = match self.view { + crate::app::ViewState::InstalledPackages => { + self.installed_package_screen.update(key) + } + + _ => None, + }; + + if let Some(signal) = signal { + match signal { + Signal::Quit => return Ok(true), + Signal::CycleTab => todo!(), + } + } + } + + Ok(false) + } + pub fn world_count(&self) -> usize { self.portage.world_packages.len() } @@ -47,20 +92,20 @@ impl App<'_> { &self.portage.installed_packages } - pub fn current_package(&self) -> &InstalledPackage { - let selected_package_index = self.list_state.selected().unwrap_or_default(); - &self.portage.installed_packages[selected_package_index] - } + // pub fn current_package(&self) -> &InstalledPackage { + // let selected_package_index = self.list_state.selected().unwrap_or_default(); + // &self.portage.installed_packages[selected_package_index] + // } pub fn total_installed_size(&self) -> usize { self.portage.installed_packages.iter().map(|p| p.size).sum() } - pub fn toggle_search_window(&mut self) { - self.showing_search_window = !self.showing_search_window; - self.textarea.clear(); - self.search_found = None; - } + // pub fn toggle_search_window(&mut self) { + // self.showing_search_window = !self.showing_search_window; + // self.textarea.clear(); + // self.search_found = None; + // } pub fn cycle_current_tab(&mut self) { match self.view { @@ -70,74 +115,74 @@ impl App<'_> { } } - pub fn search(&mut self) -> bool { - let indexes: Vec = self - .portage - .installed_packages - .iter() - .enumerate() - .filter(|(_, s)| { - s.atom - .qualified_name() - .to_lowercase() - .contains(&self.textarea.lines()[0].to_lowercase()) - }) - .map(|(i, _)| i) - .collect(); - - if let Some(index) = indexes.first() { - self.list_state.select(Some(*index)); - self.search_indexes_len = Some(indexes.len()); - self.search_indexes = Some(indexes); - self.current_search_index = Some(0); - - return true; - } - - self.search_indexes = None; - - false - } - - pub fn next_search(&mut self) { - let Some(mut current_index) = self.current_search_index else { - return; - }; - - let Some(search_indexes) = &self.search_indexes else { - return; - }; - - if current_index + 1 >= search_indexes.len() { - current_index = 0; - } else { - current_index += 1; - } - - let list_index = search_indexes[current_index]; - - self.list_state.select(Some(list_index)); - self.current_search_index = Some(current_index); - } - - pub fn prev_search(&mut self) { - let Some(mut current_index) = self.current_search_index else { - return; - }; - - let Some(search_indexes) = &self.search_indexes else { - return; - }; - - if current_index == 0 { - current_index = search_indexes.len() - 1; - } else { - current_index -= 1; - } - - let list_index = search_indexes[current_index]; - - self.list_state.select(Some(list_index)); - self.current_search_index = Some(current_index); - } + // pub fn search(&mut self) -> bool { + // let indexes: Vec = self + // .portage + // .installed_packages + // .iter() + // .enumerate() + // .filter(|(_, s)| { + // s.atom + // .qualified_name() + // .to_lowercase() + // .contains(&self.textarea.lines()[0].to_lowercase()) + // }) + // .map(|(i, _)| i) + // .collect(); + // + // if let Some(index) = indexes.first() { + // self.list_state.select(Some(*index)); + // self.search_indexes_len = Some(indexes.len()); + // self.search_indexes = Some(indexes); + // self.current_search_index = Some(0); + // + // return true; + // } + // + // self.search_indexes = None; + // + // false + // } + // + // pub fn next_search(&mut self) { + // let Some(mut current_index) = self.current_search_index else { + // return; + // }; + // + // let Some(search_indexes) = &self.search_indexes else { + // return; + // }; + // + // if current_index + 1 >= search_indexes.len() { + // current_index = 0; + // } else { + // current_index += 1; + // } + // + // let list_index = search_indexes[current_index]; + // + // self.list_state.select(Some(list_index)); + // self.current_search_index = Some(current_index); + // } + // + // pub fn prev_search(&mut self) { + // let Some(mut current_index) = self.current_search_index else { + // return; + // }; + // + // let Some(search_indexes) = &self.search_indexes else { + // return; + // }; + // + // if current_index == 0 { + // current_index = search_indexes.len() - 1; + // } else { + // current_index -= 1; + // } + // + // let list_index = search_indexes[current_index]; + // + // self.list_state.select(Some(list_index)); + // self.current_search_index = Some(current_index); + // } } diff --git a/src/event.rs b/src/event.rs index 44a4c3e..95eece2 100644 --- a/src/event.rs +++ b/src/event.rs @@ -13,11 +13,11 @@ pub fn handle_event(app: &mut App) -> io::Result { } } crate::app::ViewState::InstalledPackages => { - if app.showing_search_window { - handle_search_keybinds(key, app); - } else if handle_installed_packages_keybinds(key, app) { - return Ok(true); - } + // if app.showing_search_window { + // handle_search_keybinds(key, app); + // } else if handle_installed_packages_keybinds(key, app) { + // return Ok(true); + // } } crate::app::ViewState::AvailablePackages => { if handle_available_packages_keybinds(key, app) { @@ -30,7 +30,7 @@ pub fn handle_event(app: &mut App) -> io::Result { Ok(false) } -fn handle_available_packages_keybinds(key: KeyEvent, app: &mut App<'_>) -> bool { +fn handle_available_packages_keybinds(key: KeyEvent, app: &mut App) -> bool { match key.code { KeyCode::Char('q') => return true, KeyCode::Tab => app.cycle_current_tab(), @@ -40,55 +40,55 @@ fn handle_available_packages_keybinds(key: KeyEvent, app: &mut App<'_>) -> bool false } - -fn handle_search_keybinds(key: KeyEvent, app: &mut App) { - 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()); - } - } -} - -fn handle_installed_packages_keybinds(key: KeyEvent, app: &mut App) -> bool { - match key.modifiers { - KeyModifiers::CONTROL => match key.code { - KeyCode::Char('d') => app - .list_state - .select(Some(app.list_state.selected().unwrap_or(0) + 30)), - KeyCode::Char('u') => { - if let Some(selected) = app.list_state.selected() { - if selected < 30 { - app.list_state.select(Some(0)); - } else { - app.list_state.select(Some(selected - 30)); - } - } - } - _ => {} - }, - _ => match key.code { - KeyCode::Char('q') => return true, - KeyCode::Char('j') => app.list_state.select_next(), - KeyCode::Char('k') => app.list_state.select_previous(), - KeyCode::Char('/') => app.toggle_search_window(), - KeyCode::Char('n') => app.next_search(), - KeyCode::Char('N') => app.prev_search(), - - KeyCode::Tab => app.cycle_current_tab(), - - _ => {} - }, - } - - false -} - +// +// fn handle_search_keybinds(key: KeyEvent, app: &mut App) { +// 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()); +// } +// } +// } +// +// fn handle_installed_packages_keybinds(key: KeyEvent, app: &mut App) -> bool { +// match key.modifiers { +// KeyModifiers::CONTROL => match key.code { +// KeyCode::Char('d') => app +// .list_state +// .select(Some(app.list_state.selected().unwrap_or(0) + 30)), +// KeyCode::Char('u') => { +// if let Some(selected) = app.list_state.selected() { +// if selected < 30 { +// app.list_state.select(Some(0)); +// } else { +// app.list_state.select(Some(selected - 30)); +// } +// } +// } +// _ => {} +// }, +// _ => match key.code { +// KeyCode::Char('q') => return true, +// KeyCode::Char('j') => app.list_state.select_next(), +// KeyCode::Char('k') => app.list_state.select_previous(), +// KeyCode::Char('/') => app.toggle_search_window(), +// KeyCode::Char('n') => app.next_search(), +// KeyCode::Char('N') => app.prev_search(), +// +// KeyCode::Tab => app.cycle_current_tab(), +// +// _ => {} +// }, +// } +// +// false +// } +// fn handle_dashboard_keybinds(key: KeyEvent, app: &mut App) -> bool { match key.code { KeyCode::Char('q') => return true, diff --git a/src/gentoo/portage.rs b/src/gentoo/portage.rs index 51dc60a..b444a5b 100644 --- a/src/gentoo/portage.rs +++ b/src/gentoo/portage.rs @@ -332,7 +332,7 @@ impl Portage { .and_then(DateTime::from_timestamp_secs) .unwrap_or(DateTime::from_timestamp_nanos(0)); - let maintainer = extract_maintainer(&repo_path)?; + let maintainer = extract_maintainer(&repo_path).unwrap_or(None); let atom = PackageKey { category: cat_name.clone(), diff --git a/src/main.rs b/src/main.rs index ae3ff96..b768cc7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ +mod actions; mod app; mod event; mod gentoo; +mod screens; mod theme; -mod ui; +mod widgets; use std::{ io::{self, Stdout, stdout}, @@ -11,7 +13,6 @@ use std::{ use ratatui::{ Terminal, - backend::Backend, crossterm::{ execute, terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, @@ -19,7 +20,7 @@ use ratatui::{ prelude::CrosstermBackend, }; -use crate::{app::App, event::handle_event, gentoo::Portage, ui::ui}; +use crate::{app::App, gentoo::Portage}; struct TuiGuard; @@ -31,11 +32,9 @@ impl Drop for TuiGuard { fn main() -> color_eyre::Result<()> { color_eyre::install()?; - init_panic_hook(); - let _guard = TuiGuard; - let mut tui = init_tui()?; + let tui = init_tui()?; let mut p = Portage::new(); p.world_packages = Portage::load_world_packages()?; @@ -45,18 +44,15 @@ fn main() -> color_eyre::Result<()> { let mut app = App::new(p); - run(&mut tui, &mut app)?; + run(tui, &mut app)?; Ok(()) } -fn run(terminal: &mut Terminal, app: &mut App) -> io::Result<()> -where - io::Error: From, -{ +fn run(mut terminal: Terminal>, app: &mut App) -> color_eyre::Result<()> { loop { - terminal.draw(|frame| ui(frame, app))?; - let should_exit = handle_event(app)?; + terminal.draw(|frame| app.draw(frame))?; + let should_exit = app.update()?; if should_exit { break; } @@ -66,8 +62,9 @@ where } fn init_tui() -> io::Result>> { - enable_raw_mode()?; execute!(stdout(), EnterAlternateScreen)?; + enable_raw_mode()?; + init_panic_hook(); Terminal::new(CrosstermBackend::new(stdout())) } diff --git a/src/screens/installed_packages.rs b/src/screens/installed_packages.rs new file mode 100644 index 0000000..7e65fd5 --- /dev/null +++ b/src/screens/installed_packages.rs @@ -0,0 +1,170 @@ +use ratatui::{ + Frame, + crossterm::event::{KeyCode, KeyEvent, KeyModifiers}, + layout::{Constraint, Layout, Rect}, + style::{Color, Modifier, Style}, + text::{Line, Span}, + widgets::{Block, Borders, Clear, List, ListState, Paragraph}, +}; +use ratatui_textarea::{Key, TextArea}; + +use crate::{ + actions::Signal, + gentoo::Portage, + screens::screen::Screen, + theme::Theme, + widgets::{ + helpers::search_popup_rect, package_metadata::render_package_metadata, + use_flags::render_use_flags, + }, +}; + +#[derive(Debug, Default)] +struct SearchPopup { + pub(crate) visible: bool, + pub(crate) textarea: TextArea<'static>, +} + +#[derive(Debug, Default)] +pub struct InstalledPackagesScreen { + list_state: ListState, + + search_popup: SearchPopup, +} + +impl InstalledPackagesScreen { + fn render_packages(&mut self, frame: &mut Frame, area: Rect, repo: &Portage) { + // let items: Vec = repo + // .installed_packages() + // .iter() + // .map(|x| x.atom.qualified_name()) + // .collect(); + let items: Vec = vec![]; + + let list = List::new(items) + .style(Color::White) + .highlight_style(Theme::selected()) + .highlight_symbol("> ") + .block( + Block::default() + .borders(Borders::ALL) + .border_style(Theme::block()) + .title("Installed packages"), + ); + + frame.render_stateful_widget(list, area, &mut self.list_state); + } +} + +impl Screen for InstalledPackagesScreen { + fn draw(&mut self, frame: &mut Frame, area: Rect, repo: &Portage) { + let constraints = [Constraint::Fill(1), Constraint::Length(3)]; + + let layout = Layout::vertical(constraints); + let [top, bottom] = area.layout(&layout); + + let split = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]).split(top); + + let split_vert = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(split[1]); + + 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]); + + let text = if self.search_popup.visible { + "(esc) to quit search | (enter) to search".to_string() + } else { + let mut main_key_hint = "(q) to quit | (j) down | (k) up | (/) to search".to_string(); + // if let (Some(current), Some(total)) = (app.current_search_index, app.search_indexes_len) + // { + // let _ = write!( + // main_key_hint, + // " | (n) for next search | (N) for previous search | Searches found: {} / {}", + // current + 1, + // total + // ); + // } + + main_key_hint + }; + + let key_hint = Span::styled(text, Theme::muted()); + + let key_notes_footer = Paragraph::new(Line::from(key_hint)).block( + Block::default() + .borders(Borders::ALL) + .border_style(Theme::block()), + ); + frame.render_widget(key_notes_footer, bottom); + + if self.search_popup.visible { + let mut popup_block = Block::default() + .title("Search") + .borders(Borders::ALL) + .border_type(ratatui::widgets::BorderType::Rounded) + .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()); + // } + // } + + // 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); + + frame.render_widget( + Block::default().style(Style::default().bg(Theme::BG).add_modifier(Modifier::DIM)), + frame.area(), + ); + frame.render_widget(Clear, area); + frame.render_widget(Block::default().style(Style::default().bg(Theme::BG)), area); + frame.render_widget(&self.search_popup.textarea, area); + } + } + + fn update(&mut self, key: KeyEvent) -> Option { + 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, + }; + } else { + match key.modifiers { + KeyModifiers::CONTROL => match key.code { + KeyCode::Char('d') => todo!("next page"), + KeyCode::Char('u') => todo!("prev page"), + _ => {} + }, + + _ => match key.code { + KeyCode::Char('q') => return Some(Signal::Quit), + 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('n') => todo!("Next search"), + KeyCode::Char('N') => todo!("prev search"), + + _ => {} + }, + } + } + + None + } +} diff --git a/src/screens/mod.rs b/src/screens/mod.rs new file mode 100644 index 0000000..db3be11 --- /dev/null +++ b/src/screens/mod.rs @@ -0,0 +1,2 @@ +pub mod installed_packages; +pub mod screen; diff --git a/src/screens/screen.rs b/src/screens/screen.rs new file mode 100644 index 0000000..a87329b --- /dev/null +++ b/src/screens/screen.rs @@ -0,0 +1,11 @@ +use ratatui::{Frame, crossterm::event::KeyEvent, layout::Rect}; + +use crate::{ + actions::{Action, 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; +} diff --git a/src/widgets/helpers.rs b/src/widgets/helpers.rs new file mode 100644 index 0000000..0bde326 --- /dev/null +++ b/src/widgets/helpers.rs @@ -0,0 +1,65 @@ +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + text::{Line, Span}, +}; + +use crate::gentoo::InstalledPackage; + +pub 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 +} + +pub fn homepage_lines(pkg: &InstalledPackage, bold_style: Style) -> Vec> { + 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()) { + lines.push(Line::from(vec![ + Span::raw(" - "), // indentation + Span::styled( + url, + Style::default() + .fg(Color::LightBlue) + .add_modifier(Modifier::UNDERLINED), + ), + ])); + } + + lines +} + +pub fn human_size(bytes: usize) -> String { + const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"]; + + let mut size = bytes as f64; + let mut unit = 0; + + while size >= 1024.0 && unit < UNITS.len() - 1 { + size /= 1024.0; + unit += 1; + } + + if unit == 0 { + format!("{} {}", bytes, UNITS[unit]) + } else { + format!("{:.1} {}", size, UNITS[unit]) + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs new file mode 100644 index 0000000..66f4b70 --- /dev/null +++ b/src/widgets/mod.rs @@ -0,0 +1,3 @@ +pub mod helpers; +pub mod package_metadata; +pub mod use_flags; diff --git a/src/widgets/package_metadata.rs b/src/widgets/package_metadata.rs new file mode 100644 index 0000000..e4f83bb --- /dev/null +++ b/src/widgets/package_metadata.rs @@ -0,0 +1,75 @@ +use ratatui::{ + Frame, + layout::Rect, + text::{Line, Span}, + widgets::{Block, Borders, Paragraph, Wrap}, +}; + +use crate::{ + app::App, + gentoo::InstalledPackage, + theme::Theme, + widgets::helpers::{homepage_lines, human_size}, +}; + +pub fn render_package_metadata(frame: &mut Frame, area: Rect, pkg: &InstalledPackage) { + 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")), + ]); + + let version = Line::from_iter([ + Span::styled("Version: ", bold_style), + Span::raw(&pkg.version), + ]); + + let repository = Line::from_iter([ + Span::styled("Repository: ", bold_style), + Span::raw(&pkg.metadata.repository), + ]); + + let license = Line::from_iter([ + Span::styled("License: ", bold_style), + Span::raw(pkg.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")), + ]); + + let size = Line::from_iter([ + Span::styled("Size: ", bold_style), + Span::raw(human_size(pkg.size)), + ]); + + let build_time = Line::from_iter([ + Span::styled("Build time: ", bold_style), + Span::raw(pkg.build_time.to_string()), + ]); + + let mut lines = vec![ + maintainer, + version, + repository, + build_time, + license, + description, + size, + ]; + + let mut homepage = homepage_lines(pkg, bold_style); + lines.append(&mut homepage); + + let paragraph = Paragraph::new(lines) + .block( + Block::default() + .title("Metadata") + .borders(Borders::ALL) + .border_style(Theme::block()), + ) + .wrap(Wrap { trim: false }); + + frame.render_widget(paragraph, area); +} diff --git a/src/widgets/use_flags.rs b/src/widgets/use_flags.rs new file mode 100644 index 0000000..8e2ecf4 --- /dev/null +++ b/src/widgets/use_flags.rs @@ -0,0 +1,43 @@ +use ratatui::{ + Frame, + layout::Rect, + style::{Color, Modifier}, + widgets::{Block, Borders, List, ListItem, ListState}, +}; + +use crate::{app::App, gentoo::InstalledPackage, theme::Theme}; + +pub fn render_use_flags(frame: &mut Frame, area: Rect, package: &InstalledPackage) { + let items = package.iuse.iter().map(|x| { + let text_style = if package.enabled_use_flags.contains(&x.name) { + Theme::success() + } else if x.default { + Theme::info() + } else { + Theme::error() + }; + + let text_style = if x.default { + text_style.add_modifier(Modifier::ITALIC) + } else { + text_style + }; + + ListItem::new(x.name.clone()).style(text_style) + }); + let list = List::new(items) + .style(Color::White) + .scroll_padding(1) + .direction(ratatui::widgets::ListDirection::TopToBottom) + .block( + Block::default() + .borders(Borders::ALL) + .title("USE Flags") + .border_style(Theme::block()), + ); + + let mut list_state = ListState::default(); + list_state.select_first(); + + frame.render_stateful_widget(list, area, &mut list_state); +} -- cgit 1.3.1