slite 0.0.1-dev

Declarative migrations and schema management for SQLite
Documentation
use tui::{
    style::{Color, Modifier, Style},
    text::Text,
    widgets::{Block, List, ListItem, ListState, StatefulWidget},
};

#[derive(Debug, Clone)]
pub struct Objects<'a> {
    block: Block<'a>,
}

impl<'a> Objects<'a> {
    pub fn new(block: Block<'a>) -> Self {
        Self { block }
    }
}

impl<'a> StatefulWidget for Objects<'a> {
    type State = ObjectsState;

    fn render(
        self,
        area: tui::layout::Rect,
        buf: &mut tui::buffer::Buffer,
        state: &mut Self::State,
    ) {
        let items: Vec<ListItem> = state.objects.iter().map(|i| i.clone().into()).collect();

        tui::widgets::StatefulWidget::render(
            List::new(items)
                .highlight_style(Style::default().fg(Color::Green).bg(Color::Black))
                .block(self.block),
            area,
            buf,
            &mut state.state,
        );
    }
}

#[derive(Debug, Clone)]
pub enum ListItemType {
    Entry(String),
    Header(String),
}

impl From<ListItemType> for ListItem<'static> {
    fn from(val: ListItemType) -> Self {
        match val {
            ListItemType::Entry(title) => {
                ListItem::new(Text::styled("  ".to_owned() + &title, Style::default()))
            }
            ListItemType::Header(title) => ListItem::new(Text::styled(
                title,
                Style::default()
                    .fg(Color::Blue)
                    .add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
            )),
        }
    }
}

#[derive(Debug, Clone)]
pub struct ObjectsState {
    state: ListState,
    object_view_width: usize,
    objects: Vec<ListItemType>,
    has_items: bool,
    adjusted_index: i32,
    adjusted_size: i32,
}

impl ObjectsState {
    pub fn new(tables: Vec<String>, indexes: Vec<String>) -> ObjectsState {
        let mut list_items = vec![];
        let mut has_items = false;

        has_items |= !tables.is_empty();
        list_items.push(ListItemType::Header("Tables".to_owned()));

        list_items.extend(tables.into_iter().map(ListItemType::Entry));

        has_items |= !indexes.is_empty();
        list_items.push(ListItemType::Header("Indexes".to_owned()));

        list_items.extend(indexes.into_iter().map(ListItemType::Entry));

        let max_length = list_items
            .iter()
            .map(|o| match o {
                ListItemType::Header(header) => header.len(),
                ListItemType::Entry(title) => title.len()
            }+5)
            .max()
            .unwrap_or_default();

        let mut state = ListState::default();
        if has_items {
            state.select(Some(1));
        }
        ObjectsState {
            state,
            adjusted_size: list_items.len() as i32 - 2,
            objects: list_items,
            object_view_width: max_length,
            has_items,
            adjusted_index: 0,
        }
    }

    pub fn next(&mut self) {
        if !self.has_items {
            return;
        }
        self.adjusted_index = (self.adjusted_index + 1).rem_euclid(self.adjusted_size);

        let mut next_index = (self.state.selected().expect("Item not selected") as i32 + 1)
            .rem_euclid(self.objects.len() as i32);
        let real_index = loop {
            match self.objects.get(next_index as usize) {
                Some(ListItemType::Entry { .. }) => {
                    break next_index;
                }
                Some(ListItemType::Header(_)) => {
                    next_index = (next_index + 1).rem_euclid(self.objects.len() as i32);
                }
                None => unreachable!(),
            }
        };

        self.state.select(Some(real_index as usize));
    }

    pub fn previous(&mut self) {
        if !self.has_items {
            return;
        }
        self.adjusted_index = (self.adjusted_index - 1).rem_euclid(self.adjusted_size);

        let mut next_index = (self.state.selected().expect("Item not selected") as i32 - 1)
            .rem_euclid(self.objects.len() as i32);
        let real_index = loop {
            match self.objects.get(next_index as usize) {
                Some(ListItemType::Entry { .. }) => {
                    break next_index;
                }
                Some(ListItemType::Header(_)) => {
                    next_index = (next_index - 1).rem_euclid(self.objects.len() as i32);
                }
                None => unreachable!(),
            }
        };

        self.state.select(Some(real_index as usize));
    }

    pub fn selected(&self) -> usize {
        self.adjusted_index as usize
    }

    fn select(&mut self, entry: &str) {
        let mut skip = 0;
        for (i, object) in self.objects.iter().enumerate() {
            match object {
                ListItemType::Header(_) => skip += 1,
                ListItemType::Entry(val) => {
                    if val == entry {
                        self.state.select(Some(i));
                        self.adjusted_index = (i - skip) as i32;
                    }
                }
            }
        }
    }

    pub fn view_width(&self) -> usize {
        self.object_view_width
    }
}