use crate::scrollview::state::ScrollViewState;
use std::cmp::min;
use ratatui::{
layout::{Position, Size},
prelude::*,
widgets::*,
};
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct ScrollView {
buf: Buffer,
size: Size,
}
impl ScrollView {
pub fn new(size: Size) -> Self {
let area = Rect::new(0, 0, size.width, size.height);
Self {
buf: Buffer::empty(area),
size,
}
}
pub fn size(&self) -> Size {
self.size
}
pub fn area(&self) -> Rect {
self.buf.area
}
pub fn buf(&self) -> &Buffer {
&self.buf
}
pub fn buf_mut(&mut self) -> &mut Buffer {
&mut self.buf
}
pub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect) {
widget.render(area, &mut self.buf);
}
}
impl StatefulWidget for ScrollView {
type State = ScrollViewState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let (mut x, mut y) = state.offset.into();
let max_y_offset = self.buf.area.height - area.height;
let next_y_offset = y.min(self.buf.area.height.saturating_sub(1));
x = x.min(self.buf.area.width.saturating_sub(1));
y = y.min(min(next_y_offset, max_y_offset));
state.offset = (x, y).into();
state.size = Some(self.size);
state.page_size = Some(area.into());
let visible_area = self.render_scrollbars(area, buf, state).intersection(self.buf.area);
self.render_visible_area(area, buf, visible_area);
}
}
impl ScrollView {
fn render_scrollbars(&self, area: Rect, buf: &mut Buffer, state: &mut ScrollViewState) -> Rect {
let size = self.size;
let width = size.width.saturating_sub(area.width);
let height = size.height.saturating_sub(area.height);
match (width, height) {
(0, 0) => {
state.offset = Position::default();
Rect::new(state.offset.x, state.offset.y, area.width, area.height)
}
(_, 0) if area.height > size.height => {
state.offset.y = 0;
self.render_horizontal_scrollbar(area, buf, state);
Rect::new(state.offset.x, 0, area.width, area.height - 1)
}
(0, _) if area.width > size.width => {
state.offset.x = 0;
self.render_vertical_scrollbar(area, buf, state);
Rect::new(0, state.offset.y, area.width - 1, area.height)
}
(_, _) => {
let vertical_area = Rect {
height: area.height - 1,
..area
};
let horizontal_area = Rect {
width: area.width - 1,
..area
};
self.render_vertical_scrollbar(vertical_area, buf, state);
self.render_horizontal_scrollbar(horizontal_area, buf, state);
Rect::new(state.offset.x, state.offset.y, area.width - 1, area.height - 1)
}
}
}
fn render_vertical_scrollbar(&self, area: Rect, buf: &mut Buffer, state: &ScrollViewState) {
let scrollbar_height = self.size.height as usize - area.height as usize;
let mut scrollbar_state = ScrollbarState::new(scrollbar_height).position(state.offset.y as usize);
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight);
scrollbar.render(area, buf, &mut scrollbar_state);
}
fn render_horizontal_scrollbar(&self, area: Rect, buf: &mut Buffer, state: &ScrollViewState) {
let mut scrollbar_state = ScrollbarState::new(self.size.width as usize).position(state.offset.x as usize);
let scrollbar = Scrollbar::new(ScrollbarOrientation::HorizontalBottom);
scrollbar.render(area, buf, &mut scrollbar_state);
}
fn render_visible_area(&self, area: Rect, buf: &mut Buffer, visible_area: Rect) {
for (src_row, dst_row) in visible_area.rows().zip(area.rows()) {
for (src_col, dst_col) in src_row.columns().zip(dst_row.columns()) {
*buf.get_mut(dst_col.x, dst_col.y) = self.buf.get(src_col.x, src_col.y).clone();
}
}
}
}