radiate-ui 1.3.1

A Rust library for genetic algorithms and artificial evolution.
Documentation
use super::nav::DashboardTab;
use radiate_engines::species::SpeciesId;
use radiate_utils::SmallStr;
use ratatui::widgets::{ScrollbarState, TableState};

pub struct TableStates {
    pub time: AppTableState<SmallStr>,
    pub stats: AppTableState<SmallStr>,
    pub dist: AppTableState<SmallStr>,
    pub species: AppTableState<SpeciesId>,
    pub log: AppTableState<usize>,
    pub events: AppTableState<usize>,
}

impl TableStates {
    pub fn move_down(&mut self, tab: DashboardTab) {
        match tab {
            DashboardTab::Stats => Self::scroll_down(&mut self.stats),
            DashboardTab::Time => Self::scroll_down(&mut self.time),
            DashboardTab::Distribution => Self::scroll_down(&mut self.dist),
            DashboardTab::Species => Self::scroll_down(&mut self.species),
            DashboardTab::Log | DashboardTab::Front => Self::scroll_down(&mut self.log),
            DashboardTab::Events => Self::scroll_down(&mut self.events),
        }
    }

    pub fn move_up(&mut self, tab: DashboardTab) {
        match tab {
            DashboardTab::Stats => Self::scroll_up(&mut self.stats),
            DashboardTab::Time => Self::scroll_up(&mut self.time),
            DashboardTab::Distribution => Self::scroll_up(&mut self.dist),
            DashboardTab::Species => Self::scroll_up(&mut self.species),
            DashboardTab::Log | DashboardTab::Front => Self::scroll_up(&mut self.log),
            DashboardTab::Events => Self::scroll_up(&mut self.events),
        }
    }

    pub fn move_page_down(&mut self, tab: DashboardTab) {
        match tab {
            DashboardTab::Stats => Self::scroll_page_down(&mut self.stats),
            DashboardTab::Time => Self::scroll_page_down(&mut self.time),
            DashboardTab::Distribution => Self::scroll_page_down(&mut self.dist),
            DashboardTab::Species => Self::scroll_page_down(&mut self.species),
            DashboardTab::Log | DashboardTab::Front => Self::scroll_page_down(&mut self.log),
            DashboardTab::Events => Self::scroll_page_down(&mut self.events),
        }
    }

    pub fn move_page_up(&mut self, tab: DashboardTab) {
        match tab {
            DashboardTab::Stats => Self::scroll_page_up(&mut self.stats),
            DashboardTab::Time => Self::scroll_page_up(&mut self.time),
            DashboardTab::Distribution => Self::scroll_page_up(&mut self.dist),
            DashboardTab::Species => Self::scroll_page_up(&mut self.species),
            DashboardTab::Log | DashboardTab::Front => Self::scroll_page_up(&mut self.log),
            DashboardTab::Events => Self::scroll_page_up(&mut self.events),
        }
    }

    pub fn move_to_top(&mut self, tab: DashboardTab) {
        match tab {
            DashboardTab::Stats => Self::scroll_to(&mut self.stats, 0),
            DashboardTab::Time => Self::scroll_to(&mut self.time, 0),
            DashboardTab::Distribution => Self::scroll_to(&mut self.dist, 0),
            DashboardTab::Species => Self::scroll_to(&mut self.species, 0),
            DashboardTab::Log | DashboardTab::Front => Self::scroll_to(&mut self.log, 0),
            DashboardTab::Events => Self::scroll_to(&mut self.events, 0),
        }
    }

    pub fn move_to_bottom(&mut self, tab: DashboardTab) {
        match tab {
            DashboardTab::Stats => {
                let last = self.stats.row_count.saturating_sub(1);
                Self::scroll_to(&mut self.stats, last);
            }
            DashboardTab::Time => {
                let last = self.time.row_count.saturating_sub(1);
                Self::scroll_to(&mut self.time, last);
            }
            DashboardTab::Distribution => {
                let last = self.dist.row_count.saturating_sub(1);
                Self::scroll_to(&mut self.dist, last);
            }
            DashboardTab::Species => {
                let last = self.species.row_count.saturating_sub(1);
                Self::scroll_to(&mut self.species, last);
            }
            DashboardTab::Log | DashboardTab::Front => {
                let last = self.log.row_count.saturating_sub(1);
                Self::scroll_to(&mut self.log, last);
            }
            DashboardTab::Events => {
                let last = self.events.row_count.saturating_sub(1);
                Self::scroll_to(&mut self.events, last);
            }
        }
    }

