use std::cell::Cell;
use std::rc::Rc;
use crate::color::Color;
use crate::event::{Event, EventResult, MouseButton};
use crate::geometry::{Point, Rect, Size};
use crate::draw_ctx::DrawCtx;
use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
use crate::widget::Widget;
#[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: 15.0,
floating_width: 15.0,
handle_min_length: 10.0,
outer_margin: 5.0,
inner_margin: 7.0,
content_margin: 5.0,
margin_same: true,
kind: ScrollBarKind::default(),
color: ScrollBarColor::default(),
fade_strength: 1.0,
fade_size: 45.0,
}
}
}
impl ScrollBarStyle {
pub fn solid() -> Self {
Self {
bar_width: 8.0,
floating_width: 8.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::Foreground,
fade_strength: 0.0,
fade_size: 0.0,
}
}
pub fn thin() -> Self {
Self {
bar_width: 10.0,
floating_width: 4.0,
handle_min_length: 12.0,
outer_margin: 2.0,
inner_margin: 2.0,
content_margin: 0.0,
margin_same: true,
kind: ScrollBarKind::Floating,
color: ScrollBarColor::Background,
fade_strength: 0.0,
fade_size: 0.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);
}
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));
}
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));
}
fn scale_alpha(c: Color, a: f64) -> Color {
Color::rgba(c.r, c.g, c.b, c.a * (a as f32).clamp(0.0, 1.0))
}
const RIGHT_EDGE_GUARD: f64 = 4.0;
const BOTTOM_EDGE_GUARD: f64 = 4.0;
const GRAB_MARGIN: f64 = 6.0;
#[derive(Clone, Copy)]
struct AxisState {
enabled: bool,
offset: f64,
content: f64,
hovered_bar: bool,
hovered_thumb: bool,
dragging: bool,
drag_thumb_offset: f64,
hover_anim: crate::animation::Tween,
visibility_anim: crate::animation::Tween,
}
impl Default for AxisState {
fn default() -> Self {
Self {
enabled: false, offset: 0.0, content: 0.0,
hovered_bar: false, hovered_thumb: false, dragging: false,
drag_thumb_offset: 0.0,
hover_anim: crate::animation::Tween::new(0.0, 0.12),
visibility_anim: crate::animation::Tween::new(0.0, 0.18),
}
}
}
impl AxisState {
fn max_scroll(&self, viewport: f64) -> f64 {
(self.content - viewport).max(0.0)
}
fn interact(&self) -> bool {
self.hovered_bar || self.hovered_thumb || self.dragging
}
}
pub struct ScrollView {
bounds: Rect,
children: Vec<Box<dyn Widget>>, base: WidgetBase,
v: AxisState,
h: AxisState,
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>>>,
visibility_cell: Option<Rc<Cell<ScrollBarVisibility>>>,
style_cell: Option<Rc<Cell<ScrollBarStyle>>>,
viewport_cell: Option<Rc<Cell<Rect>>>,
}
impl ScrollView {
pub fn new(content: Box<dyn Widget>) -> Self {
Self {
bounds: Rect::default(),
children: vec![content],
base: WidgetBase::new(),
v: AxisState { enabled: true, ..AxisState::default() },
h: AxisState::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,
visibility_cell: None,
style_cell: None,
viewport_cell: None,
}
}
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_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_thumb_metrics(&self) -> Option<(f64, f64)> {
let (_, vh) = self.viewport();
if self.v.content <= vh { return None; }
let (lo, hi) = self.v_track_range();
let track_h = hi - lo;
let ratio = vh / self.v.content;
let thumb_h = (track_h * ratio).max(self.style.handle_min_length);
let travel = (track_h - thumb_h).max(0.0);
let max_s = self.v.max_scroll(vh);
let thumb_y = if max_s > 0.0 {
lo + travel * (1.0 - self.v.offset / max_s)
} else { lo + travel };
Some((thumb_y, thumb_h))
}
fn h_thumb_metrics(&self) -> Option<(f64, f64)> {
let (vw, _) = self.viewport();
if self.h.content <= vw { return None; }
let (lo, hi) = self.h_track_range();
let track_w = hi - lo;
let ratio = vw / self.h.content;
let thumb_w = (track_w * ratio).max(self.style.handle_min_length);
let travel = (track_w - thumb_w).max(0.0);
let max_s = self.h.max_scroll(vw);
let thumb_x = if max_s > 0.0 {
lo + travel * (self.h.offset / max_s)
} else { lo };
Some((thumb_x, thumb_w))
}
fn pos_on_v_thumb(&self, pos: Point) -> bool {
let bar_right = self.v_bar_right();
let bar_left = bar_right - self.style.bar_width;
let hit_left = bar_left - GRAB_MARGIN;
if pos.x < hit_left || pos.x >= bar_right { return false; }
if let Some((ty, th)) = self.v_thumb_metrics() {
pos.y >= ty && pos.y <= ty + th
} else { false }
}
fn pos_on_h_thumb(&self, pos: Point) -> bool {
let bar_bottom = self.h_bar_bottom();
let bar_top = bar_bottom + self.style.bar_width;
let hit_top = bar_top + GRAB_MARGIN;
if pos.y < bar_bottom || pos.y >= hit_top { return false; }
if let Some((tx, tw)) = self.h_thumb_metrics() {
pos.x >= tx && pos.x <= tx + tw
} else { false }
}
fn pos_in_v_hover(&self, pos: Point) -> bool {
let bar_right = self.v_bar_right();
let bar_left = bar_right - self.style.bar_width - GRAB_MARGIN;
pos.x >= bar_left && pos.x < bar_right
}
fn pos_in_h_hover(&self, pos: Point) -> bool {
let bar_bottom = self.h_bar_bottom();
let bar_top = bar_bottom + self.style.bar_width + GRAB_MARGIN;
pos.y >= bar_bottom && pos.y < bar_top
}
fn clamp_offsets(&mut self) {
let (vw, vh) = self.viewport();
self.v.offset = self.v.offset.clamp(0.0, self.v.max_scroll(vh)).round();
self.h.offset = self.h.offset.clamp(0.0, self.h.max_scroll(vw)).round();
}
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 should_paint_v(&self) -> bool {
let (_, vh) = self.viewport();
if self.v.content <= vh { return false; }
let floating = self.style.kind == ScrollBarKind::Floating;
match self.bar_visibility {
ScrollBarVisibility::AlwaysHidden => false,
ScrollBarVisibility::AlwaysVisible => true,
ScrollBarVisibility::VisibleWhenNeeded =>
!floating || self.v.hovered_bar || self.v.dragging,
}
}
fn should_paint_h(&self) -> bool {
let (vw, _) = self.viewport();
if self.h.content <= vw { return false; }
let floating = self.style.kind == ScrollBarKind::Floating;
match self.bar_visibility {
ScrollBarVisibility::AlwaysHidden => false,
ScrollBarVisibility::AlwaysVisible => true,
ScrollBarVisibility::VisibleWhenNeeded =>
!floating || self.h.hovered_bar || self.h.dragging,
}
}
}
impl Widget for ScrollView {
fn type_name(&self) -> &'static str { "ScrollView" }
fn bounds(&self) -> Rect { self.bounds }
fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
fn children(&self) -> &[Box<dyn Widget>] { &self.children }
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
fn margin(&self) -> Insets { self.base.margin }
fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
fn min_size(&self) -> Size { self.base.min_size }
fn max_size(&self) -> Size { self.base.max_size }
fn hit_test(&self, local_pos: Point) -> bool {
if self.v.dragging || self.h.dragging { return true; }
let b = self.bounds();
local_pos.x >= 0.0 && local_pos.x <= b.width
&& local_pos.y >= 0.0 && local_pos.y <= b.height
}
fn claims_pointer_exclusively(&self, local_pos: Point) -> bool {
if self.v.dragging || self.h.dragging { return true; }
let (vw, vh) = self.viewport();
if self.v.enabled && self.v.content > vh && self.pos_in_v_hover(local_pos) { return true; }
if self.h.enabled && self.h.content > vw && self.pos_in_h_hover(local_pos) { return true; }
false
}
fn layout(&mut self, available: Size) -> Size {
if let Some(c) = &self.offset_cell { self.v.offset = c.get(); }
if let Some(c) = &self.visibility_cell {
self.bar_visibility = c.get();
} else if !self.visibility_explicit {
self.bar_visibility = current_scroll_visibility();
}
if let Some(c) = &self.style_cell {
self.style = c.get();
} else if !self.style_explicit {
self.style = current_scroll_style();
}
self.bounds = Rect::new(0.0, 0.0, available.width, available.height);
let (vw_guess, _vh_guess) = self.viewport();
let child_in_w = if self.h.enabled { f64::MAX / 2.0 } else { vw_guess };
let child_in_h = f64::MAX / 2.0;
if let Some(child) = self.children.first_mut() {
let natural = child.layout(Size::new(child_in_w, child_in_h));
self.v.content = natural.height;
self.h.content = if self.h.enabled { natural.width } else { vw_guess };
}
let (vw, vh) = self.viewport();
if self.stick_to_bottom && self.was_at_bottom {
self.v.offset = self.v.max_scroll(vh);
}
self.clamp_offsets();
self.was_at_bottom = (self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
if let Some(c) = &self.offset_cell { c.set(self.v.offset); }
if let Some(c) = &self.max_scroll_cell { c.set(self.v.max_scroll(vh)); }
if let Some(c) = &self.viewport_cell {
c.set(Rect::new(self.h.offset, self.v.offset, vw, vh));
}
if let Some(child) = self.children.first_mut() {
let child_y = vh - self.v.content + self.v.offset;
let child_x = -self.h.offset;
child.set_bounds(Rect::new(
child_x.round(), child_y.round(),
if self.h.enabled { self.h.content } else { vw },
self.v.content,
));
}
available
}
fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
fn clip_children_rect(&self) -> Option<(f64, f64, f64, f64)> {
let (vw, vh) = self.viewport();
Some((0.0, self.bounds.height - vh, vw, vh))
}
fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
let v = ctx.visuals();
self.v.visibility_anim.set_target(if self.should_paint_v() { 1.0 } else { 0.0 });
self.h.visibility_anim.set_target(if self.should_paint_h() { 1.0 } else { 0.0 });
let v_alpha = self.v.visibility_anim.tick();
let h_alpha = self.h.visibility_anim.tick();
let paint_v = self.v.enabled && self.v.content > self.viewport().1 && v_alpha > 0.001;
let paint_h = self.h.enabled && self.h.content > self.viewport().0 && h_alpha > 0.001;
let track_color_base = match self.style.color {
ScrollBarColor::Background => v.scroll_track,
ScrollBarColor::Foreground => Color::rgba(
v.accent.r, v.accent.g, v.accent.b, 0.08),
};
let thumb_idle = match self.style.color {
ScrollBarColor::Background => v.scroll_thumb,
ScrollBarColor::Foreground => v.accent,
};
if paint_v {
if let Some((ty, th)) = self.v_thumb_metrics() {
let bar_right = self.v_bar_right();
self.v.hover_anim.set_target(if self.v.interact() { 1.0 } else { 0.0 });
let t = self.v.hover_anim.tick();
let bar_w = self.style.bar_width_at(t);
let bar_x = bar_right - bar_w;
let r = bar_w * 0.5;
let (lo, hi) = self.v_track_range();
ctx.set_fill_color(scale_alpha(track_color_base, v_alpha));
ctx.begin_path();
ctx.rounded_rect(bar_x, lo, bar_w, hi - lo, r);
ctx.fill();
let tc = if self.v.dragging {
v.scroll_thumb_dragging
} else if self.v.hovered_thumb {
v.scroll_thumb_hovered
} else { thumb_idle };
ctx.set_fill_color(scale_alpha(tc, v_alpha));
ctx.begin_path();
ctx.rounded_rect(bar_x, ty, bar_w, th, r);
ctx.fill();
}
}
if paint_h {
if let Some((tx, tw)) = self.h_thumb_metrics() {
let bar_bottom = self.h_bar_bottom();
self.h.hover_anim.set_target(if self.h.interact() { 1.0 } else { 0.0 });
let t = self.h.hover_anim.tick();
let bar_h = self.style.bar_width_at(t);
let r = bar_h * 0.5;
let (lo, hi) = self.h_track_range();
ctx.set_fill_color(scale_alpha(track_color_base, h_alpha));
ctx.begin_path();
ctx.rounded_rect(lo, bar_bottom, hi - lo, bar_h, r);
ctx.fill();
let tc = if self.h.dragging {
v.scroll_thumb_dragging
} else if self.h.hovered_thumb {
v.scroll_thumb_hovered
} else { thumb_idle };
ctx.set_fill_color(scale_alpha(tc, h_alpha));
ctx.begin_path();
ctx.rounded_rect(tx, bar_bottom, tw, bar_h, r);
ctx.fill();
}
}
if self.style.fade_strength > 0.001 && self.style.fade_size > 0.5 {
self.paint_fade(ctx);
}
}
fn on_event(&mut self, event: &Event) -> EventResult {
match event {
Event::MouseWheel { delta_y, delta_x, .. } => {
let mut consumed = false;
if self.v.enabled {
self.v.offset = self.v.offset + delta_y * 40.0;
consumed = true;
}
if self.h.enabled {
self.h.offset = self.h.offset + delta_x * 40.0;
consumed = true;
}
self.clamp_offsets();
let (_, vh) = self.viewport();
self.was_at_bottom = (self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
if let Some(c) = &self.offset_cell { c.set(self.v.offset); }
if consumed {
crate::animation::request_tick();
EventResult::Consumed
} else {
EventResult::Ignored
}
}
Event::MouseMove { pos } => {
let (vw, vh) = self.viewport();
let v_scroll = self.v.enabled && self.v.content > vh;
let h_scroll = self.h.enabled && self.h.content > vw;
let was_vb = self.v.hovered_bar;
let was_vt = self.v.hovered_thumb;
let was_hb = self.h.hovered_bar;
let was_ht = self.h.hovered_thumb;
self.v.hovered_bar = v_scroll && self.pos_in_v_hover(*pos);
self.v.hovered_thumb = v_scroll && self.pos_on_v_thumb(*pos);
self.h.hovered_bar = h_scroll && self.pos_in_h_hover(*pos);
self.h.hovered_thumb = h_scroll && self.pos_on_h_thumb(*pos);
if was_vb != self.v.hovered_bar || was_vt != self.v.hovered_thumb
|| was_hb != self.h.hovered_bar || was_ht != self.h.hovered_thumb
{
crate::animation::request_tick();
}
if self.v.dragging {
if let Some((_, th)) = self.v_thumb_metrics() {
let (lo, hi) = self.v_track_range();
let travel = (hi - lo - th).max(1.0);
let new_ty = (pos.y - self.v.drag_thumb_offset)
.clamp(lo, lo + travel);
let frac = 1.0 - (new_ty - lo) / travel;
self.v.offset = (frac * self.v.max_scroll(vh)).max(0.0);
self.clamp_offsets();
self.was_at_bottom =
(self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
if let Some(c) = &self.offset_cell { c.set(self.v.offset); }
}
crate::animation::request_tick();
return EventResult::Consumed;
}
if self.h.dragging {
if let Some((_, tw)) = self.h_thumb_metrics() {
let (lo, hi) = self.h_track_range();
let travel = (hi - lo - tw).max(1.0);
let new_tx = (pos.x - self.h.drag_thumb_offset)
.clamp(lo, lo + travel);
let frac = (new_tx - lo) / travel;
self.h.offset = (frac * self.h.max_scroll(vw)).max(0.0);
self.clamp_offsets();
}
crate::animation::request_tick();
return EventResult::Consumed;
}
EventResult::Ignored
}
Event::MouseDown { pos, button: MouseButton::Left, .. } => {
let (vw, vh) = self.viewport();
let v_scroll = self.v.enabled && self.v.content > vh;
let h_scroll = self.h.enabled && self.h.content > vw;
if v_scroll && self.pos_in_v_hover(*pos) {
if self.pos_on_v_thumb(*pos) {
let ty = self.v_thumb_metrics().map(|(y, _)| y).unwrap_or(0.0);
self.v.dragging = true;
self.v.drag_thumb_offset = pos.y - ty;
} else if let Some((ty, th)) = self.v_thumb_metrics() {
let page = (vh - 16.0).max(20.0);
if pos.y > ty + th {
self.v.offset = (self.v.offset - page).max(0.0);
} else if pos.y < ty {
self.v.offset = (self.v.offset + page).min(self.v.max_scroll(vh));
}
self.clamp_offsets();
if let Some(c) = &self.offset_cell { c.set(self.v.offset); }
crate::animation::request_tick();
}
return EventResult::Consumed;
}
if h_scroll && self.pos_in_h_hover(*pos) {
if self.pos_on_h_thumb(*pos) {
let tx = self.h_thumb_metrics().map(|(x, _)| x).unwrap_or(0.0);
self.h.dragging = true;
self.h.drag_thumb_offset = pos.x - tx;
} else if let Some((tx, tw)) = self.h_thumb_metrics() {
let page = (vw - 16.0).max(20.0);
if pos.x < tx {
self.h.offset = (self.h.offset - page).max(0.0);
} else if pos.x > tx + tw {
self.h.offset = (self.h.offset + page).min(self.h.max_scroll(vw));
}
self.clamp_offsets();
crate::animation::request_tick();
}
return EventResult::Consumed;
}
EventResult::Ignored
}
Event::MouseUp { button: MouseButton::Left, .. } => {
let was = self.v.dragging || self.h.dragging;
self.v.dragging = false;
self.h.dragging = false;
if was {
crate::animation::request_tick();
EventResult::Consumed
} else {
EventResult::Ignored
}
}
_ => EventResult::Ignored,
}
}
}
impl ScrollView {
fn paint_fade(&self, ctx: &mut dyn DrawCtx) {
let v = ctx.visuals();
let c = v.panel_fill;
let (vw, vh) = self.viewport();
let strength = self.style.fade_strength.clamp(0.0, 1.0) as f32;
let size = self.style.fade_size.max(0.0);
let max_a = strength;
if self.v.enabled {
if self.v.offset > 0.5 {
Self::fill_v_gradient(ctx, c, max_a, 0.0, self.bounds.height - size, vw, size, false);
}
if (self.v.max_scroll(vh) - self.v.offset) > 0.5 {
let y_bottom = self.bounds.height - vh;
Self::fill_v_gradient(ctx, c, max_a, 0.0, y_bottom, vw, size, true);
}
}
if self.h.enabled {
if self.h.offset > 0.5 {
Self::fill_h_gradient(ctx, c, max_a, 0.0, self.bounds.height - vh, size, vh, true);
}
if (self.h.max_scroll(vw) - self.h.offset) > 0.5 {
Self::fill_h_gradient(ctx, c, max_a, vw - size, self.bounds.height - vh, size, vh, false);
}
}
}
fn fill_v_gradient(
ctx: &mut dyn DrawCtx,
c: Color,
max_alpha: f32,
x: f64,
y: f64,
w: f64,
h: f64,
opaque_at_bottom: bool,
) {
const STEPS: usize = 64;
let strip_h = h / STEPS as f64;
for i in 0..STEPS {
let t = (i as f32 + 0.5) / STEPS as f32;
let a = if opaque_at_bottom { 1.0 - t } else { t };
ctx.set_fill_color(Color::rgba(c.r, c.g, c.b, a * max_alpha));
ctx.begin_path();
ctx.rect(x, y + i as f64 * strip_h, w, strip_h + 0.5);
ctx.fill();
}
}
fn fill_h_gradient(
ctx: &mut dyn DrawCtx,
c: Color,
max_alpha: f32,
x: f64,
y: f64,
w: f64,
h: f64,
opaque_at_left: bool,
) {
const STEPS: usize = 64;
let strip_w = w / STEPS as f64;
for i in 0..STEPS {
let t = (i as f32 + 0.5) / STEPS as f32;
let a = if opaque_at_left { 1.0 - t } else { t };
ctx.set_fill_color(Color::rgba(c.r, c.g, c.b, a * max_alpha));
ctx.begin_path();
ctx.rect(x + i as f64 * strip_w, y, strip_w + 0.5, h);
ctx.fill();
}
}
}