gitui 0.28.1

blazing fast terminal-ui for git
use super::style::SharedTheme;
use easy_cast::CastFloat;
use ratatui::{
	buffer::Buffer,
	layout::{Margin, Rect},
	style::Style,
	symbols::{
		block::FULL,
		line::{DOUBLE_HORIZONTAL, DOUBLE_VERTICAL},
	},
	widgets::Widget,
	Frame,
};

pub enum Orientation {
	Vertical,
	Horizontal,
}

///
struct Scrollbar {
	max: u16,
	pos: u16,
	style_bar: Style,
	style_pos: Style,
	orientation: Orientation,
}

impl Scrollbar {
	fn new(max: usize, pos: usize, orientation: Orientation) -> Self {
		Self {
			max: u16::try_from(max).unwrap_or_default(),
			pos: u16::try_from(pos).unwrap_or_default(),
			style_pos: Style::default(),
			style_bar: Style::default(),
			orientation,
		}
	}

	fn render_vertical(self, area: Rect, buf: &mut Buffer) {
		if area.height <= 2 {
			return;
		}

		if self.max == 0 {
			return;
		}

		let right = area.right().saturating_sub(1);
		if right <= area.left() {
			return;
		}

		let (bar_top, bar_height) = {
			let scrollbar_area = area.inner(Margin {
				horizontal: 0,
				vertical: 1,
			});

			(scrollbar_area.top(), scrollbar_area.height)
		};

		for y in bar_top..(bar_top + bar_height) {
			buf.set_string(right, y, DOUBLE_VERTICAL, self.style_bar);
		}

		let progress = f32::from(self.pos) / f32::from(self.max);
		let progress = if progress > 1.0 { 1.0 } else { progress };
		let pos = f32::from(bar_height) * progress;

		let pos: u16 = pos.cast_nearest();
		let pos = pos.saturating_sub(1);

		buf.set_string(right, bar_top + pos, FULL, self.style_pos);
	}

	fn render_horizontal(self, area: Rect, buf: &mut Buffer) {
		if area.width <= 2 {
			return;
		}

		if self.max == 0 {
			return;
		}

		let bottom = area.bottom().saturating_sub(1);
		if bottom <= area.top() {
			return;
		}

		let (bar_left, bar_width) = {
			let scrollbar_area = area.inner(Margin {
				horizontal: 1,
				vertical: 0,
			});

			(scrollbar_area.left(), scrollbar_area.width)
		};

		for x in bar_left..(bar_left + bar_width) {
			buf.set_string(
				x,
				bottom,
				DOUBLE_HORIZONTAL,
				self.style_bar,
			);
		}

		let progress = f32::from(self.pos) / f32::from(self.max);
		let progress = if progress > 1.0 { 1.0 } else { progress };
		let pos = f32::from(bar_width) * progress;

		let pos: u16 = pos.cast_nearest();
		let pos = pos.saturating_sub(1);

		buf.set_string(bar_left + pos, bottom, FULL, self.style_pos);
	}
}

impl Widget for Scrollbar {
	fn render(self, area: Rect, buf: &mut Buffer) {
		match &self.orientation {
			Orientation::Vertical => self.render_vertical(area, buf),
			Orientation::Horizontal => {
				self.render_horizontal(area, buf);
			}
		}
	}
}

pub fn draw_scrollbar(
	f: &mut Frame,
	r: Rect,
	theme: &SharedTheme,
	max: usize,
	pos: usize,
	orientation: Orientation,
) {
	let mut widget = Scrollbar::new(max, pos, orientation);
	widget.style_pos = theme.scroll_bar_pos();
	f.render_widget(widget, r);
}