aboutsummaryrefslogtreecommitdiff
path: root/src/screens
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens')
-rw-r--r--src/screens/dashboard.rs144
-rw-r--r--src/screens/installed_packages.rs148
-rw-r--r--src/screens/mod.rs1
-rw-r--r--src/screens/screen.rs7
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>;
}