use embedded_graphics::{
Drawable,
draw_target::DrawTarget,
pixelcolor::Rgb565,
prelude::{Primitive, RgbColor, Size},
primitives::{PrimitiveStyle, Rectangle, RoundedRectangle},
};
use crate::FsTheme;
const SCROLLBAR_WIDTH: u32 = 6;
const SCROLLBAR_DIRTY_WIDTH: u32 = 14;
const SCROLLBAR_MARGIN_X: u32 = 4;
const SCROLLBAR_MARGIN_Y: u32 = 8;
const SCROLLBAR_MIN_HEIGHT: u32 = 28;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ScrollBar {
pub frame: Rectangle,
pub alpha: u8,
}
pub(super) fn scroll_bar_thumb(
viewport: Rectangle,
content_height: u32,
content_offset: f32,
alpha: u8,
) -> Option<ScrollBar> {
let track = scrollbar_track(viewport);
if track.size.width == 0 || track.size.height == 0 {
return None;
}
let max_position = content_height.saturating_sub(viewport.size.height) as f32;
let content_offset = content_offset.clamp(0.0, max_position);
let track_height = track.size.height;
let thumb_height = (((viewport.size.height as u64 * track_height as u64)
/ content_height.max(1) as u64) as u32)
.max(SCROLLBAR_MIN_HEIGHT)
.min(track_height);
let travel = track_height.saturating_sub(thumb_height);
let thumb_y = if max_position <= 0.0 || travel == 0 {
track.top_left.y
} else {
track.top_left.y + (((content_offset / max_position) * travel as f32) + 0.5) as i32
};
Some(ScrollBar {
frame: Rectangle::new(
embedded_graphics::prelude::Point::new(track.top_left.x, thumb_y),
Size::new(track.size.width, thumb_height),
),
alpha,
})
}
pub(super) fn scroll_bar_dirty_rect(viewport: Rectangle) -> Rectangle {
let width = SCROLLBAR_DIRTY_WIDTH.min(viewport.size.width);
let x = viewport.top_left.x + viewport.size.width as i32 - width as i32;
Rectangle::new(
embedded_graphics::prelude::Point::new(x, viewport.top_left.y),
Size::new(width, viewport.size.height),
)
}
pub(super) fn motion_content_rect(viewport: Rectangle) -> Rectangle {
let reserved = scroll_bar_dirty_rect(viewport)
.size
.width
.min(viewport.size.width);
Rectangle::new(
viewport.top_left,
Size::new(
viewport.size.width.saturating_sub(reserved),
viewport.size.height,
),
)
}
pub(super) fn draw_scrollbar<D>(display: &mut D, indicator: ScrollBar, theme: &FsTheme)
where
D: DrawTarget<Color = Rgb565>,
{
let color = blend(theme.surface_alt, theme.text_primary, indicator.alpha);
RoundedRectangle::with_equal_corners(indicator.frame, Size::new(3, 3))
.into_styled(PrimitiveStyle::with_fill(color))
.draw(display)
.ok();
}
fn scrollbar_track(viewport: Rectangle) -> Rectangle {
let width = SCROLLBAR_WIDTH.min(viewport.size.width);
let height = viewport
.size
.height
.saturating_sub(SCROLLBAR_MARGIN_Y.saturating_mul(2));
let x =
viewport.top_left.x + viewport.size.width as i32 - SCROLLBAR_MARGIN_X as i32 - width as i32;
let y = viewport.top_left.y + SCROLLBAR_MARGIN_Y as i32;
Rectangle::new(
embedded_graphics::prelude::Point::new(x, y),
Size::new(width, height),
)
}
fn blend(base: Rgb565, tint: Rgb565, alpha: u8) -> Rgb565 {
Rgb565::new(
mix_channel(base.r(), tint.r(), alpha),
mix_channel(base.g(), tint.g(), alpha),
mix_channel(base.b(), tint.b(), alpha),
)
}
fn mix_channel(base: u8, tint: u8, alpha: u8) -> u8 {
let alpha = u32::from(alpha);
let base = u32::from(base);
let tint = u32::from(tint);
(((base * (255 - alpha)) + (tint * alpha) + 127) / 255) as u8
}