    pub fn selected_metric(&self, tab: DashboardTab) -> Option<&str> {
        match tab {
            DashboardTab::Time => self.time.selected_value.as_deref(),
            DashboardTab::Stats => self.stats.selected_value.as_deref(),
            DashboardTab::Distribution => self.dist.selected_value.as_deref(),
            DashboardTab::Species
            | DashboardTab::Log
            | DashboardTab::Front
            | DashboardTab::Events => None,
        }
    }

    fn scroll_to<T>(t: &mut AppTableState<T>, target: usize) {
        if t.row_count == 0 {
            return;
        }
        let i = target.min(t.row_count - 1);
        t.state.select(Some(i));
        t.selected_row = i;
        t.scroll_bar = t.scroll_bar.as_mut().map(|sb| sb.position(i));
    }

    fn scroll_page_down<T>(t: &mut AppTableState<T>) {
        if t.row_count == 0 {
            return;
        }
        let current = t.state.selected().unwrap_or(0);
        let page = t.visible_rows.max(1);
        let next = (current + page).min(t.row_count - 1);
        Self::scroll_to(t, next);
    }

    fn scroll_page_up<T>(t: &mut AppTableState<T>) {
        if t.row_count == 0 {
            return;
        }
        let current = t.state.selected().unwrap_or(0);
        let page = t.visible_rows.max(1);
        let next = current.saturating_sub(page);
        Self::scroll_to(t, next);
    }

    fn scroll_down<T>(t: &mut AppTableState<T>) {
        if t.row_count == 0 {
            return;
        }
        if let Some(i) = t.state.selected() {
            let next = if i + 1 >= t.row_count { 0 } else { i + 1 };
            t.state.select(Some(next));
            t.selected_row = next;
            t.scroll_bar = t.scroll_bar.as_mut().map(|sb| sb.position(next));
        } else {
            t.state.select(Some(0));
            t.selected_row = 0;
            t.scroll_bar = t.scroll_bar.as_mut().map(|sb| sb.position(0));
        }
    }

    fn scroll_up<T>(t: &mut AppTableState<T>) {
        if t.row_count == 0 {
            return;
        }
        if let Some(i) = t.state.selected() {
            let next = if i == 0 { t.row_count - 1 } else { i - 1 };
            t.state.select(Some(next));
            t.selected_row = next;
            t.scroll_bar = t.scroll_bar.as_mut().map(|sb| sb.position(next));
        } else {
            let last = t.row_count - 1;
            t.state.select(Some(last));
            t.selected_row = last;
            t.scroll_bar = t.scroll_bar.as_mut().map(|sb| sb.position(last));
        }
    }
}

impl Default for TableStates {
    fn default() -> Self {
        Self {
            time: AppTableState::new(),
            stats: AppTableState::new(),
            dist: AppTableState::new(),
            species: AppTableState::new(),
            log: AppTableState::new(),
            events: AppTableState::new(),
        }
    }
}

pub struct AppTableState<T> {
    pub state: TableState,
    pub scroll_bar: Option<ScrollbarState>,
    pub selected_value: Option<T>,
    pub selected_row: usize,
    pub row_count: usize,
    pub prev_row_count: usize,
    pub visible_rows: usize,
}

impl<T> AppTableState<T> {
    pub fn new() -> Self {
        Self {
            state: TableState::default(),
            scroll_bar: None,
            selected_row: 0,
            row_count: 0,
            prev_row_count: 0,
            selected_value: None,
            visible_rows: 0,
        }
    }

    pub fn update_rows<K, F>(&mut self, items: &[K], func: F)
    where
        F: Fn(&K) -> T,
    {
        let current_len = items.len();
        self.prev_row_count = self.row_count;
        self.row_count = current_len;

        if (self.prev_row_count == 0 && self.row_count > 0)
            || (self.selected_row < self.prev_row_count && self.selected_row >= self.row_count)
        {
            self.selected_row = 0;
            self.state.select(Some(0));
        }

        if self.selected_row >= current_len && current_len > 0 {
            self.selected_row = current_len - 1;
            self.state.select(Some(self.selected_row));
        }

        self.selected_value = items.get(self.selected_row).map(&func);
    }
}