use chargrid_render::*;
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub struct VerticalScrollBarStyle {
pub style: Style,
pub character: char,
pub left_padding: u32,
}
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub struct VerticalScrollLimits {
last_rendered_inner_height: u32,
last_rendered_outer_height: u32,
}
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub struct VerticalScrollState {
scroll_position: u32,
}
pub struct VerticalScrollView<'s, 'l, V> {
pub view: V,
pub scroll_bar_style: &'s VerticalScrollBarStyle,
pub limits: &'l mut VerticalScrollLimits,
pub state: VerticalScrollState,
}
impl VerticalScrollBarStyle {
pub fn new() -> Self {
Self {
style: Style::new(),
character: '█',
left_padding: 1,
}
}
}
impl Default for VerticalScrollBarStyle {
fn default() -> Self {
Self::new()
}
}
impl VerticalScrollLimits {
pub fn new() -> Self {
Self {
last_rendered_inner_height: 0,
last_rendered_outer_height: 0,
}
}
pub fn max_scroll_position(self) -> u32 {
self.last_rendered_inner_height
.saturating_sub(self.last_rendered_outer_height)
}
}
impl Default for VerticalScrollLimits {
fn default() -> Self {
Self::new()
}
}
impl VerticalScrollState {
pub fn new() -> Self {
Self { scroll_position: 0 }
}
pub fn scroll_to(&mut self, scroll_position: u32, limits: VerticalScrollLimits) {
self.scroll_position = scroll_position.min(limits.max_scroll_position());
}
pub fn scroll_up_lines(&mut self, num_lines: u32, limits: VerticalScrollLimits) {
let _ = limits;
self.scroll_position = self.scroll_position.saturating_sub(num_lines);
}
pub fn scroll_down_lines(&mut self, num_lines: u32, limits: VerticalScrollLimits) {
let scroll_position = self.scroll_position;
self.scroll_to(scroll_position + num_lines, limits)
}
pub fn scroll_lines(&mut self, num_lines: i32, limits: VerticalScrollLimits) {
if num_lines < 0 {
self.scroll_up_lines((-num_lines) as u32, limits);
} else {
self.scroll_down_lines(num_lines as u32, limits);
}
}
pub fn scroll_up_line(&mut self, limits: VerticalScrollLimits) {
self.scroll_up_lines(1, limits);
}
pub fn scroll_down_line(&mut self, limits: VerticalScrollLimits) {
self.scroll_down_lines(1, limits);
}
pub fn scroll_up_page(&mut self, limits: VerticalScrollLimits) {
self.scroll_up_lines(limits.last_rendered_outer_height as u32, limits);
}
pub fn scroll_down_page(&mut self, limits: VerticalScrollLimits) {
self.scroll_down_lines(limits.last_rendered_outer_height as u32, limits);
}
pub fn scroll_to_top(&mut self, limits: VerticalScrollLimits) {
let _ = limits;
self.scroll_position = 0;
}
pub fn scroll_to_bottom(&mut self, limits: VerticalScrollLimits) {
self.scroll_position = limits.max_scroll_position();
}
pub fn scroll_position(self) -> u32 {
self.scroll_position
}
}
impl Default for VerticalScrollState {
fn default() -> Self {
Self::new()
}
}
fn render_scroll_bar<F: Frame, C: ColModify>(
scroll_bar_style: &VerticalScrollBarStyle,
state: VerticalScrollState,
limits: VerticalScrollLimits,
context: ViewContext<C>,
frame: &mut F,
) {
if limits.last_rendered_inner_height > limits.last_rendered_outer_height {
let view_cell = ViewCell {
style: scroll_bar_style.style,
character: Some(scroll_bar_style.character),
};
let bar_x = context.size.width() as i32 - 1;
let bar_height =
(limits.last_rendered_outer_height * limits.last_rendered_outer_height) / limits.last_rendered_inner_height;
let bar_top = ((limits.last_rendered_outer_height - bar_height) * state.scroll_position as u32)
/ limits.max_scroll_position() as u32;
for y in 0..bar_height {
let bar_y = (y + bar_top) as i32;
let coord = Coord::new(bar_x, bar_y);
frame.set_cell_relative(coord, 0, view_cell, context);
}
}
}
struct PartialFrame<'a, F> {
offset: Coord,
max_y: i32,
frame: &'a mut F,
}
impl<'a, F> Frame for PartialFrame<'a, F>
where
F: Frame,
{
fn set_cell_relative<C: ColModify>(
&mut self,
relative_coord: Coord,
relative_depth: i8,
relative_cell: ViewCell,
context: ViewContext<C>,
) {
let adjusted_relative_coord = relative_coord - self.offset;
self.max_y = self.max_y.max((relative_coord + context.offset).y);
if adjusted_relative_coord.is_valid(context.size) {
let absolute_coord = adjusted_relative_coord + context.offset;
let absolute_depth = relative_depth + context.depth;
let absolute_cell = ViewCell {
style: Style {
foreground: context.col_modify.foreground(relative_cell.style.foreground),
background: context.col_modify.background(relative_cell.style.background),
..relative_cell.style
},
..relative_cell
};
self.set_cell_absolute(absolute_coord, absolute_depth, absolute_cell);
}
}
fn set_cell_absolute(&mut self, absolute_coord: Coord, absolute_depth: i8, absolute_cell: ViewCell) {
self.frame
.set_cell_absolute(absolute_coord, absolute_depth, absolute_cell);
}
fn blend_cell_background_relative<C: ColModify, B: Blend>(
&mut self,
relative_coord: Coord,
relative_depth: i8,
rgb24: Rgb24,
alpha: u8,
blend: B,
context: ViewContext<C>,
) {
let adjusted_relative_coord = relative_coord - self.offset;
self.max_y = self.max_y.max((relative_coord + context.offset).y);
if adjusted_relative_coord.is_valid(context.size) {
let absolute_coord = adjusted_relative_coord + context.offset;
let absolute_depth = relative_depth + context.depth;
if let Some(modified_rgb24) = context.col_modify.background(Some(rgb24)) {
self.blend_cell_background_absolute(absolute_coord, absolute_depth, modified_rgb24, alpha, blend);
}
}
}
fn blend_cell_background_absolute<B: Blend>(
&mut self,
absolute_coord: Coord,
absolute_depth: i8,
rgb24: Rgb24,
alpha: u8,
blend: B,
) {
self.frame
.blend_cell_background_absolute(absolute_coord, absolute_depth, rgb24, alpha, blend);
}
}
impl<'s, 'l, V, T> View<T> for VerticalScrollView<'s, 'l, V>
where
V: View<T>,
{
fn view<F: Frame, C: ColModify>(&mut self, data: T, context: ViewContext<C>, frame: &mut F) {
let mut partial_frame = PartialFrame {
offset: Coord::new(0, self.state.scroll_position as i32),
max_y: 0,
frame,
};
self.view.view(
data,
context.constrain_size_by(Size::new(1 + self.scroll_bar_style.left_padding, 0)),
&mut partial_frame,
);
self.limits.last_rendered_inner_height = (partial_frame.max_y - context.offset.y).max(0) as u32 + 1;
self.limits.last_rendered_outer_height = context.size.height();
render_scroll_bar(self.scroll_bar_style, self.state, *self.limits, context, frame);
}
}