1use std::cell::{Cell, RefCell};
2use std::rc::Rc;
3
4use web_time::Instant;
5
6use crate::nested_scroll::{NestedScrollConnection, NestedScrollSource};
7use crate::{Signal, Vec2, request_frame, signal};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum ScrollAxis {
12 Vertical,
13 Horizontal,
14 Both,
15}
16
17#[derive(Clone, Default)]
22pub struct ScrollAxisBinding {
23 pub on_scroll: Option<Rc<dyn Fn(Vec2) -> Vec2>>,
24 pub set_viewport_main: Option<Rc<dyn Fn(f32)>>,
26 pub set_content_main: Option<Rc<dyn Fn(f32)>>,
28 pub get_offset_main: Option<Rc<dyn Fn() -> f32>>,
29 pub set_offset_main: Option<Rc<dyn Fn(f32)>>,
30 pub show_scrollbar: bool,
31 pub tick: Option<Rc<dyn Fn()>>,
32 pub set_nested_scroll_parent: Option<Rc<dyn Fn(NestedScrollConnection)>>,
33}
34
35impl std::fmt::Debug for ScrollAxisBinding {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("ScrollAxisBinding")
38 .field("has_on_scroll", &self.on_scroll.is_some())
39 .field("set_viewport_main", &self.set_viewport_main.is_some())
40 .field("set_content_main", &self.set_content_main.is_some())
41 .field("get_offset_main", &self.get_offset_main.is_some())
42 .field("set_offset_main", &self.set_offset_main.is_some())
43 .field("show_scrollbar", &self.show_scrollbar)
44 .field("tick", &self.tick.is_some())
45 .field(
46 "set_nested_scroll_parent",
47 &self.set_nested_scroll_parent.is_some(),
48 )
49 .finish()
50 }
51}
52
53#[derive(Clone, Default)]
55pub struct ScrollBothBinding {
56 pub on_scroll: Option<Rc<dyn Fn(Vec2) -> Vec2>>,
57 pub set_viewport_width: Option<Rc<dyn Fn(f32)>>,
58 pub set_viewport_height: Option<Rc<dyn Fn(f32)>>,
59 pub set_content_width: Option<Rc<dyn Fn(f32)>>,
60 pub set_content_height: Option<Rc<dyn Fn(f32)>>,
61 pub get_offset_xy: Option<Rc<dyn Fn() -> (f32, f32)>>,
62 pub set_offset_xy: Option<Rc<dyn Fn(f32, f32)>>,
63 pub show_scrollbar: bool,
64 pub tick: Option<Rc<dyn Fn()>>,
65 pub set_nested_scroll_parent: Option<Rc<dyn Fn(NestedScrollConnection)>>,
66}
67
68impl std::fmt::Debug for ScrollBothBinding {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 f.debug_struct("ScrollBothBinding")
71 .field("has_on_scroll", &self.on_scroll.is_some())
72 .field("set_viewport_width", &self.set_viewport_width.is_some())
73 .field("set_viewport_height", &self.set_viewport_height.is_some())
74 .field("set_content_width", &self.set_content_width.is_some())
75 .field("set_content_height", &self.set_content_height.is_some())
76 .field("get_offset_xy", &self.get_offset_xy.is_some())
77 .field("set_offset_xy", &self.set_offset_xy.is_some())
78 .field("show_scrollbar", &self.show_scrollbar)
79 .field("tick", &self.tick.is_some())
80 .field(
81 "set_nested_scroll_parent",
82 &self.set_nested_scroll_parent.is_some(),
83 )
84 .finish()
85 }
86}
87
88#[derive(Clone, Debug)]
89pub enum ScrollBinding {
90 Vertical(ScrollAxisBinding),
91 Horizontal(ScrollAxisBinding),
92 Both(ScrollBothBinding),
93}
94
95impl ScrollBinding {
96 pub fn axis(&self) -> ScrollAxis {
97 match self {
98 ScrollBinding::Vertical(_) => ScrollAxis::Vertical,
99 ScrollBinding::Horizontal(_) => ScrollAxis::Horizontal,
100 ScrollBinding::Both(_) => ScrollAxis::Both,
101 }
102 }
103}
104
105const OVERSHOOT_DECAY_PER_60HZ: f32 = 0.78;
106
107#[derive(Clone)]
110pub struct ScrollPhysics {
111 pub(crate) vel: f32,
112 last_t: Instant,
113 last_input_t: Instant,
114 pub(crate) animating: bool,
115 stop_velocity: f32,
116 input_activate_velocity: f32,
117}
118
119impl ScrollPhysics {
120 pub fn new(_decay_per_60hz: f32, stop_velocity: f32, input_activate_velocity: f32) -> Self {
121 let now = Instant::now();
122 Self {
123 vel: 0.0,
124 last_t: now,
125 last_input_t: now,
126 animating: false,
127 stop_velocity,
128 input_activate_velocity,
129 }
130 }
131
132 pub fn record_input(&mut self, consumed: f32) {
133 let now = Instant::now();
134 let raw_dt = (now - self.last_input_t).as_secs_f32();
135 let dt = raw_dt.clamp(1.0 / 240.0, 1.0 / 15.0);
136 self.last_input_t = now;
137 if raw_dt > 0.1 {
138 self.vel = 0.0;
139 }
140 let instant_vel = consumed / dt;
141 const SMOOTHING: f32 = 0.35;
142 self.vel = self.vel * (1.0 - SMOOTHING) + instant_vel * SMOOTHING;
143 const MAX_VEL: f32 = 8000.0;
144 self.vel = self.vel.clamp(-MAX_VEL, MAX_VEL);
145 self.animating = self.vel.abs() > self.input_activate_velocity;
146 }
147
148 fn dt(&mut self) -> f32 {
149 let now = Instant::now();
150 let dt = (now - self.last_t).as_secs_f32().min(0.1);
151 self.last_t = now;
152 dt
153 }
154
155 pub fn tick_integrate(&mut self, current: f32, min: f32, max: f32) -> Option<f32> {
156 if !self.animating {
157 return None;
158 }
159 let dt = self.dt();
160 if dt <= 0.0 {
161 return None;
162 }
163 let vel0 = self.vel;
164 if vel0.abs() < self.stop_velocity {
165 self.vel = 0.0;
166 self.animating = false;
167 return None;
168 }
169 let new = (current + vel0 * dt).clamp(min, max);
170 if (new - current).abs() < 0.01 && (current <= min || current >= max) {
171 self.vel = 0.0;
172 self.animating = false;
173 return None;
174 }
175 let speed = self.vel.abs();
176 let t = (speed / 4000.0).min(1.0);
177 let effective_decay = 0.85 + t * 0.10;
178 let decay = effective_decay.powf(dt * 60.0);
179 self.vel = vel0 * decay;
180 if self.vel.abs() < self.stop_velocity {
181 self.vel = 0.0;
182 self.animating = false;
183 return None;
184 }
185 Some(new)
186 }
187
188 pub fn is_animating(&self) -> bool {
189 self.animating
190 }
191}
192
193#[derive(Clone)]
195pub struct ScrollState {
196 scroll_offset: Signal<f32>,
197 viewport_height: Signal<f32>,
198 content_height: Signal<f32>,
199 physics: RefCell<ScrollPhysics>,
200 overscroll: Signal<f32>,
201 overscroll_enabled: bool,
202 pub(crate) parent_connection: Rc<RefCell<Option<NestedScrollConnection>>>,
203 show_scrollbar: Cell<bool>,
204 prev_tick: Cell<Instant>,
205}
206
207impl Default for ScrollState {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213impl ScrollState {
214 pub fn new() -> Self {
215 Self {
216 scroll_offset: signal(0.0),
217 viewport_height: signal(0.0),
218 content_height: signal(0.0),
219 physics: RefCell::new(ScrollPhysics::new(0.90, 15.0, 50.0)),
220 overscroll: signal(0.0),
221 overscroll_enabled: true,
222 parent_connection: Rc::new(RefCell::new(None)),
223 show_scrollbar: Cell::new(true),
224 prev_tick: Cell::new(Instant::now()),
225 }
226 }
227
228 pub fn set_overscroll_enabled(&mut self, enabled: bool) {
229 self.overscroll_enabled = enabled;
230 }
231
232 pub fn overscroll_offset(&self) -> f32 {
233 self.overscroll.get()
234 }
235
236 pub fn set_nested_scroll_parent(&self, conn: NestedScrollConnection) {
237 *self.parent_connection.borrow_mut() = Some(conn);
238 }
239
240 pub fn set_show_scrollbar(&self, show: bool) {
241 self.show_scrollbar.set(show);
242 }
243
244 pub fn set_viewport_height(&self, h: f32) {
245 let h = h.max(0.0);
246 if (self.viewport_height.get() - h).abs() > 0.5 {
247 self.viewport_height.set(h);
248 self.clamp_offset();
249 }
250 }
251
252 pub fn set_content_height(&self, h: f32) {
253 let h = h.max(0.0);
254 if (self.content_height.get() - h).abs() > 0.5 {
255 self.content_height.set(h);
256 self.clamp_offset();
257 }
258 }
259
260 pub fn set_overscroll(&self, val: f32) {
261 self.overscroll.set(val);
262 }
263
264 pub fn set_offset(&self, off: f32) {
265 let vh = self.viewport_height.get();
266 let ch = self.content_height.get();
267 let max_off = (ch - vh).max(0.0);
268 self.scroll_offset.set(off.clamp(0.0, max_off));
269 }
270
271 fn clamp_offset(&self) {
272 let vh = self.viewport_height.get();
273 let ch = self.content_height.get();
274 let max_off = (ch - vh).max(0.0);
275 self.scroll_offset.update(|o| {
276 if *o > max_off {
277 *o = max_off;
278 }
279 if *o < 0.0 {
280 *o = 0.0;
281 }
282 });
283 }
284
285 pub fn get(&self) -> f32 {
286 self.scroll_offset.get()
287 }
288
289 pub fn scroll_immediate(&self, dy: f32) -> f32 {
290 let before = self.scroll_offset.get();
291 let vh = self.viewport_height.get();
292 let ch = self.content_height.get();
293 let max_off = (ch - vh).max(0.0);
294
295 let os = self.overscroll.get();
296 if self.overscroll_enabled && os.abs() > 0.5 {
297 if os.signum() * dy < 0.0 {
298 let reduction = dy.abs().min(os.abs());
299 self.overscroll.set(os - os.signum() * reduction);
300 let remainder = dy - dy.signum() * reduction;
301 if remainder.abs() > 0.5 {
302 let new_off = (before + remainder).clamp(0.0, max_off);
303 self.scroll_offset.set(new_off);
304 let consumed = new_off - before;
305 let leftover = remainder - consumed;
306 self.physics.borrow_mut().record_input(consumed);
307 return leftover;
308 }
309 return 0.0;
310 } else {
311 let total = os + dy;
312 let bandied = Self::rubber_band(total, 150.0);
313 self.overscroll.set(bandied);
314 self.physics.borrow_mut().record_input(dy);
315 return 0.0;
316 }
317 }
318
319 let can_overscroll = self.overscroll_enabled && max_off > 5.0;
320 let new_off = (before + dy).clamp(0.0, max_off);
321 self.scroll_offset.set(new_off);
322
323 let consumed = new_off - before;
324 let leftover = dy - consumed;
325
326 if can_overscroll
327 && leftover.abs() > 0.5
328 && ((before <= 0.0 && dy < 0.0) || (before >= max_off && dy > 0.0))
329 {
330 let bandied = Self::rubber_band(leftover, 150.0);
331 self.overscroll.set(os + bandied);
332 self.physics.borrow_mut().record_input(consumed);
333 return 0.0;
334 }
335
336 self.physics.borrow_mut().record_input(consumed);
337 leftover
338 }
339
340 fn rubber_band(amount: f32, max: f32) -> f32 {
341 let sign = amount.signum();
342 let abs_val = amount.abs();
343 if abs_val <= 0.0 {
344 return 0.0;
345 }
346 (1.0 - 1.0 / (1.0 + abs_val / max)) * max * sign
347 }
348
349 pub fn tick(&self) -> bool {
350 let now = Instant::now();
351 let dt = (now - self.prev_tick.get()).as_secs_f32().min(0.1);
352 self.prev_tick.set(now);
353
354 if self.overscroll_enabled {
355 let os = self.overscroll.get();
356 if os.abs() > 0.5 {
357 let decayed = os * OVERSHOOT_DECAY_PER_60HZ.powf(dt * 60.0);
358 if decayed.abs() < 0.5 {
359 self.overscroll.set(0.0);
360 } else {
361 self.overscroll.set(decayed);
362 }
363 request_frame();
364 return true;
365 }
366 }
367
368 let vh = self.viewport_height.get();
369 let ch = self.content_height.get();
370 let max_off = (ch - vh).max(0.0);
371
372 let mut p = self.physics.borrow_mut();
373 if let Some(new_off) = p.tick_integrate(self.scroll_offset.get(), 0.0, max_off) {
374 drop(p);
375 self.scroll_offset.set(new_off);
376 request_frame();
377 true
378 } else {
379 false
380 }
381 }
382
383 pub fn show_scrollbar(&self) -> bool {
384 self.show_scrollbar.get()
385 }
386
387 pub fn to_binding(&self) -> ScrollBinding {
389 let this = Rc::new(self.clone());
390 let pc = Rc::clone(&this.parent_connection);
391 let c_on_scroll = {
392 let state = Rc::clone(&pc);
393 let this = Rc::clone(&this);
394 Rc::new(move |d: Vec2| -> Vec2 {
395 let d = run_pre_scroll(&*state, d);
396 let leftover_y = this.scroll_immediate(d.y);
397 let result = Vec2 {
398 x: d.x,
399 y: leftover_y,
400 };
401 run_post_scroll(&*state, result)
402 })
403 } as Rc<dyn Fn(Vec2) -> Vec2>;
404 let c_set_viewport = {
405 let this = Rc::clone(&this);
406 Rc::new(move |h: f32| this.set_viewport_height(h))
407 };
408 let c_set_content = {
409 let this = Rc::clone(&this);
410 Rc::new(move |h: f32| this.set_content_height(h))
411 };
412 let c_get = {
413 let this = Rc::clone(&this);
414 Rc::new(move || this.get() + this.overscroll_offset())
415 };
416 let c_set = {
417 let this = Rc::clone(&this);
418 Rc::new(move |off: f32| this.set_offset(off))
419 };
420 let c_tick = {
421 let this = Rc::clone(&this);
422 Rc::new(move || {
423 this.tick();
424 })
425 };
426 let c_set_nested = {
427 let state = Rc::clone(&pc);
428 Rc::new(move |conn| {
429 *state.borrow_mut() = Some(conn);
430 })
431 };
432 ScrollBinding::Vertical(ScrollAxisBinding {
433 on_scroll: Some(c_on_scroll),
434 set_viewport_main: Some(c_set_viewport),
435 set_content_main: Some(c_set_content),
436 get_offset_main: Some(c_get),
437 set_offset_main: Some(c_set),
438 show_scrollbar: self.show_scrollbar(),
439 tick: Some(c_tick),
440 set_nested_scroll_parent: Some(c_set_nested),
441 })
442 }
443}
444
445#[derive(Clone)]
447pub struct HorizontalScrollState {
448 scroll_offset: Signal<f32>,
449 viewport_width: Signal<f32>,
450 content_width: Signal<f32>,
451 physics: RefCell<ScrollPhysics>,
452 overscroll: Signal<f32>,
453 overscroll_enabled: bool,
454 pub(crate) parent_connection: Rc<RefCell<Option<NestedScrollConnection>>>,
455 show_scrollbar: Cell<bool>,
456 prev_tick: Cell<Instant>,
457}
458
459impl Default for HorizontalScrollState {
460 fn default() -> Self {
461 Self::new()
462 }
463}
464
465impl HorizontalScrollState {
466 pub fn new() -> Self {
467 Self {
468 scroll_offset: signal(0.0),
469 viewport_width: signal(0.0),
470 content_width: signal(0.0),
471 physics: RefCell::new(ScrollPhysics::new(0.90, 15.0, 50.0)),
472 overscroll: signal(0.0),
473 overscroll_enabled: true,
474 parent_connection: Rc::new(RefCell::new(None)),
475 show_scrollbar: Cell::new(true),
476 prev_tick: Cell::new(Instant::now()),
477 }
478 }
479
480 pub fn set_overscroll_enabled(&mut self, enabled: bool) {
481 self.overscroll_enabled = enabled;
482 }
483
484 pub fn overscroll_offset(&self) -> f32 {
485 self.overscroll.get()
486 }
487
488 pub fn set_nested_scroll_parent(&self, conn: NestedScrollConnection) {
489 *self.parent_connection.borrow_mut() = Some(conn);
490 }
491
492 pub fn set_show_scrollbar(&self, show: bool) {
493 self.show_scrollbar.set(show);
494 }
495
496 pub fn set_viewport_width(&self, w: f32) {
497 let w = w.max(0.0);
498 if (self.viewport_width.get() - w).abs() > 0.5 {
499 self.viewport_width.set(w);
500 self.clamp();
501 }
502 }
503
504 pub fn set_content_width(&self, w: f32) {
505 let w = w.max(0.0);
506 if (self.content_width.get() - w).abs() > 0.5 {
507 self.content_width.set(w);
508 self.clamp();
509 }
510 }
511
512 pub fn set_overscroll(&self, val: f32) {
513 self.overscroll.set(val);
514 }
515
516 pub fn set_offset(&self, off: f32) {
517 let max_off = (self.content_width.get() - self.viewport_width.get()).max(0.0);
518 self.scroll_offset.set(off.clamp(0.0, max_off));
519 }
520
521 fn clamp(&self) {
522 let max_off = (self.content_width.get() - self.viewport_width.get()).max(0.0);
523 self.scroll_offset.update(|o| {
524 *o = o.clamp(0.0, max_off);
525 });
526 }
527
528 pub fn get(&self) -> f32 {
529 self.scroll_offset.get()
530 }
531
532 pub fn scroll_immediate(&self, dx: f32) -> f32 {
533 let before = self.scroll_offset.get();
534 let max_off = (self.content_width.get() - self.viewport_width.get()).max(0.0);
535
536 let os = self.overscroll.get();
537 if self.overscroll_enabled && os.abs() > 0.5 {
538 if os.signum() * dx < 0.0 {
539 let reduction = dx.abs().min(os.abs());
540 self.overscroll.set(os - os.signum() * reduction);
541 let remainder = dx - dx.signum() * reduction;
542 if remainder.abs() > 0.5 {
543 let new_off = (before + remainder).clamp(0.0, max_off);
544 self.scroll_offset.set(new_off);
545 let consumed = new_off - before;
546 let leftover = remainder - consumed;
547 self.physics.borrow_mut().record_input(consumed);
548 return leftover;
549 }
550 return 0.0;
551 } else {
552 let total = os + dx;
553 let bandied = ScrollState::rubber_band(total, 150.0);
554 self.overscroll.set(bandied);
555 self.physics.borrow_mut().record_input(dx);
556 return 0.0;
557 }
558 }
559
560 let can_overscroll = self.overscroll_enabled && max_off > 5.0;
561 let new_off = (before + dx).clamp(0.0, max_off);
562 self.scroll_offset.set(new_off);
563
564 let consumed = new_off - before;
565 let leftover = dx - consumed;
566
567 if can_overscroll
568 && leftover.abs() > 0.5
569 && ((before <= 0.0 && dx < 0.0) || (before >= max_off && dx > 0.0))
570 {
571 let bandied = ScrollState::rubber_band(leftover, 150.0);
572 self.overscroll.set(os + bandied);
573 self.physics.borrow_mut().record_input(consumed);
574 return 0.0;
575 }
576
577 self.physics.borrow_mut().record_input(consumed);
578 leftover
579 }
580
581 pub fn tick(&self) -> bool {
582 let now = Instant::now();
583 let dt = (now - self.prev_tick.get()).as_secs_f32().min(0.1);
584 self.prev_tick.set(now);
585
586 if self.overscroll_enabled {
587 let os = self.overscroll.get();
588 if os.abs() > 0.5 {
589 let decayed = os * OVERSHOOT_DECAY_PER_60HZ.powf(dt * 60.0);
590 if decayed.abs() < 0.5 {
591 self.overscroll.set(0.0);
592 } else {
593 self.overscroll.set(decayed);
594 }
595 request_frame();
596 return true;
597 }
598 }
599
600 let max_off = (self.content_width.get() - self.viewport_width.get()).max(0.0);
601 let mut p = self.physics.borrow_mut();
602 if let Some(new_off) = p.tick_integrate(self.scroll_offset.get(), 0.0, max_off) {
603 drop(p);
604 self.scroll_offset.set(new_off);
605 request_frame();
606 true
607 } else {
608 false
609 }
610 }
611
612 pub fn show_scrollbar(&self) -> bool {
613 self.show_scrollbar.get()
614 }
615
616 pub fn to_binding(&self) -> ScrollBinding {
617 let this = Rc::new(self.clone());
618 let pc = Rc::clone(&this.parent_connection);
619 let c_on_scroll = {
620 let state = Rc::clone(&pc);
621 let this = Rc::clone(&this);
622 Rc::new(move |d: Vec2| -> Vec2 {
623 let d = run_pre_scroll(&*state, d);
624 let leftover_x = this.scroll_immediate(d.x);
625 let result = Vec2 {
626 x: leftover_x,
627 y: d.y,
628 };
629 run_post_scroll(&*state, result)
630 })
631 } as Rc<dyn Fn(Vec2) -> Vec2>;
632 let c_set_vp = {
633 let this = Rc::clone(&this);
634 Rc::new(move |w: f32| this.set_viewport_width(w))
635 };
636 let c_set_ct = {
637 let this = Rc::clone(&this);
638 Rc::new(move |w: f32| this.set_content_width(w))
639 };
640 let c_get = {
641 let this = Rc::clone(&this);
642 Rc::new(move || this.get() + this.overscroll_offset())
643 };
644 let c_set = {
645 let this = Rc::clone(&this);
646 Rc::new(move |off: f32| this.set_offset(off))
647 };
648 let c_tick = {
649 let this = Rc::clone(&this);
650 Rc::new(move || {
651 this.tick();
652 })
653 };
654 let c_set_nested = {
655 let state = Rc::clone(&pc);
656 Rc::new(move |conn| {
657 *state.borrow_mut() = Some(conn);
658 })
659 };
660 ScrollBinding::Horizontal(ScrollAxisBinding {
661 on_scroll: Some(c_on_scroll),
662 set_viewport_main: Some(c_set_vp),
663 set_content_main: Some(c_set_ct),
664 get_offset_main: Some(c_get),
665 set_offset_main: Some(c_set),
666 show_scrollbar: this.show_scrollbar(),
667 tick: Some(c_tick),
668 set_nested_scroll_parent: Some(c_set_nested),
669 })
670 }
671}
672
673#[derive(Clone)]
675pub struct ScrollStateXY {
676 off_x: Signal<f32>,
677 off_y: Signal<f32>,
678 vp_w: Signal<f32>,
679 vp_h: Signal<f32>,
680 c_w: Signal<f32>,
681 c_h: Signal<f32>,
682 physics_x: RefCell<ScrollPhysics>,
683 physics_y: RefCell<ScrollPhysics>,
684 os_x: Signal<f32>,
685 os_y: Signal<f32>,
686 overscroll_enabled: bool,
687 pub(crate) parent_connection: Rc<RefCell<Option<NestedScrollConnection>>>,
688 show_scrollbar: Cell<bool>,
689 prev_tick: Cell<Instant>,
690}
691
692impl Default for ScrollStateXY {
693 fn default() -> Self {
694 Self::new()
695 }
696}
697
698impl ScrollStateXY {
699 pub fn new() -> Self {
700 Self {
701 off_x: signal(0.0),
702 off_y: signal(0.0),
703 vp_w: signal(0.0),
704 vp_h: signal(0.0),
705 c_w: signal(0.0),
706 c_h: signal(0.0),
707 physics_x: RefCell::new(ScrollPhysics::new(0.90, 15.0, 50.0)),
708 physics_y: RefCell::new(ScrollPhysics::new(0.90, 15.0, 50.0)),
709 os_x: signal(0.0),
710 os_y: signal(0.0),
711 overscroll_enabled: true,
712 parent_connection: Rc::new(RefCell::new(None)),
713 show_scrollbar: Cell::new(true),
714 prev_tick: Cell::new(Instant::now()),
715 }
716 }
717
718 pub fn set_overscroll_enabled(&mut self, enabled: bool) {
719 self.overscroll_enabled = enabled;
720 }
721
722 pub fn set_show_scrollbar(&self, show: bool) {
723 self.show_scrollbar.set(show);
724 }
725
726 pub fn overscroll_offset(&self) -> (f32, f32) {
727 (self.os_x.get(), self.os_y.get())
728 }
729
730 pub fn set_nested_scroll_parent(&self, conn: NestedScrollConnection) {
731 *self.parent_connection.borrow_mut() = Some(conn);
732 }
733
734 pub fn set_viewport(&self, w: f32, h: f32) {
735 let w = w.max(0.0);
736 let h = h.max(0.0);
737 let changed = (self.vp_w.get() - w).abs() > 0.5 || (self.vp_h.get() - h).abs() > 0.5;
738 if changed {
739 self.vp_w.set(w);
740 self.vp_h.set(h);
741 self.clamp();
742 }
743 }
744 pub fn set_content(&self, w: f32, h: f32) {
745 let w = w.max(0.0);
746 let h = h.max(0.0);
747 let changed = (self.c_w.get() - w).abs() > 0.5 || (self.c_h.get() - h).abs() > 0.5;
748 if changed {
749 self.c_w.set(w);
750 self.c_h.set(h);
751 self.clamp();
752 }
753 }
754 pub fn set_offset_xy(&self, x: f32, y: f32) {
755 let max_x = (self.c_w.get() - self.vp_w.get()).max(0.0);
756 let max_y = (self.c_h.get() - self.vp_h.get()).max(0.0);
757 self.off_x.set(x.clamp(0.0, max_x));
758 self.off_y.set(y.clamp(0.0, max_y));
759 }
760 fn clamp(&self) {
761 let max_x = (self.c_w.get() - self.vp_w.get()).max(0.0);
762 let max_y = (self.c_h.get() - self.vp_h.get()).max(0.0);
763 self.off_x.update(|x| *x = x.clamp(0.0, max_x));
764 self.off_y.update(|y| *y = y.clamp(0.0, max_y));
765 }
766 pub fn get(&self) -> (f32, f32) {
767 (self.off_x.get(), self.off_y.get())
768 }
769 fn rubber_band(amount: f32, max: f32) -> f32 {
770 let sign = amount.signum();
771 let abs_val = amount.abs();
772 let result = if abs_val <= 0.0 {
773 0.0
774 } else {
775 (1.0 - 1.0 / (1.0 + abs_val / max)) * max
776 };
777 result * sign
778 }
779 fn os_scroll_axis(
780 off: &Signal<f32>,
781 os: &Signal<f32>,
782 overscroll_enabled: bool,
783 max_off: f32,
784 dx: f32,
785 physics: &mut ScrollPhysics,
786 ) -> f32 {
787 let before = off.get();
788 let os_val = os.get();
789 if overscroll_enabled && os_val.abs() > 0.5 {
790 if os_val.signum() * dx < 0.0 {
791 let reduction = dx.abs().min(os_val.abs());
792 os.set(os_val - os_val.signum() * reduction);
793 let remainder = dx - dx.signum() * reduction;
794 if remainder.abs() > 0.5 {
795 let new_off = (before + remainder).clamp(0.0, max_off);
796 let consumed = new_off - before;
797 off.set(new_off);
798 let leftover = remainder - consumed;
799 physics.record_input(consumed);
800 return leftover;
801 }
802 return 0.0;
803 } else {
804 let total = os_val + dx;
805 let bandied = Self::rubber_band(total, 150.0);
806 os.set(bandied);
807 physics.record_input(dx);
808 return 0.0;
809 }
810 }
811
812 let can_os = overscroll_enabled && max_off > 5.0;
813 let new_off = (before + dx).clamp(0.0, max_off);
814 off.set(new_off);
815 let consumed = new_off - before;
816 let leftover = dx - consumed;
817
818 if can_os
819 && leftover.abs() > 0.5
820 && ((before <= 0.0 && dx < 0.0) || (before >= max_off && dx > 0.0))
821 {
822 let bandied = Self::rubber_band(leftover, 150.0);
823 os.set(os_val + bandied);
824 physics.record_input(consumed);
825 return 0.0;
826 }
827
828 physics.record_input(consumed);
829 leftover
830 }
831 pub fn scroll_immediate(&self, d: Vec2) -> Vec2 {
832 let max_x = (self.c_w.get() - self.vp_w.get()).max(0.0);
833 let max_y = (self.c_h.get() - self.vp_h.get()).max(0.0);
834
835 let mut px = self.physics_x.borrow_mut();
836 let mut py = self.physics_y.borrow_mut();
837 let lx = Self::os_scroll_axis(&self.off_x, &self.os_x, self.overscroll_enabled, max_x, d.x, &mut px);
838 let ly = Self::os_scroll_axis(&self.off_y, &self.os_y, self.overscroll_enabled, max_y, d.y, &mut py);
839 drop((px, py));
840
841 Vec2 { x: lx, y: ly }
842 }
843 fn tick_os_axis(os: &Signal<f32>, enabled: bool, dt: f32) -> bool {
844 if !enabled {
845 return false;
846 }
847 let v = os.get();
848 if v.abs() > 0.5 {
849 let decayed = v * OVERSHOOT_DECAY_PER_60HZ.powf(dt * 60.0);
850 if decayed.abs() < 0.5 {
851 os.set(0.0);
852 } else {
853 os.set(decayed);
854 }
855 request_frame();
856 true
857 } else {
858 false
859 }
860 }
861 pub fn tick(&self) -> bool {
862 let now = Instant::now();
863 let dt = (now - self.prev_tick.get()).as_secs_f32().min(0.1);
864 self.prev_tick.set(now);
865
866 if self.overscroll_enabled {
867 if Self::tick_os_axis(&self.os_x, true, dt) || Self::tick_os_axis(&self.os_y, true, dt)
868 {
869 return true;
870 }
871 }
872
873 let max_x = (self.c_w.get() - self.vp_w.get()).max(0.0);
874 let max_y = (self.c_h.get() - self.vp_h.get()).max(0.0);
875 let (bx, by) = (self.off_x.get(), self.off_y.get());
876
877 let mut px = self.physics_x.borrow_mut();
878 let mut py = self.physics_y.borrow_mut();
879 if !px.animating && !py.animating {
880 return false;
881 }
882 if px.animating {
883 if px.vel.abs() < px.stop_velocity {
884 px.vel = 0.0;
885 px.animating = false;
886 } else {
887 let nx = (bx + px.vel * dt).clamp(0.0, max_x);
888 if (nx - bx).abs() < 0.01 && (bx <= 0.0 || bx >= max_x) {
889 px.vel = 0.0;
890 px.animating = false;
891 } else {
892 let speed = px.vel.abs();
893 let t = (speed / 4000.0).min(1.0);
894 let effective_decay = 0.85 + t * 0.10;
895 px.vel *= effective_decay.powf(dt * 60.0);
896 if px.vel.abs() < px.stop_velocity {
897 px.vel = 0.0;
898 px.animating = false;
899 } else {
900 self.off_x.set(nx);
901 }
902 }
903 }
904 }
905 if py.animating {
906 if py.vel.abs() < py.stop_velocity {
907 py.vel = 0.0;
908 py.animating = false;
909 } else {
910 let ny = (by + py.vel * dt).clamp(0.0, max_y);
911 if (ny - by).abs() < 0.01 && (by <= 0.0 || by >= max_y) {
912 py.vel = 0.0;
913 py.animating = false;
914 } else {
915 let speed = py.vel.abs();
916 let t = (speed / 4000.0).min(1.0);
917 let effective_decay = 0.85 + t * 0.10;
918 py.vel *= effective_decay.powf(dt * 60.0);
919 if py.vel.abs() < py.stop_velocity {
920 py.vel = 0.0;
921 py.animating = false;
922 } else {
923 self.off_y.set(ny);
924 }
925 }
926 }
927 }
928 let running = px.animating || py.animating;
929 if running {
930 request_frame();
931 }
932 running
933 }
934
935 pub fn show_scrollbar(&self) -> bool {
936 self.show_scrollbar.get()
937 }
938
939 pub fn to_binding(&self) -> ScrollBinding {
940 let this = Rc::new(self.clone());
941 let pc = Rc::clone(&this.parent_connection);
942 let c_on_scroll = {
943 let state = Rc::clone(&pc);
944 let this = Rc::clone(&this);
945 Rc::new(move |d: Vec2| -> Vec2 {
946 let d = run_pre_scroll(&*state, d);
947 let result = this.scroll_immediate(d);
948 run_post_scroll(&*state, result)
949 })
950 } as Rc<dyn Fn(Vec2) -> Vec2>;
951 let c_set_vw = {
952 let this = Rc::clone(&this);
953 Rc::new(move |w: f32| this.set_viewport(w, this.vp_h.get()))
954 };
955 let c_set_vh = {
956 let this = Rc::clone(&this);
957 Rc::new(move |h: f32| this.set_viewport(this.vp_w.get(), h))
958 };
959 let c_set_cw = {
960 let this = Rc::clone(&this);
961 Rc::new(move |w: f32| this.set_content(w, this.c_h.get()))
962 };
963 let c_set_ch = {
964 let this = Rc::clone(&this);
965 Rc::new(move |h: f32| this.set_content(this.c_w.get(), h))
966 };
967 let c_get_xy = {
968 let this = Rc::clone(&this);
969 Rc::new(move || {
970 let (ox, oy) = this.get();
971 let (osx, osy) = this.overscroll_offset();
972 (ox + osx, oy + osy)
973 })
974 };
975 let c_set_xy = {
976 let this = Rc::clone(&this);
977 Rc::new(move |x: f32, y: f32| this.set_offset_xy(x, y))
978 };
979 let c_tick = {
980 let this = Rc::clone(&this);
981 Rc::new(move || {
982 this.tick();
983 })
984 };
985 let c_set_nested = {
986 let state = Rc::clone(&pc);
987 Rc::new(move |conn| {
988 *state.borrow_mut() = Some(conn);
989 })
990 };
991 ScrollBinding::Both(ScrollBothBinding {
992 on_scroll: Some(c_on_scroll),
993 set_viewport_width: Some(c_set_vw),
994 set_viewport_height: Some(c_set_vh),
995 set_content_width: Some(c_set_cw),
996 set_content_height: Some(c_set_ch),
997 get_offset_xy: Some(c_get_xy),
998 set_offset_xy: Some(c_set_xy),
999 show_scrollbar: this.show_scrollbar(),
1000 tick: Some(c_tick),
1001 set_nested_scroll_parent: Some(c_set_nested),
1002 })
1003 }
1004}
1005
1006pub fn run_pre_scroll(conn: &RefCell<Option<NestedScrollConnection>>, d: Vec2) -> Vec2 {
1007 if let Some(ref parent) = *conn.borrow() {
1008 let consumed = parent.dispatch_pre_scroll(d, NestedScrollSource::UserInput);
1009 d - consumed
1010 } else {
1011 d
1012 }
1013}
1014
1015pub fn run_post_scroll(
1016 conn: &RefCell<Option<NestedScrollConnection>>,
1017 leftover: Vec2,
1018) -> Vec2 {
1019 if let Some(ref parent) = *conn.borrow() {
1020 let added =
1021 parent.dispatch_post_scroll(Vec2::ZERO, leftover, NestedScrollSource::UserInput);
1022 leftover - added
1023 } else {
1024 leftover
1025 }
1026}