diff options
| author | pablu <me@pablu.de> | 2026-06-03 10:47:59 +0200 |
|---|---|---|
| committer | pablu <me@pablu.de> | 2026-06-03 10:47:59 +0200 |
| commit | c5a634bd5408ea5174035abf8713953e7fc48e08 (patch) | |
| tree | f41267354ca791dbe437a206935f5004e29a4a97 | |
| parent | 92b39b19a1c50f9462f7b0b99e8013abf672c533 (diff) | |
Implement PackageLoader, just for available packages right now
| -rw-r--r-- | src/app.rs | 54 | ||||
| -rw-r--r-- | src/background_loader.rs | 47 | ||||
| -rw-r--r-- | src/gentoo/portage.rs | 6 | ||||
| -rw-r--r-- | src/main.rs | 15 | ||||
| -rw-r--r-- | src/screens/dashboard.rs | 49 | ||||
| -rw-r--r-- | src/screens/installed_packages.rs | 9 | ||||
| -rw-r--r-- | src/screens/screen.rs | 4 |
7 files changed, 159 insertions, 25 deletions
@@ -1,4 +1,3 @@ -use color_eyre::eyre::Ok; use ratatui::{ Frame, crossterm::event::{self, Event, KeyCode}, @@ -9,6 +8,7 @@ use ratatui::{ }; use crate::{ + background_loader::PackageLoader, gentoo::portage::Portage, screens::{ dashboard::DashboardScreen, installed_packages::InstalledPackagesScreen, screen::Screen, @@ -25,6 +25,14 @@ pub enum ViewState { AvailablePackages = 2, } +#[derive(Debug, Clone, Copy)] +pub enum LoadingState { + Idle, + Loading, + Complete, + Error, +} + #[derive(Debug)] pub struct App { portage: Portage, @@ -33,6 +41,10 @@ pub struct App { dashboard_screen: DashboardScreen, pub view: ViewState, + + available_loader: Option<PackageLoader>, + pub loading_state: LoadingState, + pub loading_error: Option<String>, } impl App { @@ -44,6 +56,10 @@ impl App { installed_package_screen: InstalledPackagesScreen::default(), view: ViewState::Dashboard, + + available_loader: None, + loading_state: LoadingState::Idle, + loading_error: None, } } @@ -58,10 +74,11 @@ impl App { match self.view { ViewState::InstalledPackages => { self.installed_package_screen - .draw(frame, rest, &self.portage); + .draw(frame, rest, &self.portage, &self.loading_state); } ViewState::Dashboard => { - self.dashboard_screen.draw(frame, rest, &self.portage); + self.dashboard_screen + .draw(frame, rest, &self.portage, &self.loading_state); } ViewState::AvailablePackages => { frame.render_widget(Text::from("NOT IMPLEMENTED"), rest); @@ -70,7 +87,7 @@ impl App { } pub fn update(&mut self) -> color_eyre::Result<bool> { - if let Event::Key(key) = event::read()? { + if let Ok(Event::Key(key)) = event::read() { let signal = match self.view { crate::app::ViewState::InstalledPackages => { self.installed_package_screen.update(key, &self.portage) @@ -95,6 +112,35 @@ impl App { Ok(false) } + pub fn poll_available_packages(&mut self) { + if let Some(loader) = &self.available_loader { + if let Some(msg) = loader.try_recv() { + match msg { + crate::background_loader::LoaderMessage::Loading => { + self.loading_state = LoadingState::Loading; + } + crate::background_loader::LoaderMessage::Complete(packages) => { + self.portage.available = packages; + self.loading_state = LoadingState::Complete; + + // TODO: Join loader thread with main thread again + self.available_loader = None; + } + crate::background_loader::LoaderMessage::Error(e) => { + self.loading_error = Some(e); + self.loading_state = LoadingState::Error; + self.available_loader = None; + } + } + } + } + } + + pub fn start_available_packages_load(&mut self) { + self.available_loader = Some(PackageLoader::spawn()); + self.loading_state = LoadingState::Loading; + } + pub fn cycle_current_tab(&mut self) { match self.view { ViewState::Dashboard => self.view = ViewState::InstalledPackages, diff --git a/src/background_loader.rs b/src/background_loader.rs new file mode 100644 index 0000000..ea9d331 --- /dev/null +++ b/src/background_loader.rs @@ -0,0 +1,47 @@ +use std::{ + sync::mpsc::{Receiver, channel}, + thread, +}; + +use crate::gentoo::{package::Package, portage_loader::load_available_packages}; + +pub enum LoaderMessage { + Loading, + Complete(Vec<Package>), + Error(String), +} + +#[derive(Debug)] +pub struct PackageLoader { + receiver: Receiver<LoaderMessage>, + thread_handle: thread::JoinHandle<()>, +} + +impl PackageLoader { + pub fn spawn() -> Self { + let (tx, rx) = channel(); + let thread_handle = thread::spawn(move || { + let _ = tx.send(LoaderMessage::Loading); + + match load_available_packages() { + Ok(packages) => { + let _ = tx.send(LoaderMessage::Complete(packages)); + } + Err(e) => { + let _ = tx.send(LoaderMessage::Error(e.to_string())); + } + } + }); + + PackageLoader { + receiver: rx, + thread_handle, + } + } + + pub fn try_recv(&self) -> Option<LoaderMessage> { + self.receiver.try_recv().ok() + } + + //TODO: join thread back to main thread after packages were loaded +} diff --git a/src/gentoo/portage.rs b/src/gentoo/portage.rs index 779f746..935c1ce 100644 --- a/src/gentoo/portage.rs +++ b/src/gentoo/portage.rs @@ -7,9 +7,9 @@ use crate::gentoo::{ #[derive(Debug, Default)] pub struct Portage { - installed: Vec<InstalledPackage>, - world_set: HashSet<PackageKey>, - available: Vec<Package>, + pub installed: Vec<InstalledPackage>, + pub world_set: HashSet<PackageKey>, + pub available: Vec<Package>, } impl Portage { diff --git a/src/main.rs b/src/main.rs index eecbffc..3f48139 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod app; +mod background_loader; mod gentoo; mod screens; mod signal; @@ -8,12 +9,14 @@ mod widgets; use std::{ io::{self, Stdout, stdout}, panic::{set_hook, take_hook}, + time::Duration, }; use clap::command; use ratatui::{ Terminal, crossterm::{ + event::poll, execute, terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }, @@ -48,6 +51,7 @@ fn main() -> color_eyre::Result<()> { let p = Portage::new(installed_packages, world_packages, vec![]); let mut app = App::new(p); + app.start_available_packages_load(); run(tui, &mut app)?; @@ -56,10 +60,15 @@ fn main() -> color_eyre::Result<()> { fn run(mut terminal: Terminal<CrosstermBackend<Stdout>>, app: &mut App) -> color_eyre::Result<()> { loop { + app.poll_available_packages(); + terminal.draw(|frame| app.draw(frame))?; - let should_exit = app.update()?; - if should_exit { - break; + + if poll(Duration::from_millis(50))? { + let should_exit = app.update()?; + if should_exit { + break; + } } } diff --git a/src/screens/dashboard.rs b/src/screens/dashboard.rs index 87d2e81..1fcb242 100644 --- a/src/screens/dashboard.rs +++ b/src/screens/dashboard.rs @@ -6,7 +6,7 @@ use ratatui::{ }; use crate::{ - gentoo::Portage, screens::screen::Screen, signal::Signal, theme::Theme, + app::LoadingState, gentoo::Portage, screens::screen::Screen, signal::Signal, theme::Theme, widgets::helpers::human_size, }; @@ -35,6 +35,7 @@ impl Screen for DashboardScreen { frame: &mut ratatui::Frame, area: ratatui::prelude::Rect, repo: &crate::gentoo::Portage, + loading_state: &LoadingState, ) { let logo = " ██████╗ ██╗ ██╗██╗███████╗██╗ ██╗ @@ -76,17 +77,6 @@ impl Screen for DashboardScreen { 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()); @@ -130,6 +120,41 @@ impl Screen for DashboardScreen { frame.render_widget(installed_size, splits[0]); frame.render_widget(distfiles_cache, splits[1]); frame.render_widget(portage_news, splits[2]); + + let keys_text = "(q) to quit | (tab) to cycle tabs"; + + let loading_text = match loading_state { + LoadingState::Loading => "⟳ Loading available packages...", + LoadingState::Error => "⚠ Failed to load available packages", + LoadingState::Complete => "✓ Available packages loaded", + LoadingState::Idle => "", + }; + + let full_text = if !loading_text.is_empty() { + // Calculate spacing to push loading text to the right + vec![Line::from(vec![ + Span::styled(keys_text, Theme::muted()), + Span::raw(" | "), + Span::styled( + loading_text, + match loading_state { + crate::app::LoadingState::Loading => Theme::muted(), + crate::app::LoadingState::Error => Theme::error(), + crate::app::LoadingState::Complete => Theme::success(), + crate::app::LoadingState::Idle => Theme::muted(), + }, + ), + ])] + } else { + vec![Line::from(Span::styled(keys_text, Theme::muted()))] + }; + + let key_notes = Paragraph::new(full_text).block( + Block::default() + .borders(Borders::ALL) + .border_style(Theme::block()), + ); + frame.render_widget(key_notes, key_hints); } fn update(&mut self, key: ratatui::crossterm::event::KeyEvent, _: &Portage) -> Option<Signal> { diff --git a/src/screens/installed_packages.rs b/src/screens/installed_packages.rs index cc2f3cb..4421837 100644 --- a/src/screens/installed_packages.rs +++ b/src/screens/installed_packages.rs @@ -9,6 +9,7 @@ use ratatui::{ use ratatui_textarea::TextArea; use crate::{ + app::LoadingState, gentoo::{InstalledPackage, Portage, package::PackageKey}, screens::screen::Screen, signal::Signal, @@ -130,7 +131,13 @@ impl Default for InstalledPackagesScreen { } impl Screen for InstalledPackagesScreen { - fn draw(&mut self, frame: &mut Frame, area: Rect, repo: &Portage) { + fn draw( + &mut self, + frame: &mut Frame, + area: Rect, + repo: &Portage, + _loading_state: &LoadingState, + ) { let constraints = [Constraint::Fill(1), Constraint::Length(3)]; let layout = Layout::vertical(constraints); diff --git a/src/screens/screen.rs b/src/screens/screen.rs index 330e4c9..8e7449c 100644 --- a/src/screens/screen.rs +++ b/src/screens/screen.rs @@ -1,8 +1,8 @@ use ratatui::{Frame, crossterm::event::KeyEvent, layout::Rect}; -use crate::{gentoo::Portage, signal::Signal}; +use crate::{app::LoadingState, gentoo::Portage, signal::Signal}; pub(crate) trait Screen { - fn draw(&mut self, frame: &mut Frame, area: Rect, repo: &Portage); + fn draw(&mut self, frame: &mut Frame, area: Rect, repo: &Portage, loading_state: &LoadingState); fn update(&mut self, key: KeyEvent, repo: &Portage) -> Option<Signal>; } |
