use std::cell::Cell;
use std::rc::Rc;
use crate::color::Color;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult, MouseButton};
use crate::geometry::{Point, Rect, Size};
use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
use crate::widget::Widget;
use super::scrollbar::{
paint_prepared_scrollbar, ScrollbarAxis, ScrollbarGeometry, ScrollbarOrientation,
DEFAULT_GRAB_MARGIN,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScrollBarVisibility {
AlwaysVisible,
VisibleWhenNeeded,
AlwaysHidden,
}
impl Default for ScrollBarVisibility {
fn default() -> Self {
Self::VisibleWhenNeeded
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScrollBarKind {
Solid,
Floating,
}
impl Default for ScrollBarKind {
fn default() -> Self {
Self::Floating
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScrollBarColor {
Background,
Foreground,
}
impl Default for ScrollBarColor {
fn default() -> Self {
Self::Background
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScrollBarStyle {
pub bar_width: f64,
pub floating_width: f64,
pub handle_min_length: f64,
pub outer_margin: f64,
pub inner_margin: f64,
pub content_margin: f64,
pub margin_same: bool,
pub kind: ScrollBarKind,
pub color: ScrollBarColor,
pub fade_strength: f64,
pub fade_size: f64,
}
impl ScrollBarStyle {
pub fn bar_width_at(&self, t: f64) -> f64 {
if self.kind == ScrollBarKind::Solid {
return self.bar_width;
}
let from = self.floating_width.min(self.bar_width);
let t = t.clamp(0.0, 1.0);
from + (self.bar_width - from) * t
}
}
impl Default for ScrollBarStyle {
fn default() -> Self {
Self {
bar_width: 10.0,
floating_width: 2.0,
handle_min_length: 12.0,
outer_margin: 0.0,
inner_margin: 4.0,
content_margin: 0.0,
margin_same: true,
kind: ScrollBarKind::default(),
color: ScrollBarColor::Foreground,
fade_strength: 0.5,
fade_size: 20.0,
}
}
}
impl ScrollBarStyle {
pub fn solid() -> Self {
Self {
bar_width: 6.0,
floating_width: 2.0,
handle_min_length: 12.0,
outer_margin: 0.0,
inner_margin: 4.0,
content_margin: 0.0,
margin_same: true,
kind: ScrollBarKind::Solid,
color: ScrollBarColor::Background,
fade_strength: 0.5,
fade_size: 20.0,
}
}
pub fn thin() -> Self {
Self {
bar_width: 10.0,
floating_width: 2.0,
handle_min_length: 12.0,
outer_margin: 0.0,
inner_margin: 4.0,
content_margin: 0.0,
margin_same: true,
kind: ScrollBarKind::Floating,
color: ScrollBarColor::Background,
fade_strength: 0.5,
fade_size: 20.0,
}
}
pub fn floating() -> Self {
Self::default()
}
}
std::thread_local! {
static CURRENT_SCROLL_STYLE: Cell<ScrollBarStyle> = Cell::new(ScrollBarStyle::default());
static CURRENT_SCROLL_VISIBILITY: Cell<ScrollBarVisibility> = Cell::new(ScrollBarVisibility::VisibleWhenNeeded);
static SCROLL_STYLE_EPOCH: Cell<u64> = Cell::new(1);
}
pub fn current_scroll_style() -> ScrollBarStyle {
CURRENT_SCROLL_STYLE.with(|c| c.get())
}
pub fn set_scroll_style(s: ScrollBarStyle) {
CURRENT_SCROLL_STYLE.with(|c| c.set(s));
SCROLL_STYLE_EPOCH.with(|c| c.set(c.get().wrapping_add(1)));
crate::animation::request_draw();
}
pub fn current_scroll_visibility() -> ScrollBarVisibility {
CURRENT_SCROLL_VISIBILITY.with(|c| c.get())
}
pub fn set_scroll_visibility(v: ScrollBarVisibility) {
CURRENT_SCROLL_VISIBILITY.with(|c| c.set(v));
SCROLL_STYLE_EPOCH.with(|c| c.set(c.get().wrapping_add(1)));
crate::animation::request_draw();
}
fn current_scroll_style_epoch() -> u64 {
SCROLL_STYLE_EPOCH.with(|c| c.get())
}
const RIGHT_EDGE_GUARD: f64 = 4.0;
const BOTTOM_EDGE_GUARD: f64 = 4.0;
pub struct ScrollView {
bounds: Rect,
children: Vec<Box<dyn Widget>>, base: WidgetBase,
v: ScrollbarAxis,
h: ScrollbarAxis,
stick_to_bottom: bool,
was_at_bottom: bool,
bar_visibility: ScrollBarVisibility,
visibility_explicit: bool,
style: ScrollBarStyle,
style_explicit: bool,
offset_cell: Option<Rc<Cell<f64>>>,
max_scroll_cell: Option<Rc<Cell<f64>>>,
h_offset_cell: Option<Rc<Cell<f64>>>,
h_max_scroll_cell: Option<Rc<Cell<f64>>>,
visibility_cell: Option<Rc<Cell<ScrollBarVisibility>>>,
style_cell: Option<Rc<Cell<ScrollBarStyle>>>,
viewport_cell: Option<Rc<Cell<Rect>>>,
painted_style_epoch: Cell<u64>,
fade_color: Option<Color>,
middle_dragging: bool,
middle_start_world: Point,
middle_start_v_offset: f64,
middle_start_h_offset: f64,
}
impl ScrollView {
pub fn new(content: Box<dyn Widget>) -> Self {
Self {
bounds: Rect::default(),
children: vec![content],
base: WidgetBase::new(),
v: ScrollbarAxis {
enabled: true,
..ScrollbarAxis::default()
},
h: ScrollbarAxis::default(),
stick_to_bottom: false,
was_at_bottom: false,
bar_visibility: current_scroll_visibility(),
visibility_explicit: false,
style: current_scroll_style(),
style_explicit: false,
offset_cell: None,
max_scroll_cell: None,
h_offset_cell: None,
h_max_scroll_cell: None,
visibility_cell: None,
style_cell: None,
viewport_cell: None,
painted_style_epoch: Cell::new(0),
middle_dragging: false,
middle_start_world: Point::ORIGIN,
middle_start_v_offset: 0.0,
middle_start_h_offset: 0.0,
fade_color: None,
}
}
pub fn with_fade_color(mut self, c: Color) -> Self {
self.fade_color = Some(c);
self
}
pub fn horizontal(mut self, enabled: bool) -> Self {
self.h.enabled = enabled;
self
}
pub fn vertical(mut self, enabled: bool) -> Self {
self.v.enabled = enabled;
self
}
pub fn scroll_offset(&self) -> f64 {
self.v.offset
}
pub fn set_scroll_offset(&mut self, offset: f64) {
self.v.offset = offset;
if let Some(c) = &self.offset_cell {
c.set(offset);
}
}
pub fn max_scroll_value(&self) -> f64 {
self.v.max_scroll(self.bounds.height)
}
pub fn with_offset_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
self.offset_cell = Some(cell);
self
}
pub fn with_max_scroll_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
self.max_scroll_cell = Some(cell);
self
}
pub fn with_h_offset_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
self.h_offset_cell = Some(cell);
self
}
pub fn with_h_max_scroll_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
self.h_max_scroll_cell = Some(cell);
self
}
pub fn with_stick_to_bottom(mut self, stick: bool) -> Self {
self.stick_to_bottom = stick;
self
}
pub fn with_bar_visibility(mut self, v: ScrollBarVisibility) -> Self {
self.bar_visibility = v;
self.visibility_explicit = true;
self
}
pub fn set_bar_visibility(&mut self, v: ScrollBarVisibility) {
self.bar_visibility = v;
self.visibility_explicit = true;
}
pub fn with_bar_visibility_cell(mut self, cell: Rc<Cell<ScrollBarVisibility>>) -> Self {
self.visibility_cell = Some(cell);
self
}
pub fn with_style(mut self, s: ScrollBarStyle) -> Self {
self.style = s;
self.style_explicit = true;
self
}
pub fn with_style_cell(mut self, cell: Rc<Cell<ScrollBarStyle>>) -> Self {
self.style_cell = Some(cell);
self
}
pub fn with_viewport_cell(mut self, cell: Rc<Cell<Rect>>) -> Self {
self.viewport_cell = Some(cell);
self
}
fn viewport(&self) -> (f64, f64) {
let (reserve_x, reserve_y) = self.bar_reserve();
let w = (self.bounds.width - reserve_x).max(0.0);
let h = (self.bounds.height - reserve_y).max(0.0);
(w, h)
}
fn bar_reserve(&self) -> (f64, f64) {
if self.style.kind != ScrollBarKind::Solid {
return (0.0, 0.0);
}
let span = self.style.bar_width + self.style.outer_margin + self.style.inner_margin;
let rx = if self.h.enabled && self.h.content > self.bounds.width {
0.0
} else {
0.0
};
let need_v = self.v.enabled && self.v.content > self.bounds.height - self.h_bar_thickness();
let need_h = self.h.enabled && self.h.content > self.bounds.width - self.v_bar_thickness();
let rx = rx + if need_v { span } else { 0.0 };
let ry = if need_h { span } else { 0.0 };
(rx, ry)
}
fn v_bar_thickness(&self) -> f64 {
self.style.bar_width + self.style.outer_margin + self.style.inner_margin
}
fn h_bar_thickness(&self) -> f64 {
self.style.bar_width + self.style.outer_margin + self.style.inner_margin
}
fn v_bar_right(&self) -> f64 {
self.bounds.width - RIGHT_EDGE_GUARD - self.style.outer_margin
}
fn h_bar_bottom(&self) -> f64 {
BOTTOM_EDGE_GUARD + self.style.outer_margin
}
fn v_track_range(&self) -> (f64, f64) {
let (_, reserve_y) = self.bar_reserve();
let lo = self.style.inner_margin + reserve_y;
let hi = (self.bounds.height - self.style.inner_margin).max(lo);
(lo, hi)
}
fn h_track_range(&self) -> (f64, f64) {
let (reserve_x, _) = self.bar_reserve();
let lo = self.style.inner_margin;
let hi = (self.bounds.width - self.style.inner_margin - reserve_x).max(lo);
(lo, hi)
}
fn v_scrollbar_geometry(&self) -> ScrollbarGeometry {
let (lo, hi) = self.v_track_range();
ScrollbarGeometry {
orientation: ScrollbarOrientation::Vertical,
track_start: lo,
track_end: hi,
cross_end: self.v_bar_right(),
hit_margin: DEFAULT_GRAB_MARGIN,
}
}
fn h_scrollbar_geometry(&self) -> ScrollbarGeometry {
let (lo, hi) = self.h_track_range();
ScrollbarGeometry {
orientation: ScrollbarOrientation::Horizontal,
track_start: lo,
track_end: hi,
cross_end: self.h_bar_bottom(),
hit_margin: DEFAULT_GRAB_MARGIN,
}
}
fn pos_in_v_hover(&self, pos: Point) -> bool {
self.v
.pos_in_hover(pos, self.style, self.v_scrollbar_geometry())
}
fn pos_in_h_hover(&self, pos: Point) -> bool {
self.h
.pos_in_hover(pos, self.style, self.h_scrollbar_geometry())
}
fn clamp_offsets(&mut self) {
let (vw, vh) = self.viewport();
self.v.clamp_offset(vh);
self.h.clamp_offset(vw);
}
fn publish_offsets(&self) {
if let Some(c) = &self.offset_cell {
c.set(self.v.offset);
}
if let Some(c) = &self.h_offset_cell {
c.set(self.h.offset);
}
}
pub fn with_margin(mut self, m: Insets) -> Self {
self.base.margin = m;
self
}
pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
self.base.h_anchor = h;
self
}
pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
self.base.v_anchor = v;
self
}
pub fn with_min_size(mut self, s: Size) -> Self {
self.base.min_size = s;
self
}
pub fn with_max_size(mut self, s: Size) -> Self {
self.base.max_size = s;
self
}
fn scrollbar_animation_active(&self) -> bool {
self.v.animation_active() || self.h.animation_active()
}
}
mod widget_impl;