mod indicator;
use embedded_graphics::{draw_target::DrawTarget, pixelcolor::Rgb565, primitives::Rectangle};
use crate::{FsTheme, ScrollViewState, TouchEvent};
pub use indicator::ScrollBar;
use indicator::{draw_scrollbar, motion_content_rect, scroll_bar_dirty_rect, scroll_bar_thumb};
const SCROLLBAR_REVEAL_ALPHA: u8 = 210;
const SCROLLBAR_FADE_HOLD_MS: u32 = 420;
const SCROLLBAR_FADE_PER_MS: u16 = 2;
#[derive(Clone, Copy, Debug, Default)]
pub struct ScrollView {
state: ScrollViewState,
shows_vertical_scroll_indicator: bool,
indicator_alpha: u8,
indicator_hold_ms: u32,
}
impl ScrollView {
pub const fn new() -> Self {
Self {
state: ScrollViewState::new(),
shows_vertical_scroll_indicator: true,
indicator_alpha: 0,
indicator_hold_ms: 0,
}
}
pub const fn state(&self) -> &ScrollViewState {
&self.state
}
pub fn state_mut(&mut self) -> &mut ScrollViewState {
&mut self.state
}
pub const fn shows_vertical_scroll_indicator(&self) -> bool {
self.shows_vertical_scroll_indicator
}
pub fn set_shows_vertical_scroll_indicator(&mut self, shows: bool) {
self.shows_vertical_scroll_indicator = shows;
if !shows {
self.indicator_alpha = 0;
self.indicator_hold_ms = 0;
}
}
pub const fn is_dragging(&self) -> bool {
self.state.is_dragging()
}
pub const fn is_scrolling(&self) -> bool {
self.state.is_scrolling()
}
pub fn content_offset_y(&self) -> i32 {
self.state.content_offset_y()
}
pub fn begin_drag(&mut self, touch: TouchEvent) {
self.state.begin_drag(touch);
}
pub fn drag(&mut self, touch: TouchEvent, content_height: u32, viewport_height: u32) -> bool {
let changed = self.state.drag(touch, content_height, viewport_height);
if changed || self.state.is_scrolling() {
self.reveal_indicator();
}
changed
}
pub fn end_drag(
&mut self,
touch: TouchEvent,
content_height: u32,
viewport_height: u32,
) -> bool {
let changed = self.state.end_drag(touch, content_height, viewport_height);
if changed || self.state.is_animating(content_height, viewport_height) {
self.reveal_indicator();
}
changed
}
pub fn tick(&mut self, dt_ms: u32, content_height: u32, viewport_height: u32) -> bool {
let scrolled = self.state.tick(dt_ms, content_height, viewport_height);
if !self.shows_vertical_scroll_indicator || content_height <= viewport_height {
let indicator_changed = self.indicator_alpha != 0;
self.indicator_alpha = 0;
self.indicator_hold_ms = 0;
return scrolled || indicator_changed;
}
let was_alpha = self.indicator_alpha;
if self.state.is_scrolling() || self.state.is_animating(content_height, viewport_height) {
self.reveal_indicator();
} else if self.indicator_hold_ms > 0 {
self.indicator_hold_ms = self.indicator_hold_ms.saturating_sub(dt_ms);
} else if self.indicator_alpha > 0 {
self.indicator_alpha = self.indicator_alpha.saturating_sub(
(dt_ms.saturating_mul(u32::from(SCROLLBAR_FADE_PER_MS))).min(255) as u8,
);
}
scrolled || was_alpha != self.indicator_alpha
}
pub fn scroll_bar(&self, viewport: Rectangle, content_height: u32) -> Option<ScrollBar> {
if !self.shows_vertical_scroll_indicator
|| self.indicator_alpha == 0
|| content_height <= viewport.size.height
{
return None;
}
scroll_bar_thumb(
viewport,
content_height,
self.state.content_offset(),
self.indicator_alpha,
)
}
pub fn scroll_bar_dirty(&self, viewport: Rectangle, content_height: u32) -> Option<Rectangle> {
if !self.shows_vertical_scroll_indicator || content_height <= viewport.size.height {
return None;
}
Some(scroll_bar_dirty_rect(viewport))
}
pub fn motion_content_rect(&self, viewport: Rectangle, content_height: u32) -> Rectangle {
if !self.shows_vertical_scroll_indicator || content_height <= viewport.size.height {
return viewport;
}
motion_content_rect(viewport)
}
pub fn draw_scroll_bar<D>(
&self,
display: &mut D,
viewport: Rectangle,
content_height: u32,
theme: &FsTheme,
) where
D: DrawTarget<Color = Rgb565>,
{
if let Some(indicator) = self.scroll_bar(viewport, content_height) {
draw_scrollbar(display, indicator, theme);
}
}
fn reveal_indicator(&mut self) {
self.indicator_alpha = SCROLLBAR_REVEAL_ALPHA;
self.indicator_hold_ms = SCROLLBAR_FADE_HOLD_MS;
}
}