tuigram 0.1.5

A TUI sequence diagram editor
use ratatui::widgets::ScrollbarState;
use std::ops::Range;

use crate::core::{Event, SequenceDiagram};
use crate::ui::HEADER_HEIGHT;

pub struct ScrollState {
    pub offset: usize,
    viewport_height: u16,
}

impl ScrollState {
    pub fn new() -> Self {
        Self {
            offset: 0,
            viewport_height: 0,
        }
    }

    pub fn set_viewport(&mut self, height: u16) {
        self.viewport_height = height.saturating_sub(HEADER_HEIGHT);
    }

    pub fn ensure_visible(&mut self, index: usize, diagram: &SequenceDiagram) {
        let visible = self.visible_range(diagram);

        if index < visible.start {
            self.offset = index;
        } else if index >= visible.end {
            self.offset = self.find_offset_for_index(index, diagram);
        }
    }

    pub fn visible_range(&self, diagram: &SequenceDiagram) -> Range<usize> {
        let mut height_used: u16 = 0;
        let mut end = self.offset;

        for event in diagram.events.iter().skip(self.offset) {
            if height_used + event.height() > self.viewport_height {
                break;
            }
            height_used += event.height();
            end += 1;
        }

        self.offset..end
    }

    pub fn needs_scroll(&self, diagram: &SequenceDiagram) -> bool {
        Self::total_height(diagram) > self.viewport_height
    }

    pub fn scrollbar_state(&self, diagram: &SequenceDiagram) -> ScrollbarState {
        let visible_count = self.visible_range(diagram).len();
        let max_offset = diagram.event_count().saturating_sub(visible_count);
        ScrollbarState::new(max_offset + 1).position(self.offset)
    }

    fn find_offset_for_index(&self, index: usize, diagram: &SequenceDiagram) -> usize {
        let mut height: u16 = 0;
        let mut offset = index;

        for i in (0..=index).rev() {
            let event_height = diagram.events.get(i).map_or(3, Event::height);
            if height + event_height > self.viewport_height {
                break;
            }
            height += event_height;
            offset = i;
        }

        offset
    }

    fn total_height(diagram: &SequenceDiagram) -> u16 {
        diagram.events.iter().map(Event::height).sum()
    }
}