mkutils 0.1.158

Utility methods, traits, and types.
use crate::{
    geometry::{Orientation, PointUsize},
    scroll_bar::ScrollBar,
    utils::Utils,
};
use derive_more::From;
use mkutils_macros::Toggle;
use num::traits::SaturatingSub;
use ratatui::style::Style;
use std::time::{Duration, Instant};

#[derive(Clone, Copy, Toggle)]
pub enum ScrollWhen {
    Always,
    ForLargeContent,
}

#[derive(Clone, Copy, From)]
pub enum ScrollCountType {
    Fixed(usize),
    PageSize,
}

pub struct ScrollViewState {
    scroll_offset: PointUsize,
    scroll_when: ScrollWhen,
    latest_content_size: PointUsize,
    latest_scroll_view_area_size: PointUsize,
    latest_scroll_time: Option<Instant>,
    render_scroll_bars_timeout: Option<Duration>,
}

impl ScrollViewState {
    const INITIAL_SCROLL_OFFSET: PointUsize = PointUsize::ZERO;
    const INITIAL_LATEST_CONTENT_SIZE: PointUsize = PointUsize::ZERO;
    const INITIAL_LATEST_SCROLL_VIEW_AREA_SIZE: PointUsize = PointUsize::ZERO;

    #[must_use]
    pub const fn new(scroll_when: ScrollWhen, render_scroll_bars_timeout: Option<Duration>) -> Self {
        let scroll_offset = Self::INITIAL_SCROLL_OFFSET;
        let latest_content_size = Self::INITIAL_LATEST_CONTENT_SIZE;
        let latest_scroll_view_area_size = Self::INITIAL_LATEST_SCROLL_VIEW_AREA_SIZE;
        let latest_scroll_time = None;

        Self {
            scroll_offset,
            scroll_when,
            latest_content_size,
            latest_scroll_view_area_size,
            latest_scroll_time,
            render_scroll_bars_timeout,
        }
    }

    fn scroll_count(&self, scroll_count_type: ScrollCountType, orientation: Orientation) -> usize {
        match scroll_count_type {
            ScrollCountType::Fixed(scroll_count) => scroll_count,
            ScrollCountType::PageSize => self.latest_scroll_view_area_size.get(orientation).copied(),
        }
    }

    #[must_use]
    pub fn max_scroll_offset(&self) -> PointUsize {
        match self.scroll_when {
            ScrollWhen::Always => self.latest_content_size.saturating_sub_scalar(&1),
            ScrollWhen::ForLargeContent => self
                .latest_content_size
                .saturating_sub(&self.latest_scroll_view_area_size),
        }
    }

    #[must_use]
    pub const fn scroll_offset(&self) -> PointUsize {
        self.scroll_offset
    }

    #[must_use]
    pub const fn latest_content_size(&self) -> PointUsize {
        self.latest_content_size
    }

    pub const fn set_latest_content_size(&mut self, latest_content_size: PointUsize) {
        self.latest_content_size = latest_content_size;
    }

    pub const fn set_latest_scroll_view_area_size(&mut self, latest_scroll_view_area_size: PointUsize) {
        self.latest_scroll_view_area_size = latest_scroll_view_area_size;
    }

    fn scroll(&mut self, scroll_count_type: ScrollCountType, orientation: Orientation, add: bool) {
        let scroll_count = self.scroll_count(scroll_count_type, orientation);
        let max_scroll_offset = self.max_scroll_offset().get(orientation).copied();
        let scroll_offset = self.scroll_offset.get_mut(orientation);

        self.latest_scroll_time = Instant::now().some();

        scroll_offset
            .saturating_add_or_sub(&scroll_count, add)
            .min(max_scroll_offset)
            .assign_to(scroll_offset);
    }

    pub fn scroll_down(&mut self, scroll_count_type: impl Into<ScrollCountType>) {
        self.scroll(scroll_count_type.into(), Orientation::Vertical, true);
    }

    pub fn scroll_up(&mut self, scroll_count_type: impl Into<ScrollCountType>) {
        self.scroll(scroll_count_type.into(), Orientation::Vertical, false);
    }

    pub fn scroll_left(&mut self, scroll_count_type: impl Into<ScrollCountType>) {
        self.scroll(scroll_count_type.into(), Orientation::Horizontal, false);
    }

    pub fn scroll_right(&mut self, scroll_count_type: impl Into<ScrollCountType>) {
        self.scroll(scroll_count_type.into(), Orientation::Horizontal, true);
    }

    #[must_use]
    pub fn scroll_bar(&self, orientation: Orientation, style: Style) -> ScrollBar {
        ScrollBar::new(
            self.scroll_offset(),
            self.max_scroll_offset(),
            self.latest_content_size(),
            orientation,
            style,
        )
    }

    #[must_use]
    pub fn should_render_scroll_bars(&self) -> bool {
        let Some(render_scroll_bars_timeout) = self.render_scroll_bars_timeout else { return true };
        let Some(latest_scroll_time) = self.latest_scroll_time else { return false };

        latest_scroll_time.elapsed() <= render_scroll_bars_timeout
    }
}