aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpablu <me@pablu.de>2026-06-03 10:47:59 +0200
committerpablu <me@pablu.de>2026-06-03 10:47:59 +0200
commitc5a634bd5408ea5174035abf8713953e7fc48e08 (patch)
treef41267354ca791dbe437a206935f5004e29a4a97
parent92b39b19a1c50f9462f7b0b99e8013abf672c533 (diff)
Implement PackageLoader, just for available packages right now
-rw-r--r--src/app.rs54
-rw-r--r--src/background_loader.rs47
-rw-r--r--src/gentoo/portage.rs6
-rw-r--r--src/main.rs15
-rw-r--r--src/screens/dashboard.rs49
-rw-r--r--src/screens/installed_packages.rs9
-rw-r--r--src/screens/screen.rs4
7 files changed, 159 insertions, 25 deletions
diff --git a/src/app.rs b/src/app.rs
index c423e50..6819bf0 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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>;
}