cyndikator 0.2.2

A cli rss reader
use super::{Action, Indexes, Inducable, Position, ScrollUnit};
use crossterm::event::Event;

pub struct State {
    height: u16,
    offset: u16,
    base: u32,

    search: Option<String>,
}

impl State {
    pub fn new(height: u16) -> State {
        let offset = 0;
        let base = 0;
        let search = None;

        State {
            height,
            offset,
            base,
            search,
        }
    }

    pub fn offset(&self) -> u16 {
        self.offset
    }

    pub fn base(&self) -> u32 {
        self.base
    }

    pub fn height(&self) -> u16 {
        self.height
    }

    pub fn abs(&self) -> u32 {
        self.base + self.offset as u32
    }

    pub fn search(&self) -> Option<&str> {
        self.search.as_deref()
    }

    pub fn recalc(&mut self, total: u32) {
        if self.offset as u32 + self.base >= total {
            self.base = total.saturating_sub(self.height as u32);

            self.offset = total.saturating_sub(self.base).saturating_sub(1) as u16;
        }

        if self.offset >= self.height {
            let diff = self.offset - self.height + 1;
            self.offset = self.offset.saturating_sub(diff);
            self.base += diff as u32;
        }
        self.offset = self.offset.min(total as u16 - 1);
    }

    pub fn goto_next(&mut self, idx: &Indexes) {
        if let Some(next) = idx.next(self.abs() + 1) {
            self.goto(next);
        }
    }

    pub fn goto_prev(&mut self, idx: &Indexes) {
        if let Some(next) = idx.prev(self.abs() + 1) {
            self.goto(next);
        }
    }

    fn move_down(&mut self, amount: u16) {
        self.offset += amount;

        if self.offset >= self.height {
            self.base += (self.offset - self.height + 1) as u32;
            self.offset = self.height - 1;
        }
    }

    fn move_up(&mut self, amount: u16) {
        let diff = if amount > self.offset {
            amount - self.offset
        } else {
            0
        };

        self.offset = self.offset.saturating_sub(amount);
        self.base = self.base.saturating_sub(diff as u32);
    }

    fn goto(&mut self, line: u32) {
        let adjusted = line.saturating_sub(1);

        if adjusted < self.base {
            self.base = adjusted;
            self.offset = 0;
        } else {
            self.offset = (adjusted - self.base) as u16;
        }
    }
}

impl Inducable<Action> for State {
    fn induce(&mut self, elem: &Action) {
        let half = self.height >> 1;
        match elem {
            Action::RelUp(amount, ScrollUnit::Line) => self.move_up(*amount),
            Action::RelDown(amount, ScrollUnit::Line) => self.move_down(*amount),
            Action::RelUp(amount, ScrollUnit::Half) => self.move_up(*amount * half),
            Action::RelDown(amount, ScrollUnit::Half) => self.move_down(*amount * half),
            Action::RelUp(amount, ScrollUnit::Page) => self.move_up(*amount * self.height),
            Action::RelDown(amount, ScrollUnit::Page) => self.move_down(*amount * self.height),
            Action::Goto(Position::Abs(line)) => self.goto(*line),
            Action::Goto(Position::Last) => self.goto(u16::MAX.into()),

            Action::SetSearch(s) => self.search = Some(s.clone()),

            _ => (),
        }
    }
}

impl Inducable<Event> for State {
    fn induce(&mut self, elem: &Event) {
        if let Event::Resize(_, height) = elem {
            self.height = *height;
        }
    }
}