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