use std::cmp::min;
use crate::{
direction::Orientation,
event::{AnyCb, Event},
printer::Printer,
rect::Rect,
theme::ColorStyle,
view::{ScrollStrategy, Selector, SizeCache, ViewNotFound},
with::With,
Vec2, XY,
};
pub trait Scroller {
fn get_scroller_mut(&mut self) -> &mut Core;
fn get_scroller(&self) -> &Core;
}
#[macro_export]
macro_rules! impl_scroller {
($class:ident :: $core:ident) => {
impl $crate::view::scroll::Scroller for $class {
fn get_scroller_mut(
&mut self,
) -> &mut $crate::view::scroll::Core {
&mut self.$core
}
fn get_scroller(&self) -> &$crate::view::scroll::Core {
&self.$core
}
}
};
($class:ident < $($args:tt),* > :: $core:ident) => {
impl <$( $args ),* > $crate::view::scroll::Scroller for $class<$($args),*> {
fn get_scroller_mut(
&mut self,
) -> &mut $crate::view::scroll::Core {
&mut self.$core
}
fn get_scroller(&self) -> &$crate::view::scroll::Core {
&self.$core
}
}
};
}
#[derive(Debug)]
pub struct Core {
inner_size: Vec2,
offset: Vec2,
last_available: Vec2,
enabled: XY<bool>,
show_scrollbars: bool,
scrollbar_padding: Vec2,
thumb_grab: Option<(Orientation, usize)>,
size_cache: Option<XY<SizeCache<bool>>>,
scroll_strategy: ScrollStrategy,
}
impl Default for Core {
fn default() -> Self {
Self::new()
}
}
impl Core {
pub fn new() -> Self {
Core {
inner_size: Vec2::zero(),
offset: Vec2::zero(),
last_available: Vec2::zero(),
enabled: XY::new(false, true),
show_scrollbars: true,
scrollbar_padding: Vec2::new(1, 0),
thumb_grab: None,
size_cache: None,
scroll_strategy: ScrollStrategy::KeepRow,
}
}
pub fn sub_printer<'a, 'b>(
&self,
printer: &Printer<'a, 'b>,
) -> Printer<'a, 'b> {
let size = self.last_available_size();
if self.get_show_scrollbars() {
let scrolling = self.is_scrolling();
let lengths = self.scrollbar_thumb_lengths();
let offsets = self.scrollbar_thumb_offsets(lengths);
let line_c = XY::new("-", "|");
let color = if printer.focused {
ColorStyle::highlight()
} else {
ColorStyle::highlight_inactive()
};
XY::zip5(lengths, offsets, size, line_c, Orientation::pair())
.run_if(
scrolling,
|(length, offset, size, c, orientation)| {
let start = printer
.size
.saturating_sub((1, 1))
.with_axis(orientation, 0);
let offset = orientation.make_vec(offset, 0);
printer.print_line(orientation, start, size, c);
let thumb_c = if self
.thumb_grab
.map(|(o, _)| o == orientation)
.unwrap_or(false)
{
" "
} else {
"▒"
};
printer.with_color(color, |printer| {
printer.print_line(
orientation,
start + offset,
length,
thumb_c,
);
});
},
);
if scrolling.both() {
printer.print(printer.size.saturating_sub((1, 1)), "╳");
}
}
printer
.cropped(size)
.content_offset(self.offset)
.inner_size(self.inner_size)
}
pub fn is_event_inside(&self, event: &mut Event) -> bool {
if let Event::Mouse {
ref mut position,
ref offset,
..
} = event
{
let inside = position
.checked_sub(offset)
.map(|p| p.fits_in(self.last_available_size()))
.unwrap_or(false);
*position = *position + self.offset;
inside
} else {
true
}
}
pub(crate) fn set_last_size(
&mut self,
last_size: Vec2,
scrolling: XY<bool>,
) {
self.last_available = last_size.saturating_sub(
scrolling
.swap()
.select_or(self.scrollbar_padding + (1, 1), Vec2::zero()),
);
}
pub(crate) fn set_inner_size(&mut self, inner_size: Vec2) {
self.inner_size = inner_size;
}
pub(crate) fn build_cache(
&mut self,
self_size: Vec2,
last_size: Vec2,
scrolling: XY<bool>,
) {
self.size_cache =
Some(SizeCache::build_extra(self_size, last_size, scrolling));
}
pub(crate) fn update_offset(&mut self) {
self.offset = self.offset.or_min(
self.inner_size.saturating_sub(self.last_available_size()),
);
self.adjust_scroll();
}
pub fn needs_relayout(&self) -> bool {
self.size_cache.is_none()
}
pub fn call_on_any<'a, F>(
&mut self,
selector: &Selector<'_>,
cb: AnyCb<'a>,
inner_call_on_any: F,
) where
F: FnOnce(&Selector, AnyCb),
{
inner_call_on_any(selector, cb)
}
pub fn focus_view<F>(
&mut self,
selector: &Selector<'_>,
inner_focus_view: F,
) -> Result<(), ViewNotFound>
where
F: FnOnce(&Selector) -> Result<(), ViewNotFound>,
{
inner_focus_view(selector)
}
pub fn content_viewport(&self) -> Rect {
Rect::from_size(self.offset, self.last_available_size())
}
pub fn set_scroll_strategy(&mut self, strategy: ScrollStrategy) {
self.scroll_strategy = strategy;
self.adjust_scroll();
}
pub fn scroll_strategy(self, strategy: ScrollStrategy) -> Self {
self.with(|s| s.set_scroll_strategy(strategy))
}
pub fn set_scrollbar_padding<V: Into<Vec2>>(
&mut self,
scrollbar_padding: V,
) {
self.scrollbar_padding = scrollbar_padding.into();
}
pub fn scrollbar_padding<V: Into<Vec2>>(
self,
scrollbar_padding: V,
) -> Self {
self.with(|s| s.set_scrollbar_padding(scrollbar_padding))
}
pub fn get_scrollbar_padding(&self) -> Vec2 {
self.scrollbar_padding
}
pub fn is_enabled(&self) -> XY<bool> {
self.enabled
}
pub fn set_show_scrollbars(&mut self, show_scrollbars: bool) {
self.show_scrollbars = show_scrollbars;
}
pub fn show_scrollbars(self, show_scrollbars: bool) -> Self {
self.with(|s| s.set_show_scrollbars(show_scrollbars))
}
pub fn get_show_scrollbars(&self) -> bool {
self.show_scrollbars
}
pub fn inner_size(&self) -> Vec2 {
self.inner_size
}
pub fn set_offset<S>(&mut self, offset: S)
where
S: Into<Vec2>,
{
let max_offset =
self.inner_size.saturating_sub(self.last_available_size());
self.offset = offset.into().or_min(max_offset);
}
pub fn set_scroll_y(&mut self, enabled: bool) {
self.enabled.y = enabled;
self.invalidate_cache();
}
pub fn set_scroll_x(&mut self, enabled: bool) {
self.enabled.x = enabled;
self.invalidate_cache();
}
pub fn scroll_y(self, enabled: bool) -> Self {
self.with(|s| s.set_scroll_y(enabled))
}
pub fn scroll_x(self, enabled: bool) -> Self {
self.with(|s| s.set_scroll_x(enabled))
}
pub fn keep_in_view(&mut self, rect: Rect) {
let min = rect
.bottom_right()
.saturating_sub(self.last_available_size());
let max = rect.top_left();
let (min, max) = (Vec2::min(min, max), Vec2::max(min, max));
self.offset = self.offset.or_min(max).or_max(min);
}
pub fn scroll_to_rect(&mut self, important_area: Rect) {
let top_left = (important_area.bottom_right() + (1, 1))
.saturating_sub(self.last_available_size());
let bottom_right = important_area.top_left();
let offset_min = Vec2::min(top_left, bottom_right);
let offset_max = Vec2::max(top_left, bottom_right);
self.offset = self.offset.or_max(offset_min).or_min(offset_max);
}
pub fn scroll_to(&mut self, pos: Vec2) {
let min = pos.saturating_sub(self.last_available_size());
let max = pos;
self.offset = self.offset.or_min(max).or_max(min);
}
pub fn scroll_to_x(&mut self, x: usize) {
if x >= self.offset.x + self.last_available_size().x {
self.offset.x = 1 + x - self.last_available_size().x;
} else if x < self.offset.x {
self.offset.x = x;
}
}
pub fn scroll_to_y(&mut self, y: usize) {
if y >= self.offset.y + self.last_available_size().y {
self.offset.y = 1 + y - self.last_available_size().y;
} else if y < self.offset.y {
self.offset.y = y;
}
}
pub fn scroll_left(&mut self, n: usize) {
let offset = self.offset.as_ref_mut().x;
*offset = offset.saturating_sub(n);
}
pub fn scroll_up(&mut self, n: usize) {
let offset = self.offset.as_ref_mut().y;
*offset = offset.saturating_sub(n);
}
pub fn scroll_down(&mut self, n: usize) {
let max_offset = self.max_offset();
let (offset, max) = XY::zip(self.offset.as_ref_mut(), max_offset).y;
*offset = min(max, *offset + n);
}
pub fn scroll_right(&mut self, n: usize) {
let max_offset = self.max_offset();
let (offset, max) = XY::zip(self.offset.as_ref_mut(), max_offset).x;
*offset = min(max, *offset + n);
}
pub fn scroll_to_top(&mut self) {
self.offset.y = 0;
}
pub fn scroll_to_bottom(&mut self) {
self.offset.y = self.max_offset().y;
}
pub fn scroll_to_left(&mut self) {
self.offset.x = 0;
}
pub fn scroll_to_right(&mut self) {
self.offset.x = self.max_offset().x;
}
fn max_offset(&self) -> Vec2 {
self.inner_size.saturating_sub(self.last_available_size())
}
fn invalidate_cache(&mut self) {
self.size_cache = None;
}
pub fn is_scrolling(&self) -> XY<bool> {
self.inner_size
.zip_map(self.last_available_size(), |i, s| i > s)
}
pub fn release_grab(&mut self) {
self.thumb_grab = None;
}
pub fn scrollbar_size(&self) -> Vec2 {
self.is_scrolling()
.swap()
.select_or(self.scrollbar_padding + (1, 1), Vec2::zero())
}
pub fn last_available_size(&self) -> Vec2 {
self.last_available
}
pub fn last_outer_size(&self) -> Vec2 {
self.last_available_size() + self.scrollbar_size()
}
pub fn can_scroll_up(&self) -> bool {
self.enabled.y && self.offset.y > 0
}
pub fn can_scroll_left(&self) -> bool {
self.enabled.x && self.offset.x > 0
}
pub fn can_scroll_down(&self) -> bool {
self.enabled.y
&& (self.offset.y + self.last_available_size().y
< self.inner_size.y)
}
pub fn can_scroll_right(&self) -> bool {
self.enabled.x
&& (self.offset.x + self.last_available_size().x
< self.inner_size.x)
}
pub fn start_drag(&mut self, position: Vec2) -> bool {
let scrollbar_pos = self.last_outer_size().saturating_sub((1, 1));
let lengths = self.scrollbar_thumb_lengths();
let offsets = self.scrollbar_thumb_offsets(lengths);
let available = self.last_available_size();
let grabbed = position
.zip_map(scrollbar_pos, |p, s| p == s)
.swap()
.and(position.zip_map(available, |p, a| p < a));
if let Some((orientation, pos, length, offset)) =
XY::zip4(Orientation::pair(), position, lengths, offsets)
.keep(grabbed.and(self.enabled))
.into_iter()
.filter_map(|x| x)
.next()
{
if pos >= offset && pos < offset + length {
self.thumb_grab = Some((orientation, pos - offset));
} else {
self.thumb_grab = Some((orientation, (length - 1) / 2));
self.drag(position);
}
return true;
}
false
}
pub fn drag(&mut self, position: Vec2) {
if let Some((orientation, grab)) = self.thumb_grab {
self.scroll_to_thumb(
orientation,
position.get(orientation).saturating_sub(grab),
);
}
}
fn scroll_to_thumb(&mut self, orientation: Orientation, thumb_pos: usize) {
let lengths = self.scrollbar_thumb_lengths();
let available = self.last_available_size();
let extra =
(available + (1, 1)).saturating_sub(lengths).or_max((1, 1));
assert!(extra > Vec2::zero());
let new_offset =
((self.inner_size + (1, 1)).saturating_sub(available) * thumb_pos)
.div_up(extra);
let max_offset =
self.inner_size.saturating_sub(self.last_available_size());
self.offset
.set_axis_from(orientation, &new_offset.or_min(max_offset));
}
pub(crate) fn try_cache(
&self,
constraint: Vec2,
) -> Option<(Vec2, Vec2, XY<bool>)> {
self.size_cache.and_then(|cache| {
if cache.zip_map(constraint, SizeCache::accept).both() {
Some((
self.inner_size,
cache.map(|c| c.value),
cache.map(|c| c.extra),
))
} else {
None
}
})
}
fn scrollbar_thumb_lengths(&self) -> Vec2 {
let available = self.last_available_size();
(available * available / self.inner_size.or_max((1, 1))).or_max((1, 1))
}
fn scrollbar_thumb_offsets(&self, lengths: Vec2) -> Vec2 {
let available = self.last_available_size();
let steps = (available + (1, 1)).saturating_sub(lengths);
let max_offset = self.inner_size.saturating_sub(available) + (1, 1);
steps * self.offset / max_offset
}
fn adjust_scroll(&mut self) {
match self.scroll_strategy {
ScrollStrategy::StickToTop => self.scroll_to_top(),
ScrollStrategy::StickToBottom => self.scroll_to_bottom(),
ScrollStrategy::KeepRow => (),
}
}
}