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