1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use crate::{api::prelude::*, proc_macros::*};
#[derive(Default, AsAny)]
pub struct ScrollViewerState {
delta: Option<Point>,
}
impl ScrollViewerState {
fn scroll(&mut self, delta: Point) {
self.delta = Some(delta);
}
}
impl State for ScrollViewerState {
fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
if let Some(delta) = self.delta {
self.delta = None;
let mode = *ctx.widget().get::<ScrollViewerMode>("mode");
if mode.vertical != ScrollMode::Auto && mode.horizontal != ScrollMode::Auto {
return;
}
let size = ctx.widget().get::<Rectangle>("bounds").size();
let speed = *ctx.widget().get::<f64>("speed");
let mut padding = *ctx.widget().get::<Thickness>("padding");
if let Some(child) = &mut ctx.try_child_from_index(0) {
let child_size = child.get::<Rectangle>("bounds").size();
if mode.vertical == ScrollMode::Auto && child_size.height() > size.height() {
padding.set_top(offset(
size.height(),
child_size.height(),
padding.top(),
delta.y() * speed,
));
}
if mode.horizontal == ScrollMode::Auto && child_size.width() > size.width() {
padding.set_left(offset(
size.width(),
child_size.width(),
padding.left(),
delta.x() * speed,
));
}
} else {
return;
}
ctx.widget().set("padding", padding);
}
}
}
widget!(
ScrollViewer<ScrollViewerState>: MouseHandler {
mode: ScrollViewerMode,
speed: f64,
padding: Thickness
}
);
impl Template for ScrollViewer {
fn template(self, id: Entity, _: &mut BuildContext) -> Self {
self.name("ScrollViewer")
.padding(0)
.speed(2)
.clip(true)
.mode(ScrollViewerMode::default())
.on_scroll(move |states, p| {
states.get_mut::<ScrollViewerState>(id).scroll(p);
false
})
}
fn layout(&self) -> Box<dyn Layout> {
Box::new(PaddingLayout::new())
}
}
fn offset(size: f64, child_size: f64, current_offset: f64, delta: f64) -> f64 {
(current_offset + delta).min(0.).max(size - child_size)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_offset() {
let width = 100.;
let child_width = 200.0;
assert!((offset(width, child_width, 0., -10.) + 10.).abs() < f64::EPSILON);
assert!((offset(width, child_width, 0., -200.) + 100.).abs() < f64::EPSILON);
assert!((offset(width, child_width, 0., 200.) + 0.).abs() < f64::EPSILON);
}
}