rustpm 0.2.2

A fast, friendly APT frontend with kernel, desktop, and sources management
use crossterm::event::KeyCode;
use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    widgets::{Block, Borders, Cell, Paragraph, Row, Table, TableState},
    Frame,
};

use crate::desktop::manager::InstalledStatus;

pub struct DesktopPanel {
    pub desktops: Vec<InstalledStatus>,
    pub state: TableState,
}

impl DesktopPanel {
    pub fn new(desktops: Vec<InstalledStatus>) -> Self {
        let mut state = TableState::default();
        if !desktops.is_empty() {
            state.select(Some(0));
        }
        Self { desktops, state }
    }

    pub fn handle_key(&mut self, code: KeyCode) {
        match code {
            KeyCode::Char('j') | KeyCode::Down => self.next(1),
            KeyCode::Char('k') | KeyCode::Up => self.prev(1),
            KeyCode::PageDown => self.next(10),
            KeyCode::PageUp => self.prev(10),
            _ => {}
        }
    }

    fn next(&mut self, step: usize) {
        let len = self.desktops.len();
        if len == 0 { return; }
        let i = self.state.selected().map_or(0, |i| (i + step).min(len - 1));
        self.state.select(Some(i));
    }

    fn prev(&mut self, step: usize) {
        let i = self.state.selected().map_or(0, |i| i.saturating_sub(step));
        self.state.select(Some(i));
    }

    pub fn render(&self, f: &mut Frame, area: Rect) {
        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Min(0), Constraint::Length(3)])
            .split(area);

        let rows: Vec<Row> = self
            .desktops
            .iter()
            .map(|d| {
                let check = if d.installed { "" } else { " " };
                let pkgs = d.profile.packages.join(", ");
                let status = if d.installed { "installed" } else { "available" };

                let style = if d.installed {
                    Style::default().fg(Color::Green)
                } else {
                    Style::default().fg(Color::White)
                };

                Row::new(vec![
                    Cell::from(check),
                    Cell::from(d.profile.display_name),
                    Cell::from(pkgs),
                    Cell::from(d.profile.display_manager),
                    Cell::from(status),
                ])
                .style(style)
            })
            .collect();

        let widths = [
            Constraint::Length(3),
            Constraint::Length(18),
            Constraint::Min(30),
            Constraint::Length(12),
            Constraint::Length(12),
        ];

        let table = Table::new(rows, widths)
            .header(
                Row::new(vec!["", "Desktop", "Packages", "Display Mgr", "Status"])
                    .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
            )
            .block(Block::default().borders(Borders::ALL).title(" Desktop Environments "))
            .row_highlight_style(Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD))
            .highlight_symbol("");

        let mut state = self.state.clone();
        f.render_stateful_widget(table, chunks[0], &mut state);

        let actions = Paragraph::new("  Enter: install/remove  s: switch default DM  q: back  [?] Help")
            .block(Block::default().borders(Borders::ALL).title(" Actions "));
        f.render_widget(actions, chunks[1]);
    }
}