@rafaelaugusto/jolt 0.1.0-rc.2 → 0.1.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +4 -4
  2. package/dist/animate.d.ts.map +1 -1
  3. package/dist/animate.js +20 -5
  4. package/dist/animate.js.map +1 -1
  5. package/dist/bottom-sheet/geometry.d.ts +7 -2
  6. package/dist/bottom-sheet/geometry.d.ts.map +1 -1
  7. package/dist/bottom-sheet/geometry.js +7 -3
  8. package/dist/bottom-sheet/geometry.js.map +1 -1
  9. package/dist/bottom-sheet/index.d.ts +10 -0
  10. package/dist/bottom-sheet/index.d.ts.map +1 -1
  11. package/dist/bottom-sheet/index.js +72 -22
  12. package/dist/bottom-sheet/index.js.map +1 -1
  13. package/dist/bottom-sheet/native.d.ts +5 -0
  14. package/dist/bottom-sheet/native.d.ts.map +1 -1
  15. package/dist/bottom-sheet/native.js +6 -0
  16. package/dist/bottom-sheet/native.js.map +1 -1
  17. package/dist/driver/android.d.ts.map +1 -1
  18. package/dist/driver/android.js +15 -7
  19. package/dist/driver/android.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/interactive-navigation/index.d.ts +2 -0
  25. package/dist/interactive-navigation/index.d.ts.map +1 -1
  26. package/dist/interactive-navigation/index.js +1 -0
  27. package/dist/interactive-navigation/index.js.map +1 -1
  28. package/dist/interactive-navigation/modal.d.ts +34 -0
  29. package/dist/interactive-navigation/modal.d.ts.map +1 -0
  30. package/dist/interactive-navigation/modal.js +253 -0
  31. package/dist/interactive-navigation/modal.js.map +1 -0
  32. package/dist/live-spring-driver.d.ts +5 -0
  33. package/dist/live-spring-driver.d.ts.map +1 -1
  34. package/dist/live-spring-driver.js +13 -0
  35. package/dist/live-spring-driver.js.map +1 -1
  36. package/dist/mixer.d.ts +7 -1
  37. package/dist/mixer.d.ts.map +1 -1
  38. package/dist/mixer.js +57 -22
  39. package/dist/mixer.js.map +1 -1
  40. package/dist/motion-value.d.ts +12 -1
  41. package/dist/motion-value.d.ts.map +1 -1
  42. package/dist/motion-value.js +115 -7
  43. package/dist/motion-value.js.map +1 -1
  44. package/dist/scroll-header/geometry.d.ts +1 -1
  45. package/dist/scroll-header/geometry.d.ts.map +1 -1
  46. package/dist/scroll-header/geometry.js +7 -2
  47. package/dist/scroll-header/geometry.js.map +1 -1
  48. package/dist/scroll-header/index.d.ts.map +1 -1
  49. package/dist/scroll-header/index.js +13 -0
  50. package/dist/scroll-header/index.js.map +1 -1
  51. package/dist/shapes/index.d.ts +1 -1
  52. package/dist/shapes/index.d.ts.map +1 -1
  53. package/dist/shapes/index.js +23 -9
  54. package/dist/shapes/index.js.map +1 -1
  55. package/dist/types.d.ts +5 -1
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/value-track.d.ts +9 -0
  58. package/dist/value-track.d.ts.map +1 -1
  59. package/dist/value-track.js +28 -16
  60. package/dist/value-track.js.map +1 -1
  61. package/package.json +1 -1
  62. package/platforms/android/java/org/rafaelaugusto/jolt/BlurEffect.java +14 -2
  63. package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +30 -8
  64. package/platforms/android/java/org/rafaelaugusto/jolt/KeyframeFactory.java +26 -0
  65. package/platforms/android/java/org/rafaelaugusto/jolt/LiveSpring.java +36 -9
  66. package/platforms/android/java/org/rafaelaugusto/jolt/ScrollHeader.java +3 -2
  67. package/platforms/android/java/org/rafaelaugusto/jolt/ShapeLayer.java +17 -7
  68. package/platforms/android/proguard-rules.pro +1 -0
  69. package/platforms/ios/src/JoltInteractiveModal.h +16 -0
  70. package/platforms/ios/src/JoltInteractiveModal.m +178 -0
  71. package/platforms/ios/src/JoltLiveSpring.h +1 -0
  72. package/platforms/ios/src/JoltLiveSpring.m +183 -67
  73. package/platforms/ios/src/module.modulemap +1 -0
  74. package/src/android-plugin.d.ts +10 -0
  75. package/src/animate.ts +23 -5
  76. package/src/bottom-sheet/geometry.ts +9 -2
  77. package/src/bottom-sheet/index.ts +94 -20
  78. package/src/bottom-sheet/native.ts +6 -0
  79. package/src/driver/android.ts +31 -14
  80. package/src/index.ts +1 -1
  81. package/src/interactive-navigation/index.ts +7 -0
  82. package/src/interactive-navigation/modal.ts +306 -0
  83. package/src/ios-plugin.d.ts +23 -0
  84. package/src/live-spring-driver.ts +15 -0
  85. package/src/mixer.ts +62 -22
  86. package/src/motion-value.ts +121 -9
  87. package/src/scroll-header/geometry.ts +7 -2
  88. package/src/scroll-header/index.ts +12 -0
  89. package/src/shapes/index.ts +20 -9
  90. package/src/types.ts +5 -2
  91. package/src/value-track.ts +25 -14
