Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scroll bounce #315

Merged
merged 19 commits into from
Feb 13, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 21 additions & 17 deletions esy.lock/index.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
{
"checksum": "172de2e0481d422d5d08539ac434ba67",
"root": "revery@link:./package.json",
"checksum": "b8676a52b49d394440ad75443d183b9c",
"root": "revery@link-dev:./package.json",
bryphe marked this conversation as resolved.
Show resolved Hide resolved
"node": {
"revery@link:./package.json": {
"id": "revery@link:./package.json",
"revery@link-dev:./package.json": {
"id": "revery@link-dev:./package.json",
"name": "revery",
"version": "link:./package.json",
"source": { "type": "link", "path": ".", "manifest": "package.json" },
"version": "link-dev:./package.json",
"source": {
"type": "link-dev",
"path": ".",
"manifest": "package.json"
},
"overrides": [],
"dependencies": [
"reason-glfw@3.2.1013@d41d8cd9",
Expand Down Expand Up @@ -245,20 +249,20 @@
"@opam/base-bytes@opam:base@19d0c2ff"
]
},
"@opam/yojson@opam:1.5.0@890db858": {
"id": "@opam/yojson@opam:1.5.0@890db858",
"@opam/yojson@opam:1.6.0@f7ec7c12": {
"id": "@opam/yojson@opam:1.6.0@f7ec7c12",
"name": "@opam/yojson",
"version": "opam:1.5.0",
"version": "opam:1.6.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/d8/d80de1bacdde292af42f7c78b323da7b#md5:d80de1bacdde292af42f7c78b323da7b",
"archive:https://github.com/ocaml-community/yojson/releases/download/1.5.0/yojson-1.5.0.tbz#md5:d80de1bacdde292af42f7c78b323da7b"
"archive:https://opam.ocaml.org/cache/md5/8c/8ca16557d3068253cc375452af3bde96#md5:8ca16557d3068253cc375452af3bde96",
"archive:https://github.com/ocaml-community/yojson/releases/download/1.6.0/yojson-1.6.0.tbz#md5:8ca16557d3068253cc375452af3bde96"
],
"opam": {
"name": "yojson",
"version": "1.5.0",
"path": "esy.lock/opam/yojson.1.5.0"
"version": "1.6.0",
"path": "esy.lock/opam/yojson.1.6.0"
}
},
"overrides": [],
Expand Down Expand Up @@ -708,12 +712,12 @@
},
"overrides": [],
"dependencies": [
"ocaml@4.7.1003@d41d8cd9", "@opam/yojson@opam:1.5.0@890db858",
"ocaml@4.7.1003@d41d8cd9", "@opam/yojson@opam:1.6.0@f7ec7c12",
"@opam/ocamlfind@opam:1.8.0@96572762",
"@opam/dune@opam:1.6.3@a7d7baed", "@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.7.1003@d41d8cd9", "@opam/yojson@opam:1.5.0@890db858",
"ocaml@4.7.1003@d41d8cd9", "@opam/yojson@opam:1.6.0@f7ec7c12",
"@opam/ocamlfind@opam:1.8.0@96572762"
]
},
Expand Down Expand Up @@ -1003,14 +1007,14 @@
},
"overrides": [],
"dependencies": [
"ocaml@4.7.1003@d41d8cd9", "@opam/yojson@opam:1.5.0@890db858",
"ocaml@4.7.1003@d41d8cd9", "@opam/yojson@opam:1.6.0@f7ec7c12",
"@opam/ocamlfind@opam:1.8.0@96572762",
"@opam/dune@opam:1.6.3@a7d7baed", "@opam/cppo@opam:1.6.5@bec3dbd9",
"@opam/cmdliner@opam:1.0.2@b29b7491",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.7.1003@d41d8cd9", "@opam/yojson@opam:1.5.0@890db858",
"ocaml@4.7.1003@d41d8cd9", "@opam/yojson@opam:1.6.0@f7ec7c12",
"@opam/cppo@opam:1.6.5@bec3dbd9",
"@opam/cmdliner@opam:1.0.2@b29b7491"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ build: [
["dune" "subst"] {pinned}
["dune" "build" "-p" name "-j" jobs]
]
run-test: [["dune" "runtest" "-p" name "-j" jobs]]
depends: [
"ocaml" {>= "4.02.3"}
"dune" {build}
"cppo" {build}
"easy-format"
"biniou" {>= "1.2.0"}
"alcotest" {with-test & >= "0.8.5"}
]
synopsis:
"Yojson is an optimized parsing and printing library for the JSON format"
Expand All @@ -31,6 +33,6 @@ The program atdgen can be used to derive OCaml-JSON serializers and
deserializers from type definitions."""
url {
src:
"https://github.com/ocaml-community/yojson/releases/download/1.5.0/yojson-1.5.0.tbz"
checksum: "md5=d80de1bacdde292af42f7c78b323da7b"
"https://github.com/ocaml-community/yojson/releases/download/1.6.0/yojson-1.6.0.tbz"
checksum: "md5=8ca16557d3068253cc375452af3bde96"
}
155 changes: 130 additions & 25 deletions src/UI/Animation.re
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,18 @@ module Make = (AnimationTickerImpl: AnimationTicker) => {
easing: float => float,
};

let activeAnimations: ref(list(animation)) = ref([]);
type activeAnimation = {
animation,
update: option(float => unit),
complete: option(unit => unit),
};

type playback = {
pause: unit => unit,
stop: unit => unit,
};

let activeAnimations: ref(list(activeAnimation)) = ref([]);

type animationOptions = {
duration: Time.t,
Expand All @@ -35,10 +46,59 @@ module Make = (AnimationTickerImpl: AnimationTicker) => {
easing: float => float,
};

/*y=u0(1−t)3+3u1(1−t)2t+3u2(1−t)t2+u3t3
u0, u3 = 0,1; t in [0,1]
equivalent to a normal bezier curve where p1x = 1/3 and p2x = 2/3
*/
// From http://greweb.me/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/
bryphe marked this conversation as resolved.
Show resolved Hide resolved
module Bezier = {
let make = (mX1, mY1, mX2, mY2) => {
let a = (aA1, aA2) => {
1.0 -. 3.0 *. aA2 +. 3.0 *. aA1;
};
let b = (aA1, aA2) => {
3.0 *. aA2 -. 6.0 *. aA1;
};
let c = aA1 => {
3.0 *. aA1;
};

// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
let calcBezier = (aT, aA1, aA2) => {
((a(aA1, aA2) *. aT +. b(aA1, aA2)) *. aT +. c(aA1)) *. aT;
};

// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
let getSlope = (aT, aA1, aA2) => {
3.0 *. a(aA1, aA2) *. aT *. aT +. 2.0 *. b(aA1, aA2) *. aT +. c(aA1);
};

let getTForX = aX => {
// Newton raphson iteration
let rec newton = (guess, attempts) => {
let currentSlope = getSlope(guess, mX1, mX2);
let goodEnough = currentSlope === 0.0;
if (goodEnough || attempts === 4) {
guess;
} else {
let currentX = calcBezier(guess, mX1, mX2) -. aX;
let newGuess = guess -. currentX /. currentSlope;
newton(newGuess, attempts + 1);
};
};
newton(aX, 0);
};
let get = aX => {
if (mX1 === mY1 && mX2 === mY2) {
aX;
} else if (aX <= 0.) {
0.;
} else if (aX >= 1.) {
1.;
} else {
calcBezier(getTForX(aX), mY1, mY2);
};
};
get;
};
};

let genOneDimBezierFunc = (u1: float, u2: float) => {
let a = 3. *. (u1 -. u2) +. 1.;
let b = (-6.) *. u1 +. 3. *. u2;
Expand All @@ -52,9 +112,12 @@ module Make = (AnimationTickerImpl: AnimationTicker) => {
let linear = (t: float) => t;
let quadratic = (t: float) => t *. t;
let cubic = (t: float) => t *. t *. t;
let easeIn = genOneDimBezierFunc(0., 0.45);
let easeOut = genOneDimBezierFunc(0.45, 1.);
let easeInOut = genOneDimBezierFunc(0., 1.);
let cubicBezier = Bezier.make;
// From https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function#Keywords_for_common_cubic-bezier_timing_functions
let ease = cubicBezier(0.25, 0.1, 0.25, 1.0);
let easeIn = cubicBezier(0.42, 0.0, 1.0, 1.0);
let easeOut = cubicBezier(0.0, 0.0, 0.58, 1.0);
let easeInOut = cubicBezier(0.42, 0.0, 0.58, 1.0);

let floatValue = (v: float) => {
let ret = {current: v};
Expand All @@ -67,28 +130,37 @@ module Make = (AnimationTickerImpl: AnimationTicker) => {
(clock -. adjustedStart) /. (endTime -. adjustedStart);
};

let hasStarted = (clock: float, anim: animation) => {
let t = getLocalTime(clock, anim);
let hasStarted = (clock: float, {animation, _}) => {
let t = getLocalTime(clock, animation);
t > 0.;
};

let isComplete = (clock: float, anim: animation) => {
let isComplete = (clock: float, {animation: anim, _}) => {
let t = getLocalTime(clock, anim);
t > 1. && !anim.repeat;
};

let tickAnimation = (clock: float, anim: animation) => {
let tickAnimation = (clock: float, {animation: anim, update, complete}) => {
bryphe marked this conversation as resolved.
Show resolved Hide resolved
let t = anim.easing(getLocalTime(clock, anim));

/* If the anim is set to repeat and the time has expired, restart */
if (anim.repeat && t > 1.) {
/* reset */

anim.startTime = anim.startTime +. anim.delay +. anim.duration;
let newT = getLocalTime(clock, anim);
anim.value.current = interpolate(newT, anim.startValue, anim.toValue);
if (t >= 1.) {
if (anim.repeat) {
/* If the anim is set to repeat and the time has expired, restart */
anim.startTime = anim.startTime +. anim.delay +. anim.duration;
let newT = getLocalTime(clock, anim);
anim.value.current = interpolate(newT, anim.startValue, anim.toValue);
} else {
switch (complete) {
| Some(c) => c()
| _ => ()
};
};
} else {
anim.value.current = interpolate(t, anim.startValue, anim.toValue);
switch (update) {
| Some(u) => u(anim.value.current)
| _ => ()
};
};
};

Expand All @@ -100,9 +172,9 @@ module Make = (AnimationTickerImpl: AnimationTicker) => {
List.length(anims) > 0;
};

let start =
let tween =
(animationValue: animationValue, animationOptions: animationOptions) => {
let animation: animation = {
let animation = {
id: AnimationId.getUniqueId(),
delay: Time.to_float_seconds(animationOptions.delay),
duration: Time.to_float_seconds(animationOptions.duration),
Expand All @@ -113,13 +185,46 @@ module Make = (AnimationTickerImpl: AnimationTicker) => {
startValue: animationValue.current,
easing: animationOptions.easing,
};

activeAnimations := List.append([animation], activeAnimations^);
animation;
};

let start = (~update=?, ~complete=?, animation) => {
let activeAnimation = {animation, update, complete};
activeAnimations := List.append([activeAnimation], activeAnimations^);
let removeAnimation = l =>
List.filter(({animation: a, _}) => a.id !== animation.id, l);
let playback = {
pause: () => {
activeAnimations := removeAnimation(activeAnimations^);
},
stop: () => {
animation.value.current = animation.startValue;
activeAnimations := removeAnimation(activeAnimations^);
},
};
playback;
};

module Chain = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, we really needed the ability to chain animations! Very cool 👍

type t = {
first: animation,
second: animation,
};
let make = (second, first) => {first, second};
let start = (~update, ~complete, {first, second}) =>
first
|> start(~update, ~complete=() =>
second |> start(~update, ~complete) |> ignore
)
|> ignore;
};

let cancel = (anim: animation) =>
activeAnimations := List.filter(a => a.id !== anim.id, activeAnimations^);
activeAnimations :=
List.filter(
({animation: a, _}) => a.id !== anim.id,
activeAnimations^,
);

let cancelAll = () => activeAnimations := [];

Expand All @@ -133,4 +238,4 @@ module Make = (AnimationTickerImpl: AnimationTicker) => {
Event.subscribe(AnimationTickerImpl.onTick, t =>
tick(Time.to_float_seconds(t))
);
};
};
10 changes: 5 additions & 5 deletions src/UI/Hooks.re
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
/* Hooks specific to Revery */
open Animated;

let animation =
(v: Animated.animationValue, opts: Animated.animationOptions, slots) => {
let animation = (v: animationValue, opts: animationOptions, slots) => {
let (currentV, _set, slots) = UiReact.Hooks.state(v, slots);

let slots =
UiReact.Hooks.effect(
OnMount,
() => {
let anim = Animated.start(v, opts);
let {stop, _} = tween(v, opts) |> start;

Some(() => Animated.cancel(anim));
Some(() => stop());
},
slots,
);

(currentV.current, slots);
};
};