1use std::{fmt, mem, sync::Arc, time::Duration};
2
3use atomic::{Atomic, Ordering};
4use bitflags::bitflags;
5use parking_lot::Mutex;
6use zng_ext_input::touch::TouchPhase;
7use zng_var::{
8 ReadOnlyContextVar, VARS,
9 animation::{
10 AnimationHandle, ChaseAnimation, Transition,
11 easing::{self, EasingStep, EasingTime},
12 },
13};
14use zng_wgt::prelude::*;
15
16use super::{SMOOTH_SCROLLING_VAR, cmd};
17
18bitflags! {
19 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
24 #[serde(transparent)]
25 pub struct ScrollMode: u8 {
26 const NONE = 0;
28 const VERTICAL = 0b01;
30 const HORIZONTAL = 0b10;
32 const PAN = 0b11;
34 const ZOOM = 0b111;
37 }
38}
39impl_from_and_into_var! {
40 fn from(zoom: bool) -> ScrollMode {
45 if zoom {
46 ScrollMode::ZOOM
47 } else {
48 ScrollMode::NONE
49 }
50 }
51}
52
53context_var! {
54 pub(super) static SCROLL_VERTICAL_OFFSET_VAR: Factor = 0.fct();
58 pub(super) static SCROLL_HORIZONTAL_OFFSET_VAR: Factor = 0.fct();
62
63 pub(super) static OVERSCROLL_VERTICAL_OFFSET_VAR: Factor = 0.fct();
66
67 pub(super) static OVERSCROLL_HORIZONTAL_OFFSET_VAR: Factor = 0.fct();
70
71 pub(super) static SCROLL_VERTICAL_RATIO_VAR: Factor = 0.fct();
75
76 pub(super) static SCROLL_HORIZONTAL_RATIO_VAR: Factor = 0.fct();
80
81 pub(super) static SCROLL_VERTICAL_CONTENT_OVERFLOWS_VAR: bool = false;
83
84 pub(super) static SCROLL_HORIZONTAL_CONTENT_OVERFLOWS_VAR: bool = false;
86
87 pub(super) static SCROLL_VIEWPORT_SIZE_VAR: PxSize = PxSize::zero();
89
90 pub(super) static SCROLL_CONTENT_SIZE_VAR: PxSize = PxSize::zero();
94
95 pub(super) static SCROLL_SCALE_VAR: Factor = 1.fct();
97
98 pub(super) static SCROLL_MODE_VAR: ScrollMode = ScrollMode::empty();
100}
101
102context_local! {
103 static SCROLL_CONFIG: ScrollConfig = ScrollConfig::default();
104}
105
106#[derive(Debug, Clone, Copy, bytemuck::NoUninit)]
107#[repr(C)]
108struct RenderedOffsets {
109 h: Factor,
110 v: Factor,
111 z: Factor,
112}
113
114#[derive(Default, Debug)]
115enum ZoomState {
116 #[default]
117 None,
118 Chasing(ChaseAnimation<Factor>),
119 TouchStart {
120 start_factor: Factor,
121 start_center: euclid::Point2D<f32, Px>,
122 applied_offset: euclid::Vector2D<f32, Px>,
123 },
124}
125
126#[derive(Debug)]
127struct ScrollConfig {
128 id: Option<WidgetId>,
129 chase: [Mutex<Option<ChaseAnimation<Factor>>>; 2], zoom: Mutex<ZoomState>,
131
132 rendered: Atomic<RenderedOffsets>,
134
135 overscroll: [Mutex<AnimationHandle>; 2],
136 inertia: [Mutex<AnimationHandle>; 2],
137 auto: [Mutex<AnimationHandle>; 2],
138}
139impl Default for ScrollConfig {
140 fn default() -> Self {
141 Self {
142 id: Default::default(),
143 chase: Default::default(),
144 zoom: Default::default(),
145 rendered: Atomic::new(RenderedOffsets {
146 h: 0.fct(),
147 v: 0.fct(),
148 z: 0.fct(),
149 }),
150 overscroll: Default::default(),
151 inertia: Default::default(),
152 auto: Default::default(),
153 }
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
164pub enum ScrollFrom {
165 Var(Px),
171 VarTarget(Px),
178
179 Rendered(Px),
185}
186
187pub struct SCROLL;
189impl SCROLL {
190 pub fn try_id(&self) -> Option<WidgetId> {
192 SCROLL_CONFIG.get().id
193 }
194 pub fn id(&self) -> WidgetId {
200 self.try_id().expect("not inside scroll")
201 }
202
203 pub fn config_node(&self, child: impl UiNode) -> impl UiNode {
207 let child = match_node(child, move |_, op| {
208 if let UiNodeOp::Render { .. } | UiNodeOp::RenderUpdate { .. } = op {
209 let h = SCROLL_HORIZONTAL_OFFSET_VAR.get();
210 let v = SCROLL_VERTICAL_OFFSET_VAR.get();
211 let z = SCROLL_SCALE_VAR.get();
212 SCROLL_CONFIG.get().rendered.store(RenderedOffsets { h, v, z }, Ordering::Relaxed);
213 }
214 });
215 with_context_local_init(child, &SCROLL_CONFIG, || ScrollConfig {
216 id: WIDGET.try_id(),
217 ..Default::default()
218 })
219 }
220
221 pub fn mode(&self) -> ReadOnlyContextVar<ScrollMode> {
223 SCROLL_MODE_VAR.read_only()
224 }
225
226 pub fn vertical_offset(&self) -> ContextVar<Factor> {
234 SCROLL_VERTICAL_OFFSET_VAR
235 }
236
237 pub fn horizontal_offset(&self) -> ContextVar<Factor> {
245 SCROLL_HORIZONTAL_OFFSET_VAR
246 }
247
248 pub fn zoom_scale(&self) -> ContextVar<Factor> {
254 SCROLL_SCALE_VAR
255 }
256
257 pub fn rendered_offset(&self) -> Factor2d {
259 let cfg = SCROLL_CONFIG.get().rendered.load(Ordering::Relaxed);
260 Factor2d::new(cfg.h, cfg.v)
261 }
262
263 pub fn rendered_zoom_scale(&self) -> Factor {
265 SCROLL_CONFIG.get().rendered.load(Ordering::Relaxed).z
266 }
267
268 pub fn vertical_overscroll(&self) -> ReadOnlyContextVar<Factor> {
276 OVERSCROLL_VERTICAL_OFFSET_VAR.read_only()
277 }
278
279 pub fn horizontal_overscroll(&self) -> ReadOnlyContextVar<Factor> {
287 OVERSCROLL_HORIZONTAL_OFFSET_VAR.read_only()
288 }
289
290 pub fn vertical_ratio(&self) -> ReadOnlyContextVar<Factor> {
294 SCROLL_VERTICAL_RATIO_VAR.read_only()
295 }
296 pub fn horizontal_ratio(&self) -> ReadOnlyContextVar<Factor> {
300 SCROLL_HORIZONTAL_RATIO_VAR.read_only()
301 }
302
303 pub fn vertical_content_overflows(&self) -> ReadOnlyContextVar<bool> {
305 SCROLL_VERTICAL_CONTENT_OVERFLOWS_VAR.read_only()
306 }
307
308 pub fn horizontal_content_overflows(&self) -> ReadOnlyContextVar<bool> {
310 SCROLL_HORIZONTAL_CONTENT_OVERFLOWS_VAR.read_only()
311 }
312
313 pub fn viewport_size(&self) -> ReadOnlyContextVar<PxSize> {
315 SCROLL_VIEWPORT_SIZE_VAR.read_only()
316 }
317
318 pub fn content_size(&self) -> ReadOnlyContextVar<PxSize> {
320 SCROLL_CONTENT_SIZE_VAR.read_only()
321 }
322
323 pub fn scroll_vertical(&self, delta: ScrollFrom) {
327 self.scroll_vertical_clamp(delta, f32::MIN, f32::MAX);
328 }
329
330 pub fn scroll_horizontal(&self, delta: ScrollFrom) {
334 self.scroll_horizontal_clamp(delta, f32::MIN, f32::MAX)
335 }
336
337 pub fn scroll_vertical_clamp(&self, delta: ScrollFrom, min: f32, max: f32) {
341 self.scroll_clamp(true, SCROLL_VERTICAL_OFFSET_VAR, delta, min, max)
342 }
343
344 pub fn scroll_horizontal_clamp(&self, delta: ScrollFrom, min: f32, max: f32) {
348 self.scroll_clamp(false, SCROLL_HORIZONTAL_OFFSET_VAR, delta, min, max)
349 }
350 fn scroll_clamp(&self, vertical: bool, scroll_offset_var: ContextVar<Factor>, delta: ScrollFrom, min: f32, max: f32) {
351 let viewport = SCROLL_VIEWPORT_SIZE_VAR.get().to_array()[vertical as usize];
352 let content = SCROLL_CONTENT_SIZE_VAR.get().to_array()[vertical as usize];
353
354 let max_scroll = content - viewport;
355
356 if max_scroll <= 0 {
357 return;
358 }
359
360 match delta {
361 ScrollFrom::Var(a) => {
362 let amount = a.0 as f32 / max_scroll.0 as f32;
363 let f = scroll_offset_var.get();
364 SCROLL.chase(vertical, scroll_offset_var, |_| (f.0 + amount).clamp(min, max).fct());
365 }
366 ScrollFrom::VarTarget(a) => {
367 let amount = a.0 as f32 / max_scroll.0 as f32;
368 SCROLL.chase(vertical, scroll_offset_var, |f| (f.0 + amount).clamp(min, max).fct());
369 }
370 ScrollFrom::Rendered(a) => {
371 let amount = a.0 as f32 / max_scroll.0 as f32;
372 let f = SCROLL_CONFIG.get().rendered.load(Ordering::Relaxed).h;
373 SCROLL.chase(vertical, scroll_offset_var, |_| (f.0 + amount).clamp(min, max).fct());
374 }
375 }
376 }
377
378 pub fn auto_scroll(&self, velocity: DipVector) {
380 let viewport = SCROLL_VIEWPORT_SIZE_VAR.get();
381 let content = SCROLL_CONTENT_SIZE_VAR.get();
382 let max_scroll = content - viewport;
383
384 let velocity = velocity.to_px(WINDOW.info().scale_factor());
385
386 fn scroll(dimension: usize, velocity: Px, max_scroll: Px, offset_var: &ContextVar<Factor>) {
387 if velocity == 0 {
388 SCROLL_CONFIG.get().auto[dimension].lock().clone().stop();
389 } else {
390 let mut travel = max_scroll * offset_var.get();
391 let mut target = 0.0;
392 if velocity > Px(0) {
393 travel = max_scroll - travel;
394 target = 1.0;
395 }
396 let time = (travel.0 as f32 / velocity.0.abs() as f32).secs();
397
398 VARS.with_animation_controller(zng_var::animation::ForceAnimationController, || {
399 let handle = offset_var.ease(target, time, easing::linear);
400 mem::replace(&mut *SCROLL_CONFIG.get().auto[dimension].lock(), handle).stop();
401 });
402 }
403 }
404 scroll(0, velocity.x, max_scroll.width, &SCROLL_HORIZONTAL_OFFSET_VAR);
405 scroll(1, velocity.y, max_scroll.height, &SCROLL_VERTICAL_OFFSET_VAR);
406 }
407
408 pub fn scroll_vertical_touch(&self, delta: Px) {
413 self.scroll_touch(true, SCROLL_VERTICAL_OFFSET_VAR, OVERSCROLL_VERTICAL_OFFSET_VAR, delta)
414 }
415
416 pub fn scroll_horizontal_touch(&self, delta: Px) {
421 self.scroll_touch(false, SCROLL_HORIZONTAL_OFFSET_VAR, OVERSCROLL_HORIZONTAL_OFFSET_VAR, delta)
422 }
423
424 fn scroll_touch(&self, vertical: bool, scroll_offset_var: ContextVar<Factor>, overscroll_offset_var: ContextVar<Factor>, delta: Px) {
425 let viewport = SCROLL_VIEWPORT_SIZE_VAR.get().to_array()[vertical as usize];
426 let content = SCROLL_CONTENT_SIZE_VAR.get().to_array()[vertical as usize];
427
428 let max_scroll = content - viewport;
429 if max_scroll <= 0 {
430 return;
431 }
432
433 let delta = delta.0 as f32 / max_scroll.0 as f32;
434
435 let current = scroll_offset_var.get();
436 let mut next = current + delta.fct();
437 let mut overscroll = 0.fct();
438 if next > 1.fct() {
439 overscroll = next - 1.fct();
440 next = 1.fct();
441
442 let overscroll_px = overscroll * content.0.fct();
443 let overscroll_max = viewport.0.fct();
444 overscroll = overscroll_px.min(overscroll_max) / overscroll_max;
445 } else if next < 0.fct() {
446 overscroll = next;
447 next = 0.fct();
448
449 let overscroll_px = -overscroll * content.0.fct();
450 let overscroll_max = viewport.0.fct();
451 overscroll = -(overscroll_px.min(overscroll_max) / overscroll_max);
452 }
453
454 let _ = scroll_offset_var.set(next);
455 if overscroll != 0.fct() {
456 let new_handle = self.increment_overscroll(overscroll_offset_var, overscroll);
457
458 let config = SCROLL_CONFIG.get();
459 let mut handle = config.overscroll[vertical as usize].lock();
460 mem::replace(&mut *handle, new_handle).stop();
461 } else {
462 self.clear_horizontal_overscroll();
463 }
464 }
465
466 fn increment_overscroll(&self, overscroll: ContextVar<Factor>, delta: Factor) -> AnimationHandle {
467 enum State {
468 Increment,
469 ClearDelay,
470 Clear(Transition<Factor>),
471 }
472 let mut state = State::Increment;
473 overscroll.animate(move |a, o| match &mut state {
474 State::Increment => {
475 *o.to_mut() += delta;
477 *o.to_mut() = (*o).clamp((-1).fct(), 1.fct());
478
479 a.sleep(300.ms());
480 state = State::ClearDelay;
481 }
482 State::ClearDelay => {
483 a.restart();
484 let t = Transition::new(**o, 0.fct());
485 state = State::Clear(t);
486 }
487 State::Clear(t) => {
488 let step = easing::linear(a.elapsed_stop(300.ms()));
489 o.set(t.sample(step));
490 }
491 })
492 }
493
494 pub fn clear_vertical_overscroll(&self) {
496 self.clear_overscroll(true, OVERSCROLL_VERTICAL_OFFSET_VAR)
497 }
498
499 pub fn clear_horizontal_overscroll(&self) {
501 self.clear_overscroll(false, OVERSCROLL_HORIZONTAL_OFFSET_VAR)
502 }
503
504 fn clear_overscroll(&self, vertical: bool, overscroll_offset_var: ContextVar<Factor>) {
505 if overscroll_offset_var.get() != 0.fct() {
506 let new_handle = overscroll_offset_var.ease(0.fct(), 100.ms(), easing::linear);
507
508 let config = SCROLL_CONFIG.get();
509 let mut handle = config.overscroll[vertical as usize].lock();
510 mem::replace(&mut *handle, new_handle).stop();
511 }
512 }
513
514 pub fn scroll_vertical_touch_inertia(&self, delta: Px, duration: Duration) {
516 self.scroll_touch_inertia(true, SCROLL_VERTICAL_OFFSET_VAR, OVERSCROLL_VERTICAL_OFFSET_VAR, delta, duration)
517 }
518
519 pub fn scroll_horizontal_touch_inertia(&self, delta: Px, duration: Duration) {
521 self.scroll_touch_inertia(
522 false,
523 SCROLL_HORIZONTAL_OFFSET_VAR,
524 OVERSCROLL_HORIZONTAL_OFFSET_VAR,
525 delta,
526 duration,
527 )
528 }
529
530 fn scroll_touch_inertia(
531 &self,
532 vertical: bool,
533 scroll_offset_var: ContextVar<Factor>,
534 overscroll_offset_var: ContextVar<Factor>,
535 delta: Px,
536 duration: Duration,
537 ) {
538 let viewport = SCROLL_VIEWPORT_SIZE_VAR.get().to_array()[vertical as usize];
539 let content = SCROLL_CONTENT_SIZE_VAR.get().to_array()[vertical as usize];
540
541 let max_scroll = content - viewport;
542 if max_scroll <= 0 {
543 return;
544 }
545
546 let delta = delta.0 as f32 / max_scroll.0 as f32;
547
548 let current = scroll_offset_var.get();
549 let mut next = current + delta.fct();
550 let mut overscroll = 0.fct();
551 if next > 1.fct() {
552 overscroll = next - 1.fct();
553 next = 1.fct();
554
555 let overscroll_px = overscroll * content.0.fct();
556 let overscroll_max = viewport.0.fct();
557 overscroll = overscroll_px.min(overscroll_max) / overscroll_max;
558 } else if next < 0.fct() {
559 overscroll = next;
560 next = 0.fct();
561
562 let overscroll_px = -overscroll * content.0.fct();
563 let overscroll_max = viewport.0.fct();
564 overscroll = -(overscroll_px.min(overscroll_max) / overscroll_max);
565 }
566
567 let cfg = SCROLL_CONFIG.get();
568 let easing = |t| easing::ease_out(easing::quad, t);
569 *cfg.inertia[vertical as usize].lock() = if overscroll != 0.fct() {
570 let transition = Transition::new(current, next + overscroll);
571
572 let overscroll_var = overscroll_offset_var.actual_var();
573 let overscroll_tr = Transition::new(overscroll, 0.fct());
574 let mut is_inertia_anim = true;
575
576 scroll_offset_var.animate(move |animation, value| {
577 if is_inertia_anim {
578 let step = easing(animation.elapsed(duration));
580 let v = transition.sample(step);
581
582 if v < 0.fct() || v > 1.fct() {
583 value.set(v.clamp_range());
585 animation.restart();
586 is_inertia_anim = false;
587 let _ = overscroll_var.set(overscroll_tr.from);
588 } else {
589 value.set(v);
590 }
591 } else {
592 let step = easing::linear(animation.elapsed_stop(300.ms()));
594 let v = overscroll_tr.sample(step);
595 let _ = overscroll_var.set(v);
596 }
597 })
598 } else {
599 scroll_offset_var.ease(next, duration, easing)
600 };
601 }
602
603 pub fn chase_vertical(&self, modify_offset: impl FnOnce(Factor) -> Factor) {
606 #[cfg(feature = "dyn_closure")]
607 let modify_offset: Box<dyn FnOnce(Factor) -> Factor> = Box::new(modify_offset);
608 self.chase(true, SCROLL_VERTICAL_OFFSET_VAR, modify_offset);
609 }
610
611 pub fn chase_horizontal(&self, modify_offset: impl FnOnce(Factor) -> Factor) {
614 #[cfg(feature = "dyn_closure")]
615 let modify_offset: Box<dyn FnOnce(Factor) -> Factor> = Box::new(modify_offset);
616 self.chase(false, SCROLL_HORIZONTAL_OFFSET_VAR, modify_offset);
617 }
618
619 fn chase(&self, vertical: bool, scroll_offset_var: ContextVar<Factor>, modify_offset: impl FnOnce(Factor) -> Factor) {
620 let smooth = SMOOTH_SCROLLING_VAR.get();
621 let config = SCROLL_CONFIG.get();
622 let mut chase = config.chase[vertical as usize].lock();
623 match &mut *chase {
624 Some(t) => {
625 if smooth.is_disabled() {
626 let t = modify_offset(*t.target()).clamp_range();
627 let _ = scroll_offset_var.set(t);
628 *chase = None;
629 } else {
630 let easing = smooth.easing.clone();
631 t.modify(|f| *f = modify_offset(*f).clamp_range(), smooth.duration, move |t| easing(t));
632 }
633 }
634 None => {
635 let t = modify_offset(scroll_offset_var.get()).clamp_range();
636 if smooth.is_disabled() {
637 let _ = scroll_offset_var.set(t);
638 } else {
639 let easing = smooth.easing.clone();
640 let anim = scroll_offset_var.chase(t, smooth.duration, move |t| easing(t));
641 *chase = Some(anim);
642 }
643 }
644 }
645 }
646
647 pub fn chase_zoom(&self, modify_scale: impl FnOnce(Factor) -> Factor) {
650 #[cfg(feature = "dyn_closure")]
651 let modify_scale: Box<dyn FnOnce(Factor) -> Factor> = Box::new(modify_scale);
652 self.chase_zoom_impl(modify_scale);
653 }
654 fn chase_zoom_impl(&self, modify_scale: impl FnOnce(Factor) -> Factor) {
655 if !SCROLL_MODE_VAR.get().contains(ScrollMode::ZOOM) {
656 return;
657 }
658
659 let smooth = SMOOTH_SCROLLING_VAR.get();
660 let config = SCROLL_CONFIG.get();
661 let mut zoom = config.zoom.lock();
662
663 let min = super::MIN_ZOOM_VAR.get();
664 let max = super::MAX_ZOOM_VAR.get();
665
666 match &mut *zoom {
667 ZoomState::Chasing(t) => {
668 if smooth.is_disabled() {
669 let next = modify_scale(*t.target()).clamp(min, max);
670 let _ = SCROLL_SCALE_VAR.set(next);
671 *zoom = ZoomState::None;
672 } else {
673 let easing = smooth.easing.clone();
674 t.modify(|f| *f = modify_scale(*f).clamp(min, max), smooth.duration, move |t| easing(t));
675 }
676 }
677 _ => {
678 let t = modify_scale(SCROLL_SCALE_VAR.get()).clamp(min, max);
679 if smooth.is_disabled() {
680 let _ = SCROLL_SCALE_VAR.set(t);
681 } else {
682 let easing = smooth.easing.clone();
683 let anim = SCROLL_SCALE_VAR.chase(t, smooth.duration, move |t| easing(t));
684 *zoom = ZoomState::Chasing(anim);
685 }
686 }
687 }
688 }
689
690 pub fn zoom(&self, modify_scale: impl FnOnce(Factor) -> Factor, origin: PxPoint) {
693 #[cfg(feature = "dyn_closure")]
694 let modify_scale: Box<dyn FnOnce(Factor) -> Factor> = Box::new(modify_scale);
695 self.zoom_impl(modify_scale, origin);
696 }
697 fn zoom_impl(&self, modify_scale: impl FnOnce(Factor) -> Factor, center_in_viewport: PxPoint) {
698 if !SCROLL_MODE_VAR.get().contains(ScrollMode::ZOOM) {
699 return;
700 }
701
702 let content = WIDGET.info().scroll_info().unwrap().content();
703 let mut center_in_content = -content.origin + center_in_viewport.to_vector();
704 let mut content_size = content.size;
705
706 let rendered_scale = SCROLL.rendered_zoom_scale();
707
708 SCROLL.chase_zoom(|f| {
709 let s = modify_scale(f);
710 let f = s / rendered_scale;
711 center_in_content *= f;
712 content_size *= f;
713 s
714 });
715
716 let viewport_size = SCROLL_VIEWPORT_SIZE_VAR.get();
717
718 let max_scroll = content_size - viewport_size;
720 let offset = center_in_content - center_in_viewport;
721
722 if offset.y != Px(0) && max_scroll.height > Px(0) {
723 let offset_y = offset.y.0 as f32 / max_scroll.height.0 as f32;
724 SCROLL.chase_vertical(|_| offset_y.fct());
725 }
726 if offset.x != Px(0) && max_scroll.width > Px(0) {
727 let offset_x = offset.x.0 as f32 / max_scroll.width.0 as f32;
728 SCROLL.chase_horizontal(|_| offset_x.fct());
729 }
730 }
731
732 pub fn zoom_touch(&self, phase: TouchPhase, scale: Factor, center_in_viewport: euclid::Point2D<f32, Px>) {
734 if !SCROLL_MODE_VAR.get().contains(ScrollMode::ZOOM) {
735 return;
736 }
737
738 let cfg = SCROLL_CONFIG.get();
739
740 let rendered_scale = SCROLL.rendered_zoom_scale();
741
742 let start_scale;
743 let start_center;
744
745 let mut cfg = cfg.zoom.lock();
746
747 if let TouchPhase::Start = phase {
748 start_scale = rendered_scale;
749 start_center = center_in_viewport;
750
751 *cfg = ZoomState::TouchStart {
752 start_factor: start_scale,
753 start_center: center_in_viewport,
754 applied_offset: euclid::vec2(0.0, 0.0),
755 };
756 } else if let ZoomState::TouchStart {
757 start_factor: scale,
758 start_center: center_in_viewport,
759 ..
760 } = &*cfg
761 {
762 start_scale = *scale;
763 start_center = *center_in_viewport;
764 } else {
765 return;
767 }
768
769 let applied_offset = if let ZoomState::TouchStart { applied_offset, .. } = &mut *cfg {
771 applied_offset
772 } else {
773 unreachable!()
774 };
775
776 let scale = start_scale + (scale - 1.0.fct());
777
778 let min = super::MIN_ZOOM_VAR.get();
779 let max = super::MAX_ZOOM_VAR.get();
780 let scale = scale.clamp(min, max);
781
782 let translate_offset = start_center - center_in_viewport;
783 let translate_delta = translate_offset - *applied_offset;
784 *applied_offset = translate_offset;
785
786 let content = WIDGET.info().scroll_info().unwrap().content();
787 let mut center_in_content = -content.origin.cast::<f32>() + center_in_viewport.to_vector();
788 let mut content_size = content.size.cast::<f32>();
789
790 let scale_transform = scale / rendered_scale;
791
792 center_in_content *= scale_transform;
793 content_size *= scale_transform;
794
795 let viewport_size = SCROLL_VIEWPORT_SIZE_VAR.get().cast::<f32>();
796
797 let max_scroll = content_size - viewport_size;
799 let zoom_offset = center_in_content - center_in_viewport;
800
801 let offset = zoom_offset + translate_delta;
802
803 let _ = SCROLL_SCALE_VAR.set(scale);
804
805 if offset.y != 0.0 && max_scroll.height > 0.0 {
806 let offset_y = offset.y / max_scroll.height;
807 let _ = SCROLL_VERTICAL_OFFSET_VAR.set(offset_y.clamp(0.0, 1.0));
808 }
809 if offset.x != 0.0 && max_scroll.width > 0.0 {
810 let offset_x = offset.x / max_scroll.width;
811 let _ = SCROLL_HORIZONTAL_OFFSET_VAR.set(offset_x.clamp(0.0, 1.0));
812 }
813 }
814
815 fn can_scroll(&self, predicate: impl Fn(PxSize, PxSize) -> bool + Send + Sync + 'static) -> impl Var<bool> {
816 merge_var!(SCROLL_VIEWPORT_SIZE_VAR, SCROLL_CONTENT_SIZE_VAR, move |&vp, &ct| predicate(vp, ct))
817 }
818
819 pub fn can_scroll_vertical(&self) -> impl Var<bool> {
821 self.can_scroll(|vp, ct| ct.height > vp.height)
822 }
823
824 pub fn can_scroll_horizontal(&self) -> impl Var<bool> {
826 self.can_scroll(|vp, ct| ct.width > vp.width)
827 }
828
829 fn can_scroll_v(&self, predicate: impl Fn(PxSize, PxSize, Factor) -> bool + Send + Sync + 'static) -> impl Var<bool> {
830 merge_var!(
831 SCROLL_VIEWPORT_SIZE_VAR,
832 SCROLL_CONTENT_SIZE_VAR,
833 SCROLL_VERTICAL_OFFSET_VAR,
834 move |&vp, &ct, &vo| predicate(vp, ct, vo)
835 )
836 }
837
838 pub fn can_scroll_down(&self) -> impl Var<bool> {
841 self.can_scroll_v(|vp, ct, vo| ct.height > vp.height && 1.fct() > vo)
842 }
843
844 pub fn can_scroll_up(&self) -> impl Var<bool> {
847 self.can_scroll_v(|vp, ct, vo| ct.height > vp.height && 0.fct() < vo)
848 }
849
850 fn can_scroll_h(&self, predicate: impl Fn(PxSize, PxSize, Factor) -> bool + Send + Sync + 'static) -> impl Var<bool> {
851 merge_var!(
852 SCROLL_VIEWPORT_SIZE_VAR,
853 SCROLL_CONTENT_SIZE_VAR,
854 SCROLL_HORIZONTAL_OFFSET_VAR,
855 move |&vp, &ct, &ho| predicate(vp, ct, ho)
856 )
857 }
858
859 pub fn can_scroll_left(&self) -> impl Var<bool> {
862 self.can_scroll_h(|vp, ct, ho| ct.width > vp.width && 0.fct() < ho)
863 }
864
865 pub fn can_scroll_right(&self) -> impl Var<bool> {
868 self.can_scroll_h(|vp, ct, ho| ct.width > vp.width && 1.fct() > ho)
869 }
870
871 pub fn scroll_to(&self, mode: impl Into<super::cmd::ScrollToMode>) {
875 cmd::scroll_to(WIDGET.info(), mode.into())
876 }
877
878 pub fn scroll_to_zoom(&self, mode: impl Into<super::cmd::ScrollToMode>, zoom: impl Into<Factor>) {
882 cmd::scroll_to_zoom(WIDGET.info(), mode.into(), zoom.into())
883 }
884
885 pub fn can_zoom_in(&self) -> bool {
887 SCROLL_MODE_VAR.get().contains(ScrollMode::ZOOM) && SCROLL_SCALE_VAR.get() < super::MAX_ZOOM_VAR.get()
888 }
889
890 pub fn can_zoom_out(&self) -> bool {
892 SCROLL_MODE_VAR.get().contains(ScrollMode::ZOOM) && SCROLL_SCALE_VAR.get() > super::MIN_ZOOM_VAR.get()
893 }
894}
895
896impl SCROLL {
897 pub fn context_values_set(&self, set: &mut ContextValueSet) {
901 set.insert(&SCROLL_CONFIG);
902 }
903}
904
905pub trait WidgetInfoExt {
909 fn is_scroll(&self) -> bool;
911
912 fn scroll_info(&self) -> Option<ScrollInfo>;
914
915 fn viewport(&self) -> Option<PxRect>;
919}
920impl WidgetInfoExt for WidgetInfo {
921 fn is_scroll(&self) -> bool {
922 self.meta().get(*SCROLL_INFO_ID).is_some()
923 }
924
925 fn scroll_info(&self) -> Option<ScrollInfo> {
926 self.meta().get(*SCROLL_INFO_ID).cloned()
927 }
928
929 fn viewport(&self) -> Option<PxRect> {
930 self.meta().get(*SCROLL_INFO_ID).map(|r| r.viewport())
931 }
932}
933
934#[derive(Debug)]
935struct ScrollData {
936 viewport_transform: PxTransform,
937 viewport_size: PxSize,
938 joiner_size: PxSize,
939 content: PxRect,
940 zoom_scale: Factor,
941}
942impl Default for ScrollData {
943 fn default() -> Self {
944 Self {
945 viewport_transform: Default::default(),
946 viewport_size: Default::default(),
947 joiner_size: Default::default(),
948 content: Default::default(),
949 zoom_scale: 1.fct(),
950 }
951 }
952}
953
954#[derive(Clone, Default, Debug)]
956pub struct ScrollInfo(Arc<Mutex<ScrollData>>);
957impl ScrollInfo {
958 pub fn viewport(&self) -> PxRect {
960 self.viewport_transform()
961 .outer_transformed(PxBox::from_size(self.viewport_size()))
962 .unwrap_or_default()
963 .to_rect()
964 }
965
966 pub fn viewport_size(&self) -> PxSize {
968 self.0.lock().viewport_size
969 }
970
971 pub fn viewport_transform(&self) -> PxTransform {
973 self.0.lock().viewport_transform
974 }
975
976 pub fn joiner_size(&self) -> PxSize {
981 self.0.lock().joiner_size
982 }
983
984 pub fn content(&self) -> PxRect {
988 self.0.lock().content
989 }
990
991 pub fn zoom_scale(&self) -> Factor {
993 self.0.lock().zoom_scale
994 }
995
996 pub(super) fn set_viewport_size(&self, size: PxSize) {
997 self.0.lock().viewport_size = size;
998 }
999
1000 pub(super) fn set_viewport_transform(&self, transform: PxTransform) {
1001 self.0.lock().viewport_transform = transform;
1002 }
1003
1004 pub(super) fn set_joiner_size(&self, size: PxSize) {
1005 self.0.lock().joiner_size = size;
1006 }
1007
1008 pub(super) fn set_content(&self, content: PxRect, scale: Factor) {
1009 let mut m = self.0.lock();
1010 m.content = content;
1011 m.zoom_scale = scale;
1012 }
1013}
1014
1015static_id! {
1016 pub(super) static ref SCROLL_INFO_ID: StateId<ScrollInfo>;
1017}
1018
1019#[derive(Clone)]
1025pub struct SmoothScrolling {
1026 pub duration: Duration,
1030 pub easing: Arc<dyn Fn(EasingTime) -> EasingStep + Send + Sync>,
1034}
1035impl fmt::Debug for SmoothScrolling {
1036 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1037 f.debug_struct("SmoothScrolling")
1038 .field("duration", &self.duration)
1039 .finish_non_exhaustive()
1040 }
1041}
1042impl PartialEq for SmoothScrolling {
1043 fn eq(&self, other: &Self) -> bool {
1044 self.duration == other.duration && Arc::ptr_eq(&self.easing, &other.easing)
1045 }
1046}
1047impl Default for SmoothScrolling {
1048 fn default() -> Self {
1049 Self::new(150.ms(), easing::linear)
1050 }
1051}
1052impl SmoothScrolling {
1053 pub fn new(duration: Duration, easing: impl Fn(EasingTime) -> EasingStep + Send + Sync + 'static) -> Self {
1055 Self {
1056 duration,
1057 easing: Arc::new(easing),
1058 }
1059 }
1060
1061 pub fn disabled() -> Self {
1063 Self::new(Duration::ZERO, easing::none)
1064 }
1065
1066 pub fn is_disabled(&self) -> bool {
1070 self.duration == Duration::ZERO
1071 }
1072}
1073impl_from_and_into_var! {
1074 fn from(duration: Duration) -> SmoothScrolling {
1076 SmoothScrolling {
1077 duration,
1078 ..Default::default()
1079 }
1080 }
1081
1082 fn from(enabled: bool) -> SmoothScrolling {
1086 if enabled {
1087 SmoothScrolling::default()
1088 } else {
1089 SmoothScrolling::disabled()
1090 }
1091 }
1092
1093 fn from<F: Fn(EasingTime) -> EasingStep + Send + Sync + 'static>((duration, easing): (Duration, F)) -> SmoothScrolling {
1094 SmoothScrolling::new(duration, easing)
1095 }
1096
1097 fn from((duration, easing): (Duration, easing::EasingFn)) -> SmoothScrolling {
1098 SmoothScrolling::new(duration, easing.ease_fn())
1099 }
1100}
1101
1102#[derive(Debug, Default, Clone, PartialEq)]
1111pub struct AutoScrollArgs {}