@@ -2,25 +2,13 @@ import { instantTrack } from './track.js';
2
2
  /** Compile scalar addition before any binding's mapping. The snapshot is independent of later edits. */
3
3
  export function valueTrack(parts, rest) {
4
4
  const value = (time) => parts.reduce((sum, part) => sum + part.base + part.span * part.track.valueAt(part.elapsed + time), rest);
5
- let finite = 0;
6
- const periods = [];
7
- for (const { track, elapsed } of parts) {
8
- if (Number.isFinite(track.end))
9
- finite = Math.max(finite, track.end - elapsed);
10
- else if (elapsed < track.delay)
11
- finite = Math.max(finite, track.delay - elapsed);
12
- else
13
- periods.push(Math.max(1, track.duration * (track.reverse ? 2 : 1)));
14
- }
15
- if (finite <= 0 && !periods.length)
5
+ const span = compositionSpan(parts);
6
+ if (!span)
16
7
  return { track: instantTrack('x', value(0)), windowed: false };
17
- const loop = finite <= 0 && periods.length ? commonPeriod(periods) : undefined;
18
- // Bound finite compositions too: a minutes-long base must not undersample a short pulse.
19
- const duration = finite > 0 ? Math.min(finite, 4000) : (loop ?? 4000);
8
+ const { duration, endless, windowed } = span;
20
9
  const steps = Math.max(1, Math.min(6000, Math.ceil((duration * 60) / 1000)));
21
10
  const times = Array.from({ length: steps + 1 }, (_, i) => i / steps);
22
11
  const values = times.map((time) => value(time * duration));
23
- const endless = loop !== undefined;
24
12
  const at = (time) => value(endless
25
13
  ? ((time % duration) + duration) % duration
26
14
  : Math.min(duration, Math.max(0, time)));
@@ -38,8 +26,32 @@ export function valueTrack(parts, rest) {
38
26
  valueAt: at,
39
27
  velocityAt: (time) => (at(time + 0.5) - at(time - 0.5)) * 1000,
40
28
  };
29
+ return { track, windowed };
30
+ }
31
+ /**
32
+ * How long one sampled composition can play before it needs rebuilding. A rolling
33
+ * window repeats every four seconds; native composition avoids it entirely.
34
+ */
35
+ export function compositionSpan(parts) {
36
+ let finite = 0;
37
+ const periods = [];
38
+ for (const { track, elapsed } of parts) {
39
+ if (Number.isFinite(track.end))
40
+ finite = Math.max(finite, track.end - elapsed);
41
+ else if (elapsed < track.delay)
42
+ finite = Math.max(finite, track.delay - elapsed);
43
+ else
44
+ periods.push(Math.max(1, track.duration * (track.reverse ? 2 : 1)));
45
+ }
46
+ if (finite <= 0 && !periods.length)
47
+ return undefined;
48
+ const loop = finite <= 0 && periods.length ? commonPeriod(periods) : undefined;
49
+ // Bound finite compositions too: a minutes-long base must not undersample a short pulse.
50
+ const duration = finite > 0 ? Math.min(finite, 4000) : (loop ?? 4000);
51
+ const endless = loop !== undefined;
41
52
  return {
42
- track,
53
+ duration,
54
+ endless,
43
55
  windowed: finite > duration ||
44
56
  (!endless && parts.some(({ track }) => !Number.isFinite(track.end))),
45
57
  };
@@ -1 +1 @@
1
- {"version":3,"file":"value-track.js","sourceRoot":"","sources":["../src/value-track.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"value-track.js","sourceRoot":"","sources":["../src/value-track.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rafaelaugusto/jolt",
3
- "version": "0.1.0-rc.2",
3
+ "version": "0.1.0-rc.4",
4
4
  "description": "Native animations for NativeScript: presets, springs, keyframes and stagger in one call.",
5
5
  "keywords": [
6
6
  "nativescript",
@@ -50,12 +50,19 @@ public final class BlurEffect {
50
50
  }catch(JSONException e){throw new IllegalArgumentException("Invalid blur program",e);}
51
51
  if(!supported){apply(values[values.length-1]);return;}
52
52
  advance(0);
53
+ active++;clock(0);
54
+ }
55
+ private void clock(long at){
56
+ ValueAnimator old=animator;
57
+ if(old!=null){old.removeAllUpdateListeners();old.removeAllListeners();old.cancel();}
53
58
  final ValueAnimator a=ValueAnimator.ofFloat(0f,1f);
54
59
  a.setDuration(repeats<0?60000:Math.max(1,(long)Math.ceil(delay+duration*(repeats+1))));
55
60
  a.setRepeatCount(repeats<0?ValueAnimator.INFINITE:0);a.setInterpolator(new LinearInterpolator());
56
61
  a.addUpdateListener(v->advance(v.getCurrentPlayTime()));
57
62
  a.addListener(new AnimatorListenerAdapter(){@Override public void onAnimationEnd(Animator ended){if(animator==a){animator=null;active--;}}});
58
- animator=a;active++;a.start();
63
+ animator=a;
64
+ if(at>0)a.setCurrentPlayTime(at);
65
+ a.start();
59
66
  }
60
67
  private void advance(double elapsed){
61
68
  double t=Math.max(0,elapsed-delay),fraction;
@@ -67,9 +74,14 @@ public final class BlurEffect {
67
74
  double span=times[high]-times[low],mix=span>0?Math.max(0,Math.min(1,(fraction-times[low])/span)):1;
68
75
  apply(values[low]+(values[high]-values[low])*mix);
69
76
  }
77
+ /** A resume rebuilds the clock: Android moves a seeked start time by the paused duration on the frame after resume(). */
70
78
  public void control(double time,boolean paused){
71
79
  if(animator==null)return;
72
- animator.setCurrentPlayTime(Math.max(0,(long)time));if(paused)animator.pause();else animator.resume();advance(time);
80
+ long at=Math.max(0,(long)time);
81
+ if(paused){animator.setCurrentPlayTime(at);animator.pause();}
82
+ else if(animator.isPaused())clock(at);
83
+ else animator.setCurrentPlayTime(at);
84
+ advance(time);
73
85
  }
74
86
  public void resize(){} // RenderEffect automatically follows the view's render node bounds.
75
87
  public String snapshot(){
@@ -32,6 +32,9 @@ public final class GradientLayer extends android.graphics.drawable.Drawable {
32
32
  // angle, x, y, radius
33
33
  private final float[] geometry = new float[4];
34
34
  private float corner;
35
+ private long duration, delay;
36
+ private int repeat;
37
+ private boolean reverse;
35
38
  private ValueAnimator animator;
36
39
  private Listener listener;
37
40
 
@@ -67,14 +70,39 @@ public final class GradientLayer extends android.graphics.drawable.Drawable {
67
70
  frameColors = colors;
68
71
  frameStops = stops;
69
72
  frameGeometry = geometry;
73
+ this.duration = duration;
74
+ this.delay = delay;
75
+ this.repeat = repeat;
76
+ this.reverse = reverse;
70
77
  this.listener = listener;
71
78
  int count = colors.length / times.length;
72
79
  this.colors = new int[count];
73
80
  this.stops = new float[count];
74
81
  apply(0);
82
+ clock(0);
83
+ }
84
+
85
+ /** Rejoin the source position. Binding windows include delays in their frames. */
86
+ public void control(long elapsed, boolean paused) {
87
+ if (animator == null) return;
88
+ if (paused) {
89
+ animator.setCurrentPlayTime(Math.max(0, elapsed - delay));
90
+ animator.pause();
91
+ } else if (animator.isPaused()) clock(Math.max(0, elapsed));
92
+ else animator.setCurrentPlayTime(Math.max(0, elapsed - delay));
93
+ }
94
+
95
+ /** A resume rebuilds the animator: Android moves a seeked start time by the paused duration on the frame after resume(). */
96
+ private void clock(long elapsed) {
97
+ ValueAnimator old = animator;
98
+ if (old != null) {
99
+ old.removeAllUpdateListeners();
100
+ old.removeAllListeners();
101
+ old.cancel();
102
+ }
75
103
  final ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
76
104
  animation.setDuration(Math.max(duration, 1));
77
- animation.setStartDelay(delay);
105
+ animation.setStartDelay(Math.max(0, delay - elapsed));
78
106
  animation.setInterpolator(new LinearInterpolator());
79
107
  if (repeat != 0) {
80
108
  animation.setRepeatCount(repeat < 0 ? ValueAnimator.INFINITE : repeat);
@@ -105,16 +133,10 @@ public final class GradientLayer extends android.graphics.drawable.Drawable {
105
133
  }
106
134
  });
107
135
  animator = animation;
136
+ if (elapsed > delay) animation.setCurrentPlayTime(elapsed - delay);
108
137
  animation.start();
109
138
  }
110
139
 
111
- /** Rejoin the source position. Binding windows include delays in their frames. */
112
- public void control(long elapsed, boolean paused) {
113
- if (animator == null) return;
114
- animator.setCurrentPlayTime(Math.max(0, elapsed - animator.getStartDelay()));
115
- if (paused) animator.pause(); else animator.resume();
116
- }
117
-
118
140
  public boolean isPlaying() { return animator != null; }
119
141
  public float getCorner() { return corner; }
120
142
  public float[] getGeometry() { return geometry.clone(); }
@@ -0,0 +1,26 @@
1
+ package org.rafaelaugusto.jolt;
2
+
3
+ import android.animation.Keyframe;
4
+ import android.animation.ObjectAnimator;
5
+ import android.animation.PropertyValuesHolder;
6
+ import android.util.Property;
7
+ import android.view.View;
8
+
9
+ /** Builds a numeric track without creating a JavaScript proxy for every keyframe. */
10
+ public final class KeyframeFactory {
11
+ private KeyframeFactory() {}
12
+
13
+ public static ObjectAnimator create(View view, Property<View, Float> property,
14
+ double[] times, double[] values, double unit) {
15
+ if (times.length != values.length || values.length < 2) {
16
+ throw new IllegalArgumentException("A track needs matching times and at least two values");
17
+ }
18
+ Keyframe[] frames = new Keyframe[values.length];
19
+ for (int i = 0; i < values.length; i++) {
20
+ // Scale in double precision before the same float conversion as Keyframe.ofFloat.
21
+ frames[i] = Keyframe.ofFloat((float) times[i], (float) (values[i] * unit));
22
+ }
23
+ return ObjectAnimator.ofPropertyValuesHolder(view,
24
+ PropertyValuesHolder.ofKeyframe(property, frames));
25
+ }
26
+ }
@@ -24,7 +24,11 @@ public final class LiveSpring {
24
24
  while (remaining > 1e-9) {
25
25
  double dt = Math.min(remaining, 1.0 / 240);
26
26
  remaining -= dt;
27
- for (int i = 0; i < nodes.size(); i++) nodes.get(i).step(now - remaining * 1000, dt);
27
+ // An instant node only needs the frame's final time; springs integrate every substep.
28
+ for (int i = 0; i < nodes.size(); i++) {
29
+ LiveSpring node = nodes.get(i);
30
+ if (!node.reduced || remaining <= 1e-9) node.step(now - remaining * 1000, dt);
31
+ }
28
32
  }
29
33
  boolean active = false;
30
34
  for (int i = 0; i < nodes.size(); i++) {
@@ -46,7 +50,9 @@ public final class LiveSpring {
46
50
  private final ArrayList<Binding> bindings = new ArrayList<>();
47
51
  private LiveSpring source;
48
52
  private Mapping[] sourceMaps = new Mapping[0];
49
- private SampledInput input;
53
+ private static final SampledInput[] NONE = new SampledInput[0];
54
+ private SampledInput[] inputs = NONE;
55
+ private double inputRest;
50
56
  private boolean direct = true;
51
57
  private double targetValue;
52
58
  private boolean moving, reduced, disposed, paused, inputHeld, sensorInput;
@@ -74,25 +80,46 @@ public final class LiveSpring {
74
80
  }
75
81
  public void updateInput(String snapshot) {
76
82
  if (disposed) return;
77
- try { input = new SampledInput(new JSONObject(snapshot)); }
83
+ try { inputs = new SampledInput[]{ new SampledInput(new JSONObject(snapshot)) }; }
78
84
  catch (JSONException error) { throw new IllegalArgumentException(error); }
79
- sensorInput = false; direct = false;
85
+ inputRest = 0; sensorInput = false; direct = false;
80
86
  wake();
81
87
  }
88
+ /** The target is a rest value plus sampled inputs that each keep their own clock. */
89
+ public void setInputs(double rest, String snapshots) {
90
+ if (disposed) return;
91
+ try {
92
+ JSONArray array = new JSONArray(snapshots);
93
+ SampledInput[] next = new SampledInput[array.length()];
94
+ for (int i = 0; i < next.length; i++) next[i] = new SampledInput(array.getJSONObject(i));
95
+ inputs = next;
96
+ } catch (JSONException error) { throw new IllegalArgumentException(error); }
97
+ inputRest = rest; sensorInput = false; direct = false;
98
+ wake();
99
+ }
100
+ private double inputValue(double now) {
101
+ double value = inputRest;
102
+ for (SampledInput input : inputs) value += input.value(now);
103
+ return value;
104
+ }
105
+ private boolean inputMoving(double now) {
106
+ for (SampledInput input : inputs) if (input.running(now)) return true;
107
+ return false;
108
+ }
82
109
  public void setSensorInput(double value) {
83
110
  if (disposed || !Double.isFinite(value) || (sensorInput && inputHeld && state.position == value)) return;
84
- sensorInput = true; inputHeld = true; direct = true; targetValue = value; input = null;
111
+ sensorInput = true; inputHeld = true; direct = true; targetValue = value; inputs = NONE;
85
112
  state.position = value; state.velocity = 0; moving = false;
86
113
  schedule();
87
114
  }
88
115
  public void setTarget(double value) {
89
116
  if (disposed) return;
90
- sensorInput = false; direct = true; targetValue = value; input = null;
117
+ sensorInput = false; direct = true; targetValue = value; inputs = NONE;
91
118
  wake();
92
119
  }
93
120
  public void holdInput(double value, double velocity) {
94
121
  if (disposed) return;
95
- sensorInput = false; inputHeld = true; direct = true; targetValue = value; input = null;
122
+ sensorInput = false; inputHeld = true; direct = true; targetValue = value; inputs = NONE;
96
123
  state.position = value; state.velocity = velocity; moving = true;
97
124
  apply(); schedule();
98
125
  }
@@ -114,8 +141,8 @@ public final class LiveSpring {
114
141
  }
115
142
  private void step(double now, double dt) {
116
143
  if (disposed || paused || inputHeld) return;
117
- double target = source == null ? (direct ? targetValue : input.value(now)) : map(source.state.position, sourceMaps);
118
- boolean inputMoving = source == null ? !direct && input.running(now) : source.moving;
144
+ double target = source == null ? (direct ? targetValue : inputValue(now)) : map(source.state.position, sourceMaps);
145
+ boolean inputMoving = source == null ? !direct && inputMoving(now) : source.moving;
119
146
  if (reduced) { state.position = target; state.velocity = 0; }
120
147
  else state.advance(target, stiffness, damping, mass, dt);
121
148
  boolean rest = Math.abs(state.position - target) <= restDelta && Math.abs(state.velocity) <= restSpeed;
@@ -68,12 +68,13 @@ public final class ScrollHeader implements ViewTreeObserver.OnScrollChangedListe
68
68
  public void scroll(double target,boolean animated) {
69
69
  if(disposed || !Double.isFinite(target))return;
70
70
  target=Math.max(0,target);
71
- if(animated){list.smoothScrollBy((int)Math.round((target-current())*density),300);return;}
72
71
  double divider=list.getDividerHeight()/density, stride=rowHeight+divider;
73
72
  int index=target<spacer+divider?0:1+(int)Math.floor((target-spacer-divider)/stride);
74
73
  index=Math.max(0,Math.min(list.getCount()-1,index));
75
74
  double start=index==0?0:spacer+divider+(index-1)*stride;
76
- list.setSelectionFromTop(index,(int)Math.round((start-target)*density));
75
+ int top=(int)Math.round((start-target)*density);
76
+ if(animated){list.smoothScrollToPositionFromTop(index,top,300);return;}
77
+ list.setSelectionFromTop(index,top);
77
78
  list.post(new Runnable(){public void run(){if(!disposed)update(true);}});
78
79
  }
79
80
  public void dispose() {
@@ -56,18 +56,28 @@ public final class ShapeLayer extends Drawable {
56
56
  this.commands=commands;this.from=from;this.to=to;this.times=times;this.samples=samples;
57
57
  this.duration=duration;this.delay=delay;this.repeats=repeats;this.reverse=reverse;
58
58
  current=from.clone();rebuild();apply(0);
59
+ active++;clock(0);
60
+ }
61
+ /** A resume rebuilds the clock: Android moves a seeked start time by the paused duration on the frame after resume(). */
62
+ public void control(double time,boolean paused){
63
+ if(animator==null)return;
64
+ long at=Math.max(0,(long)time);
65
+ if(paused){animator.setCurrentPlayTime(at);animator.pause();}
66
+ else if(animator.isPaused())clock(at);
67
+ else animator.setCurrentPlayTime(at);
68
+ apply(time);
69
+ }
70
+ private void clock(long at){
71
+ ValueAnimator old=animator;
72
+ if(old!=null){old.removeAllUpdateListeners();old.removeAllListeners();old.cancel();}
59
73
  final ValueAnimator a=ValueAnimator.ofFloat(0f,1f);
60
74
  a.setDuration(repeats<0?60000:Math.max(1,(long)Math.ceil(delay+duration*(repeats+1))));
61
75
  a.setRepeatCount(repeats<0?ValueAnimator.INFINITE:0);a.setInterpolator(new LinearInterpolator());
62
76
  a.addUpdateListener(v->apply(v.getCurrentPlayTime()));
63
77
  a.addListener(new AnimatorListenerAdapter(){@Override public void onAnimationEnd(Animator ended){if(animator==a){animator=null;active--;}}});
64
- animator=a;active++;a.start();
65
- }
66
- public void control(double time,boolean paused){
67
- if(animator==null)return;
68
- animator.setCurrentPlayTime(Math.max(0,(long)time));
69
- if(paused)animator.pause();else animator.resume();
70
- apply(time);
78
+ animator=a;
79
+ if(at>0)a.setCurrentPlayTime(at);
80
+ a.start();
71
81
  }
72
82
  private void apply(double elapsed){
73
83
  double t=Math.max(0,elapsed-delay), fraction;
@@ -1,4 +1,5 @@
1
1
  -keep class org.rafaelaugusto.jolt.ColorTarget { *; }
2
+ -keep class org.rafaelaugusto.jolt.KeyframeFactory { *; }
2
3
  -keep class org.rafaelaugusto.jolt.CircleWorld { *; }
3
4
  -keep class org.rafaelaugusto.jolt.CircleWorld$* { *; }
4
5
  -keep class org.rafaelaugusto.jolt.MotionProbe { *; }
@@ -0,0 +1,16 @@
1
+ #import <UIKit/UIKit.h>
2
+ NS_ASSUME_NONNULL_BEGIN
3
+
4
+ /** Animator supplied through Core's modal transition delegate. No delegate replacement. */
5
+ @interface JoltInteractiveModal : NSObject <UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning, UIGestureRecognizerDelegate>
6
+ + (instancetype)attach:(UIViewController *)controller stiffness:(double)stiffness damping:(double)damping mass:(double)mass shouldBegin:(BOOL (^)(void))shouldBegin onEvent:(void (^)(NSInteger))onEvent;
7
+ + (NSUInteger)activeCount;
8
+ + (NSUInteger)recognizerCount;
9
+ @property(nonatomic, readonly) BOOL interactive;
10
+ @property(nonatomic, readonly) double progress;
11
+ @property(nonatomic, readonly) double velocity;
12
+ - (void)preparePresenting:(BOOL)presenting reduced:(BOOL)reduced timeScale:(double)timeScale;
13
+ - (void)cancel;
14
+ - (void)dispose;
15
+ @end
16
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,178 @@
1
+ #import "JoltInteractiveModal.h"
2
+ #import "JoltSpringState.h"
3
+
4
+ @interface JoltInteractiveModal () {
5
+ JoltSpringState _spring;
6
+ }
7
+ @property(nonatomic, weak) UIViewController *controller;
8
+ @property(nonatomic) UIPanGestureRecognizer *pan;
9
+ @property(nonatomic, copy) BOOL (^shouldBegin)(void);
10
+ @property(nonatomic, copy) void (^onEvent)(NSInteger);
11
+ @property(nonatomic) id<UIViewControllerContextTransitioning> context;
12
+ @property(nonatomic) UIView *moving;
13
+ @property(nonatomic) CGAffineTransform originalTransform;
14
+ @property(nonatomic) CGRect bounds;
15
+ @property(nonatomic) CADisplayLink *link;
16
+ @property(nonatomic) double stiffness, damping, mass, previous, started, target, timeScale;
17
+ @property(nonatomic) NSNumber *pendingEnd;
18
+ @property(nonatomic, readwrite) BOOL interactive;
19
+ @property(nonatomic) BOOL presenting, reduced, settling, committed, disposed;
20
+ @end
21
+
22
+ @implementation JoltInteractiveModal
23
+ static NSUInteger active = 0, recognizers = 0;
24
+ + (NSUInteger)activeCount { return active; }
25
+ + (NSUInteger)recognizerCount { return recognizers; }
26
+ + (instancetype)attach:(UIViewController *)controller stiffness:(double)stiffness damping:(double)damping mass:(double)mass shouldBegin:(BOOL (^)(void))shouldBegin onEvent:(void (^)(NSInteger))onEvent {
27
+ JoltInteractiveModal *owner = [self new];
28
+ owner.controller = controller;
29
+ owner.stiffness = stiffness; owner.damping = damping; owner.mass = mass;
30
+ owner.timeScale = 1;
31
+ owner.shouldBegin = shouldBegin; owner.onEvent = onEvent;
32
+ owner.pan = [[UIPanGestureRecognizer alloc] initWithTarget:owner action:@selector(pan:)];
33
+ owner.pan.maximumNumberOfTouches = 1;
34
+ owner.pan.delegate = owner;
35
+ [controller.view addGestureRecognizer:owner.pan]; recognizers++;
36
+ [[NSNotificationCenter defaultCenter] addObserver:owner selector:@selector(cancel) name:UIApplicationWillResignActiveNotification object:nil];
37
+ return owner;
38
+ }
39
+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)recognizer shouldReceiveTouch:(UITouch *)touch {
40
+ // Scrolling content and native controls keep their own interaction. A modal
41
+ // can expose an ordinary, non-scrolling header as its dismissal surface.
42
+ for (UIView *view = touch.view; view; view = view.superview) {
43
+ if ([view isKindOfClass:UIControl.class] || [view isKindOfClass:UIScrollView.class]) return NO;
44
+ if (view == self.controller.view) break;
45
+ }
46
+ return YES;
47
+ }
48
+ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)recognizer {
49
+ UIViewController *controller = self.controller;
50
+ CGPoint velocity = [self.pan velocityInView:controller.view];
51
+ return !self.disposed && !self.context && controller.presentingViewController &&
52
+ !controller.presentedViewController && !controller.transitionCoordinator && !controller.modalInPresentation &&
53
+ velocity.y > fabs(velocity.x) && self.shouldBegin && self.shouldBegin();
54
+ }
55
+ - (void)preparePresenting:(BOOL)presenting reduced:(BOOL)reduced timeScale:(double)timeScale {
56
+ self.presenting = presenting; self.reduced = reduced; self.timeScale = timeScale;
57
+ }
58
+ - (void)pan:(UIPanGestureRecognizer *)pan {
59
+ if (pan.state == UIGestureRecognizerStateBegan) {
60
+ self.pendingEnd = nil; self.interactive = YES;
61
+ _spring = (JoltSpringState){0, 0};
62
+ if (self.onEvent) self.onEvent(2);
63
+ }
64
+ if (!self.interactive || self.settling) return;
65
+ double height = MAX(1, self.context ? self.bounds.size.height : self.controller.view.bounds.size.height);
66
+ double progress = MAX(0, MIN(1, [pan translationInView:self.controller.view].y / height));
67
+ double velocity = [pan velocityInView:self.controller.view].y / height;
68
+ _spring = (JoltSpringState){progress, velocity};
69
+ if (self.context) { [self render:progress]; [self.context updateInteractiveTransition:progress]; }
70
+ if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled || pan.state == UIGestureRecognizerStateFailed) {
71
+ BOOL finish = pan.state == UIGestureRecognizerStateEnded && progress + velocity * .16 > .5;
72
+ if (self.context) [self settle:finish velocity:velocity];
73
+ else self.pendingEnd = @(finish);
74
+ }
75
+ }
76
+ - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)context { return self.reduced ? 0 : .55; }
77
+ - (BOOL)wantsInteractiveStart { return self.interactive; }
78
+ - (CGFloat)completionSpeed { return 1; }
79
+ - (UIViewAnimationCurve)completionCurve { return UIViewAnimationCurveLinear; }
80
+ - (double)progress { return _spring.position; }
81
+ - (double)velocity { return _spring.velocity; }
82
+ - (void)prepare:(id<UIViewControllerContextTransitioning>)context {
83
+ self.context = context; active++;
84
+ UIViewController *controller = [context viewControllerForKey:self.presenting ? UITransitionContextToViewControllerKey : UITransitionContextFromViewControllerKey];
85
+ self.moving = [context viewForKey:self.presenting ? UITransitionContextToViewKey : UITransitionContextFromViewKey] ?: controller.view;
86
+ self.originalTransform = self.moving.transform;
87
+ if (self.presenting) {
88
+ self.moving.frame = [context finalFrameForViewController:controller];
89
+ [context.containerView addSubview:self.moving];
90
+ }
91
+ [self.moving setNeedsLayout]; [self.moving layoutIfNeeded];
92
+ self.bounds = context.containerView.bounds;
93
+ _spring = (JoltSpringState){0, 0};
94
+ [self render:0];
95
+ }
96
+ - (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)context {
97
+ JoltSpringState held = _spring;
98
+ [self prepare:context]; _spring = held;
99
+ [self render:_spring.position]; [context updateInteractiveTransition:_spring.position];
100
+ [self startLink];
101
+ if (self.pendingEnd || self.disposed) {
102
+ [self settle:!self.disposed && self.pendingEnd.boolValue velocity:_spring.velocity];
103
+ self.pendingEnd = nil;
104
+ }
105
+ }
106
+ - (void)animateTransition:(id<UIViewControllerContextTransitioning>)context {
107
+ [self prepare:context];
108
+ if (self.onEvent) self.onEvent(0);
109
+ [self settle:YES velocity:0];
110
+ }
111
+ - (void)render:(double)progress {
112
+ double p = MAX(0, MIN(1, progress));
113
+ double translation = self.bounds.size.height * (self.presenting ? 1 - p : p);
114
+ [CATransaction begin]; [CATransaction setDisableActions:YES];
115
+ self.moving.transform = CGAffineTransformConcat(self.originalTransform, CGAffineTransformMakeTranslation(0, translation));
116
+ [CATransaction commit];
117
+ }
118
+ - (void)startLink {
119
+ if (self.link || !self.context) return;
120
+ self.previous = CACurrentMediaTime();
121
+ self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
122
+ if (@available(iOS 15.0, *)) {
123
+ float maximum = MIN(120, UIScreen.mainScreen.maximumFramesPerSecond);
124
+ self.link.preferredFrameRateRange = CAFrameRateRangeMake(MIN(80, maximum), maximum, maximum);
125
+ }
126
+ [self.link addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
127
+ }
128
+ - (void)settle:(BOOL)finish velocity:(double)velocity {
129
+ if (!self.context || self.settling) return;
130
+ self.committed = finish; self.settling = YES; self.target = finish ? 1 : 0;
131
+ _spring.velocity = velocity;
132
+ if (self.interactive) {
133
+ // Core must know cancellation BEFORE UIKit executes dismissal completion.
134
+ if (self.onEvent) self.onEvent(finish ? 3 : 4);
135
+ if (!self.context) return;
136
+ if (finish) [self.context finishInteractiveTransition]; else [self.context cancelInteractiveTransition];
137
+ }
138
+ self.started = CACurrentMediaTime(); self.previous = self.started;
139
+ if (self.reduced || self.disposed) { [self finish]; return; }
140
+ [self startLink];
141
+ }
142
+ - (void)tick:(CADisplayLink *)link {
143
+ if (!CGRectEqualToRect(self.bounds, self.context.containerView.bounds) || !self.context.containerView.window) { [self cancel]; return; }
144
+ if (!self.settling) return;
145
+ double now = CACurrentMediaTime(), seconds = MAX(0, now - self.previous); self.previous = now;
146
+ _spring = JoltSpringAdvance(_spring, self.target, self.stiffness*self.timeScale*self.timeScale, self.damping*self.timeScale, self.mass, seconds);
147
+ if (!isfinite(_spring.position) || !isfinite(_spring.velocity)) { [self finish]; return; }
148
+ [self render:_spring.position];
149
+ if ((fabs(_spring.position-self.target)<.0005 && fabs(_spring.velocity)<.005) || now-self.started>=3) [self finish];
150
+ }
151
+ - (void)finish {
152
+ if (!self.context) return;
153
+ [self.link invalidate]; self.link = nil;
154
+ BOOL completed = self.committed, presenting = self.presenting;
155
+ _spring = (JoltSpringState){completed ? 1 : 0, 0}; [self render:_spring.position];
156
+ self.moving.transform = self.originalTransform;
157
+ id<UIViewControllerContextTransitioning> context = self.context;
158
+ self.context = nil; self.moving = nil; self.settling = NO; self.interactive = NO; active--;
159
+ [context completeTransition:completed];
160
+ if (self.onEvent) self.onEvent(presenting ? 1 : completed ? 5 : 6);
161
+ }
162
+ - (void)cancel {
163
+ if (!self.context) { if (self.interactive) self.pendingEnd = @NO; return; }
164
+ if (!self.settling) [self settle:!self.interactive velocity:0];
165
+ [self finish];
166
+ }
167
+ - (void)dispose {
168
+ if (self.disposed) return;
169
+ self.disposed = YES;
170
+ [self cancel];
171
+ [self.pan.view removeGestureRecognizer:self.pan];
172
+ self.pan.delegate = nil;
173
+ if (self.pan) recognizers--;
174
+ self.pan = nil;
175
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
176
+ self.shouldBegin = nil; self.onEvent = nil;
177
+ }
178
+ @end
@@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
12
12
  + (void)sync:(UIView *)view;
13
13
  - (void)follow:(JoltLiveSpring *)source mappings:(NSString *)mappings;
14
14
  - (void)updateInput:(NSString *)snapshot;
15
+ - (void)setInputs:(NSString *)snapshots rest:(double)rest;
15
16
  - (void)setSensorInput:(double)value;
16
17
  - (void)setTarget:(double)value;
17
18
  - (void)holdInput:(double)value velocity:(double)velocity;