diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/actions/mod.rs | 3 | ||||
| -rw-r--r-- | src/app.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/screens/dashboard.rs | 11 | ||||
| -rw-r--r-- | src/screens/installed_packages.rs | 4 | ||||
| -rw-r--r-- | src/screens/screen.rs | 2 | ||||
| -rw-r--r-- | src/signal.rs (renamed from src/actions/signal.rs) | 0 | ||||
| -rw-r--r-- | src/ui.rs | 415 | ||||
| -rw-r--r-- | src/widgets/package_metadata.rs | 1 |
9 files changed, 10 insertions, 430 deletions
diff --git a/src/actions/mod.rs b/src/actions/mod.rs deleted file mode 100644 index 39af0f6..0000000 --- a/src/actions/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod signal; - -pub use signal::Signal; @@ -9,11 +9,11 @@ use ratatui::{ }; use crate::{ - actions::Signal, gentoo::portage::Portage, screens::{ dashboard::DashboardScreen, installed_packages::InstalledPackagesScreen, screen::Screen, }, + signal::Signal, theme::Theme, widgets::tabs::render_tab, }; diff --git a/src/main.rs b/src/main.rs index 120721b..5a8940b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ -mod actions; mod app; mod gentoo; mod screens; +mod signal; mod theme; mod widgets; diff --git a/src/screens/dashboard.rs b/src/screens/dashboard.rs index e03624d..9a6c277 100644 --- a/src/screens/dashboard.rs +++ b/src/screens/dashboard.rs @@ -5,7 +5,10 @@ use ratatui::{ widgets::{Block, Borders, Paragraph}, }; -use crate::{actions::Signal, screens::screen::Screen, theme::Theme, widgets::helpers::human_size}; +use crate::{ + gentoo::Portage, screens::screen::Screen, signal::Signal, theme::Theme, + widgets::helpers::human_size, +}; #[derive(Debug, Default)] pub struct DashboardScreen {} @@ -129,11 +132,7 @@ impl Screen for DashboardScreen { frame.render_widget(portage_news, splits[2]); } - fn update( - &mut self, - key: ratatui::crossterm::event::KeyEvent, - _: &crate::gentoo::Portage, - ) -> Option<crate::actions::Signal> { + fn update(&mut self, key: ratatui::crossterm::event::KeyEvent, _: &Portage) -> Option<Signal> { match key.code { KeyCode::Char('q') => Some(Signal::Quit), KeyCode::Tab => Some(Signal::CycleTab), diff --git a/src/screens/installed_packages.rs b/src/screens/installed_packages.rs index 48b0513..6175e08 100644 --- a/src/screens/installed_packages.rs +++ b/src/screens/installed_packages.rs @@ -9,9 +9,9 @@ use ratatui::{ use ratatui_textarea::TextArea; use crate::{ - actions::Signal, gentoo::{InstalledPackage, Portage}, screens::screen::Screen, + signal::Signal, theme::Theme, widgets::{ helpers::search_popup_rect, package_metadata::render_package_metadata, @@ -142,7 +142,7 @@ impl Screen for InstalledPackagesScreen { 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(); + let 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!( diff --git a/src/screens/screen.rs b/src/screens/screen.rs index f299578..330e4c9 100644 --- a/src/screens/screen.rs +++ b/src/screens/screen.rs @@ -1,6 +1,6 @@ use ratatui::{Frame, crossterm::event::KeyEvent, layout::Rect}; -use crate::{actions::Signal, gentoo::Portage}; +use crate::{gentoo::Portage, signal::Signal}; pub(crate) trait Screen { fn draw(&mut self, frame: &mut Frame, area: Rect, repo: &Portage); diff --git a/src/actions/signal.rs b/src/signal.rs index 1210e17..1210e17 100644 --- a/src/actions/signal.rs +++ b/src/signal.rs diff --git a/src/ui.rs b/src/ui.rs deleted file mode 100644 index fa515ce..0000000 --- a/src/ui.rs +++ /dev/null @@ -1,415 +0,0 @@ -use std::fmt::Write as _; - -use ratatui::{ - Frame, - layout::{Alignment, Constraint, Direction, Layout, Spacing}, - prelude::Rect, - style::{Color, Modifier, Style, Stylize}, - symbols, - text::{Line, Span, Text}, - widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Tabs, Wrap}, -}; - -use crate::{ - app::{App, ViewState}, - gentoo::InstalledPackage, - theme::Theme, -}; - -pub fn ui(frame: &mut Frame, app: &mut App) { - 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 app.view { - crate::app::ViewState::Dashboard => render_dashboard(frame, rest, app), - crate::app::ViewState::InstalledPackages => render_installed_packages(frame, rest, app), - crate::app::ViewState::AvailablePackages => { - frame.render_widget(Text::from("NOT IMPLEMENTED"), rest) - } - } -} - -fn render_tab(frame: &mut Frame, area: Rect, selected_tab: &ViewState) { - let selected_index = match selected_tab { - ViewState::Dashboard => 0, - ViewState::InstalledPackages => 1, - ViewState::AvailablePackages => 2, - }; - - let tabs = Tabs::new(vec!["Dashboard", "Installed", "Browse"]) - .block(Block::default().borders(Borders::ALL).style(Theme::block())) - .highlight_style(Theme::success()) - .select(selected_index) - .divider(symbols::DOT) - .padding(" ", " "); - frame.render_widget(tabs, area); -} - -fn render_dashboard(frame: &mut Frame, area: Rect, app: &App) { - 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 = app.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 = app.world_count().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(app.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 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]) - } -} - -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) -} - -fn render_installed_packages(frame: &mut Frame, area: Rect, app: &mut App) { - 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]); - - render_packages(frame, split[0], app); - render_use_flags(frame, split_vert[0], app); - render_package_metadata(frame, split_vert[1], app); - - let text = if app.showing_search_window { - "(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 app.showing_search_window { - 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()); - } - } - - app.textarea.set_block(popup_block); - app.textarea.set_style(Theme::muted()); - app.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(&app.textarea, area); - } -} - -fn render_package_metadata(frame: &mut Frame, area: Rect, app: &App) { - let pkg = app.current_package(); - - 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); -} - -fn homepage_lines(pkg: &InstalledPackage, 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()) { - lines.push(Line::from(vec![ - Span::raw(" - "), // indentation - Span::styled( - url, - Style::default() - .fg(Color::LightBlue) - .add_modifier(Modifier::UNDERLINED), - ), - ])); - } - - lines -} - -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) { - let items: Vec<String> = app - .installed_packages() - .iter() - .map(|x| x.atom.qualified_name()) - .collect(); - - 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 app.list_state); -} - -fn render_use_flags(frame: &mut Frame, area: Rect, app: &App) { - let selected_package = app.current_package(); - - let items = selected_package.iuse.iter().map(|x| { - let text_style = if selected_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); -} diff --git a/src/widgets/package_metadata.rs b/src/widgets/package_metadata.rs index 7377cbc..ee46f62 100644 --- a/src/widgets/package_metadata.rs +++ b/src/widgets/package_metadata.rs @@ -6,7 +6,6 @@ use ratatui::{ }; use crate::{ - app::App, gentoo::InstalledPackage, theme::Theme, widgets::helpers::{homepage_lines, human_size}, |
