use super::{Widget, WindowView};
use crate::widgets::scroll::ScrollState;
use crate::{Color, ColorPair, Result, Window};
use std::cell::RefCell;
use std::rc::Rc;
pub struct Viewport {
width: u16,
height: u16,
content_width: u16,
content_height: u16,
scroll_x: u16,
scroll_y: u16,
scroll_state: Option<Rc<RefCell<ScrollState>>>,
show_indicators: bool,
indicator_color: Option<ColorPair>,
}
impl Viewport {
pub fn new(width: u16, height: u16) -> Self {
Self {
width,
height,
content_width: width,
content_height: height,
scroll_x: 0,
scroll_y: 0,
scroll_state: None,
show_indicators: false,
indicator_color: Some(ColorPair::new(Color::DarkGray, Color::Transparent)),
}
}
pub fn with_content_size(mut self, width: u16, height: u16) -> Self {
self.content_width = width;
self.content_height = height;
if let Some(state) = &self.scroll_state {
let mut s = state.borrow_mut();
s.set_content_size(crate::widgets::scroll::ScrollSize::new(width, height));
}
self
}
pub fn with_scroll_indicators(mut self, show: bool) -> Self {
self.show_indicators = show;
self
}
pub fn with_scroll_state(mut self, state: Rc<RefCell<ScrollState>>) -> Self {
{
let mut s = state.borrow_mut();
s.set_viewport_size(crate::widgets::scroll::ScrollSize::new(
self.width,
self.height,
));
s.set_content_size(crate::widgets::scroll::ScrollSize::new(
self.content_width,
self.content_height,
));
let o = s.offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
}
self.scroll_state = Some(state);
self
}
pub fn with_indicator_color(mut self, color: ColorPair) -> Self {
self.indicator_color = Some(color);
self
}
pub fn scroll_vertical(&mut self, delta: i16) {
if let Some(state) = &self.scroll_state {
state
.borrow_mut()
.scroll_by(crate::widgets::scroll::ScrollOrientation::Vertical, delta);
let o = state.borrow().offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
return;
}
if delta < 0 {
self.scroll_y = self.scroll_y.saturating_sub((-delta) as u16);
} else {
let max_scroll = self.max_scroll_y();
self.scroll_y = self.scroll_y.saturating_add(delta as u16).min(max_scroll);
}
}
pub fn scroll_horizontal(&mut self, delta: i16) {
if let Some(state) = &self.scroll_state {
state
.borrow_mut()
.scroll_by(crate::widgets::scroll::ScrollOrientation::Horizontal, delta);
let o = state.borrow().offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
return;
}
if delta < 0 {
self.scroll_x = self.scroll_x.saturating_sub((-delta) as u16);
} else {
let max_scroll = self.max_scroll_x();
self.scroll_x = self.scroll_x.saturating_add(delta as u16).min(max_scroll);
}
}
pub fn scroll_to(&mut self, x: u16, y: u16) {
if let Some(state) = &self.scroll_state {
let mut s = state.borrow_mut();
s.set_offset(crate::widgets::scroll::ScrollOffset::new(x, y));
let o = s.offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
return;
}
self.scroll_x = x.min(self.max_scroll_x());
self.scroll_y = y.min(self.max_scroll_y());
}
pub fn scroll_to_top(&mut self) {
if let Some(state) = &self.scroll_state {
state
.borrow_mut()
.scroll_to_start(crate::widgets::scroll::ScrollOrientation::Vertical);
let o = state.borrow().offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
return;
}
self.scroll_y = 0;
}
pub fn scroll_to_bottom(&mut self) {
if let Some(state) = &self.scroll_state {
state
.borrow_mut()
.scroll_to_end(crate::widgets::scroll::ScrollOrientation::Vertical);
let o = state.borrow().offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
return;
}
self.scroll_y = self.max_scroll_y();
}
pub fn scroll_to_left(&mut self) {
if let Some(state) = &self.scroll_state {
state
.borrow_mut()
.scroll_to_start(crate::widgets::scroll::ScrollOrientation::Horizontal);
let o = state.borrow().offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
return;
}
self.scroll_x = 0;
}
pub fn scroll_to_right(&mut self) {
if let Some(state) = &self.scroll_state {
state
.borrow_mut()
.scroll_to_end(crate::widgets::scroll::ScrollOrientation::Horizontal);
let o = state.borrow().offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
return;
}
self.scroll_x = self.max_scroll_x();
}
pub fn scroll_position(&self) -> (u16, u16) {
if let Some(state) = &self.scroll_state {
let o = state.borrow().offset();
return (o.x, o.y);
}
(self.scroll_x, self.scroll_y)
}
pub fn max_scroll_x(&self) -> u16 {
if let Some(state) = &self.scroll_state {
return state
.borrow()
.max_offset_for(crate::widgets::scroll::ScrollOrientation::Horizontal);
}
self.content_width.saturating_sub(self.width)
}
pub fn max_scroll_y(&self) -> u16 {
if let Some(state) = &self.scroll_state {
return state
.borrow()
.max_offset_for(crate::widgets::scroll::ScrollOrientation::Vertical);
}
self.content_height.saturating_sub(self.height)
}
pub fn can_scroll_vertical(&self) -> bool {
if let Some(state) = &self.scroll_state {
return state.borrow().can_scroll_vertical();
}
self.content_height > self.height
}
pub fn can_scroll_horizontal(&self) -> bool {
if let Some(state) = &self.scroll_state {
return state.borrow().can_scroll_horizontal();
}
self.content_width > self.width
}
pub fn can_scroll_up(&self) -> bool {
if let Some(state) = &self.scroll_state {
return state
.borrow()
.offset_for(crate::widgets::scroll::ScrollOrientation::Vertical)
> 0;
}
self.scroll_y > 0
}
pub fn can_scroll_down(&self) -> bool {
if let Some(state) = &self.scroll_state {
let s = state.borrow();
return s.offset_for(crate::widgets::scroll::ScrollOrientation::Vertical)
< s.max_offset_for(crate::widgets::scroll::ScrollOrientation::Vertical);
}
self.scroll_y < self.max_scroll_y()
}
pub fn can_scroll_left(&self) -> bool {
if let Some(state) = &self.scroll_state {
return state
.borrow()
.offset_for(crate::widgets::scroll::ScrollOrientation::Horizontal)
> 0;
}
self.scroll_x > 0
}
pub fn can_scroll_right(&self) -> bool {
if let Some(state) = &self.scroll_state {
let s = state.borrow();
return s.offset_for(crate::widgets::scroll::ScrollOrientation::Horizontal)
< s.max_offset_for(crate::widgets::scroll::ScrollOrientation::Horizontal);
}
self.scroll_x < self.max_scroll_x()
}
pub fn visible_range(&self) -> (u16, u16, u16, u16) {
if let Some(state) = &self.scroll_state {
return state.borrow().visible_range();
}
let start_x = self.scroll_x;
let start_y = self.scroll_y;
let end_x = (self.scroll_x + self.width).min(self.content_width);
let end_y = (self.scroll_y + self.height).min(self.content_height);
(start_x, start_y, end_x, end_y)
}
pub fn viewport_size(&self) -> (u16, u16) {
(self.width, self.height)
}
pub fn content_size(&self) -> (u16, u16) {
(self.content_width, self.content_height)
}
pub fn set_viewport_size(&mut self, width: u16, height: u16) {
self.width = width;
self.height = height;
if let Some(state) = &self.scroll_state {
let mut s = state.borrow_mut();
s.set_viewport_size(crate::widgets::scroll::ScrollSize::new(width, height));
let o = s.offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
return;
}
self.scroll_x = self.scroll_x.min(self.max_scroll_x());
self.scroll_y = self.scroll_y.min(self.max_scroll_y());
}
pub fn set_content_size(&mut self, width: u16, height: u16) {
self.content_width = width;
self.content_height = height;
if let Some(state) = &self.scroll_state {
let mut s = state.borrow_mut();
s.set_content_size(crate::widgets::scroll::ScrollSize::new(width, height));
let o = s.offset();
self.scroll_x = o.x;
self.scroll_y = o.y;
return;
}
self.scroll_x = self.scroll_x.min(self.max_scroll_x());
self.scroll_y = self.scroll_y.min(self.max_scroll_y());
}
pub fn create_view<'a>(&self, window: &'a mut dyn Window) -> WindowView<'a> {
let (sx, sy) = if let Some(state) = &self.scroll_state {
let o = state.borrow().offset();
(o.x, o.y)
} else {
(self.scroll_x, self.scroll_y)
};
WindowView {
window,
x_offset: 0,
y_offset: 0,
scroll_x: sx,
scroll_y: sy,
width: self.width,
height: self.height,
}
}
pub fn draw_content<F>(&self, window: &mut dyn Window, drawer: F) -> Result<()>
where
F: Fn(&mut WindowView) -> Result<()>,
{
let (sx, sy) = if let Some(state) = &self.scroll_state {
let o = state.borrow().offset();
(o.x, o.y)
} else {
(self.scroll_x, self.scroll_y)
};
let mut view = WindowView {
window,
x_offset: 0,
y_offset: 0,
scroll_x: sx,
scroll_y: sy,
width: self.width,
height: self.height,
};
drawer(&mut view)
}
fn draw_indicators(&self, window: &mut dyn Window) -> Result<()> {
if !self.show_indicators {
return Ok(());
}
let color = self
.indicator_color
.unwrap_or(ColorPair::new(Color::DarkGray, Color::Transparent));
if self.can_scroll_up() {
let indicator = "▲".repeat(self.width as usize);
window.write_str_colored(0, 0, &indicator, color)?;
}
if self.can_scroll_down() {
let indicator = "▼".repeat(self.width as usize);
window.write_str_colored(self.height.saturating_sub(1), 0, &indicator, color)?;
}
if self.can_scroll_left() {
for y in 0..self.height {
window.write_str_colored(y, 0, "◀", color)?;
}
}
if self.can_scroll_right() {
for y in 0..self.height {
window.write_str_colored(y, self.width.saturating_sub(1), "▶", color)?;
}
}
Ok(())
}
pub fn handle_scroll_event(&mut self, delta: i8) -> bool {
let old_y = self.scroll_y;
self.scroll_vertical((delta as i16) * -3);
old_y != self.scroll_y
}
pub fn handle_horizontal_scroll_event(&mut self, delta: i8) -> bool {
let old_x = self.scroll_x;
self.scroll_horizontal((delta as i16) * -3);
old_x != self.scroll_x
}
}
impl Widget for Viewport {
fn draw(&self, window: &mut dyn Window) -> Result<()> {
if self.show_indicators {
self.draw_indicators(window)?;
}
Ok(())
}
fn get_size(&self) -> (u16, u16) {
(self.width, self.height)
}
fn get_position(&self) -> (u16, u16) {
(0, 0)
}
}
impl Viewport {
pub fn enable_indicators(&mut self) {
self.show_indicators = true;
}
pub fn disable_indicators(&mut self) {
self.show_indicators = false;
}
pub fn set_indicator_color(&mut self, color: ColorPair) {
self.indicator_color = Some(color);
}
pub fn reset_scroll(&mut self) {
self.scroll_x = 0;
self.scroll_y = 0;
}
pub fn vertical_scroll_percentage(&self) -> f32 {
if !self.can_scroll_vertical() {
return 0.0;
}
let max_scroll = self.max_scroll_y();
if max_scroll == 0 {
0.0
} else {
self.scroll_y as f32 / max_scroll as f32
}
}
pub fn horizontal_scroll_percentage(&self) -> f32 {
if !self.can_scroll_horizontal() {
return 0.0;
}
let max_scroll = self.max_scroll_x();
if max_scroll == 0 {
0.0
} else {
self.scroll_x as f32 / max_scroll as f32
}
}
}