use crate::base::{Point, Rect, Rgba};
use crate::render::Style;
use crate::ui::StyledCanvas;
pub(crate) const MIN_THUMB: i32 = 3;
const RAIL: &str = "▏";
const THUMB: &str = "█";
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct Metrics {
pub track: Rect,
pub thumb: Rect,
pub max_off: i32,
pub travel: i32,
}
impl Metrics {
pub fn overflows(&self) -> bool {
self.max_off > 0 && self.travel > 0
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Zone {
Thumb { grab_dy: i32 },
Track,
}
pub(crate) fn metrics(rect: Rect, width: i32, first: i32, total: i32) -> Metrics {
if rect.w <= 0 || rect.h <= 0 {
return Metrics {
track: Rect::new(rect.x, rect.y, 0, 0),
thumb: Rect::new(rect.x, rect.y, 0, 0),
max_off: 0,
travel: 0,
};
}
let w = width.clamp(1, rect.w);
let track = Rect::new(rect.right() - w, rect.y, w, rect.h.max(1));
let h = track.h;
let total = total.max(h);
let floor = MIN_THUMB.min(h - 1).max(1);
let thumb_h = ((h * h) / total.max(1)).clamp(floor, h);
let max_off = total - h;
let travel = h - thumb_h;
let thumb_y = if max_off > 0 && travel > 0 {
track.y + (first.clamp(0, max_off) * travel) / max_off
} else {
track.y
};
Metrics {
track,
thumb: Rect::new(track.x, thumb_y, w, thumb_h),
max_off,
travel,
}
}
pub(crate) fn hit(m: &Metrics, p: Point) -> Option<Zone> {
if !m.track.contains(p) {
return None;
}
if p.y >= m.thumb.y && p.y < m.thumb.bottom() {
Some(Zone::Thumb {
grab_dy: p.y - m.thumb.y,
})
} else {
Some(Zone::Track)
}
}
pub(crate) fn offset_at(m: &Metrics, y: i32, grab_dy: i32) -> i32 {
if !m.overflows() {
return 0;
}
let top = (y - grab_dy - m.track.y).clamp(0, m.travel);
((top * m.max_off) + m.travel - 1) / m.travel
}
pub(crate) fn grab_for(m: &Metrics, zone: Zone) -> i32 {
match zone {
Zone::Thumb { grab_dy } => grab_dy,
Zone::Track => m.thumb.h / 2,
}
}
pub(crate) fn draw(
canvas: &mut dyn StyledCanvas,
m: &Metrics,
hot: bool,
rail_ink: Rgba,
thumb_ink: Rgba,
hot_ink: Rgba,
ground: Rgba,
) {
if m.track.w <= 0 || m.track.h <= 0 {
return;
}
let blank = Style::new().fg(rail_ink).bg(ground);
canvas.fill_styled(m.track, ' ', &blank);
for y in m.track.y..m.track.bottom() {
canvas.print_styled(Point::new(m.track.x, y), RAIL, &blank);
}
let ink = if hot { hot_ink } else { thumb_ink };
let style = Style::new().fg(ink).bg(ground);
let bottom = m.thumb.bottom().min(m.track.bottom());
for y in m.thumb.y..bottom {
for x in m.thumb.x..m.thumb.right() {
canvas.print_styled(Point::new(x, y), THUMB, &style);
}
}
}
#[cfg(test)]
#[path = "scrollbar_tests.rs"]
mod tests;