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 18 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
33 changes: 32 additions & 1 deletion esy.lock/index.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"checksum": "00f17c2f78e59e271f094f9ffc9c5d07",
"checksum": "059efe49cf4f9d53e2c71dfa539a48d2",
"root": "revery@link-dev:./package.json",
bryphe marked this conversation as resolved.
Show resolved Hide resolved
"node": {
"revery@link-dev:./package.json": {
Expand All @@ -13,6 +13,7 @@
},
"overrides": [],
"dependencies": [
"rebez@github:jchavarri/rebez#46cbc183@d41d8cd9",
"reason-glfw@3.2.1015@d41d8cd9",
"reason-gl-matrix@0.9.9302@d41d8cd9",
"reason-fontkit@2.2.0@d41d8cd9", "ocaml@4.7.1004@d41d8cd9",
Expand Down Expand Up @@ -70,6 +71,22 @@
],
"devDependencies": []
},
"rebez@github:jchavarri/rebez#46cbc183@d41d8cd9": {
"id": "rebez@github:jchavarri/rebez#46cbc183@d41d8cd9",
"name": "rebez",
"version": "github:jchavarri/rebez#46cbc183",
"source": {
"type": "install",
"source": [ "github:jchavarri/rebez#46cbc183" ]
},
"overrides": [],
"dependencies": [
"refmterr@3.1.10@d41d8cd9", "pesy@0.4.1@d41d8cd9",
"ocaml@4.7.1004@d41d8cd9", "@opam/dune@opam:1.7.0@81948d64",
"@esy-ocaml/reason@3.4.0@d41d8cd9"
],
"devDependencies": []
},
"reason-glfw@3.2.1015@d41d8cd9": {
"id": "reason-glfw@3.2.1015@d41d8cd9",
"name": "reason-glfw",
Expand Down Expand Up @@ -132,6 +149,20 @@
],
"devDependencies": []
},
"pesy@0.4.1@d41d8cd9": {
"id": "pesy@0.4.1@d41d8cd9",
"name": "pesy",
"version": "0.4.1",
"source": {
"type": "install",
"source": [
"archive:https://registry.npmjs.org/pesy/-/pesy-0.4.1.tgz#sha1:37b3faccb3ecdb37f4bf3d95d04ffbd2633247af"
]
},
"overrides": [],
"dependencies": [],
"devDependencies": []
},
"ocaml@4.7.1004@d41d8cd9": {
"id": "ocaml@4.7.1004@d41d8cd9",
"name": "ocaml",
Expand Down
4 changes: 1 addition & 3 deletions examples/Examples.re
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,7 @@ let init = app => {
let example = exampleRender(win);

<View
onMouseWheel={evt =>
print_endline("onMouseWheel: " ++ string_of_float(evt.deltaY))
}
onMouseWheel={_evt => ()}
style=Style.[
position(`Absolute),
justifyContent(`Center),
Expand Down
22 changes: 18 additions & 4 deletions examples/ScrollView.re
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,23 @@ module Sample = {
let component = React.component("Sample");

let createElement = (~children as _, ()) =>
component((_slots: React.Hooks.empty) =>
component(slots => {
let (bounce, setBounce, _slots: React.Hooks.empty) =
React.Hooks.state(true, slots);
<View style=containerStyle>
<ScrollView style=outerBox>
<Text
text="Bounce"
style=Style.[
marginBottom(10),
fontFamily("Roboto-Regular.ttf"),
fontSize(20),
]
/>
<Checkbox
onChange={checked => setBounce(checked)}
style=Style.[marginBottom(10)]
/>
<ScrollView style=outerBox bounce>
<Image
src="outrun-logo.png"
style=Style.[width(512), height(256)]
Expand All @@ -44,8 +58,8 @@ module Sample = {
style=Style.[width(512), height(256)]
/>
</ScrollView>
</View>
);
</View>;
});
};

let render = () => <Sample />;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"reason-glfw": "^3.2.1015",
"reason-fontkit": "^2.2.0",
"reason-gl-matrix": "^0.9.9302",
"rebez": "*",
"@opam/color": "^0.2.0",
"@opam/js_of_ocaml": "*",
"@opam/js_of_ocaml-compiler": "*",
Expand All @@ -44,6 +45,7 @@
"flex": "^1.2.2"
},
"resolutions": {
"rebez": "github:jchavarri/rebez#46cbc183",
"@opam/cmdliner": "1.0.2",
"@opam/js_of_ocaml": "github:ocsigen/js_of_ocaml:js_of_ocaml.opam#db257ce",
"@opam/js_of_ocaml-compiler": "github:ocsigen/js_of_ocaml:js_of_ocaml-compiler.opam#db257ce",
Expand Down
21 changes: 21 additions & 0 deletions src/Core/Environment.re
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,24 @@ let getExecutingDirectory = () =>
isNative ? Filename.dirname(Sys.argv[0]) ++ Filename.dir_sep : "";

let getWorkingDirectory = () => Sys.getcwd();

type os =
| Windows
| Mac
| Linux
| Unknown;

let os = {
switch (Sys.os_type) {
| "Win32" => Windows
| _ =>
let ic = Unix.open_process_in("uname");
let _ = close_in(ic);
let uname = input_line(ic);
switch (uname) {
| "Darwin" => Mac
| "Linux" => Linux
| _ => Unknown
};
};
};
133 changes: 99 additions & 34 deletions src/UI/Animation.re
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ module type AnimationTicker = {
let onTick: Event.t(Time.t);
};

let optCall = (opt, param) =>
switch (opt) {
| Some(f) => f(param)
| None => ()
};

module Make = (AnimationTickerImpl: AnimationTicker) => {
type animationValue = {mutable current: float};

Expand All @@ -25,7 +31,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,26 +52,15 @@ 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
*/
let genOneDimBezierFunc = (u1: float, u2: float) => {
let a = 3. *. (u1 -. u2) +. 1.;
let b = (-6.) *. u1 +. 3. *. u2;
let c = 3. *. u1;
let ret = (t: float) => {
let t2 = t ** 2.;
a *. t2 *. t +. b *. t2 +. c *. t;
};
ret;
};
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 = Rebez.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 +73,31 @@ 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 {
optCall(complete, ());
};
} else {
anim.value.current = interpolate(t, anim.startValue, anim.toValue);
optCall(update, anim.value.current);
};
};

Expand All @@ -100,9 +109,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 +122,69 @@ 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};
animation.startTime = Time.to_float_seconds(AnimationTickerImpl.time());
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 = {animations: list(animation)};
let make = animation => {animations: [animation]};
let add = (animation, {animations: l}) => {
animations: List.cons(animation, l),
};
let start = (~update=?, ~complete=?, {animations: l}) => {
let currentPlayback = ref(None);
let rec runAnimation = (animations, index) => {
switch (animations) {
| [a, ...xl] =>
let playback =
a |> start(~update?, ~complete=() => runAnimation(xl, index + 1));
currentPlayback := Some(playback);
| [] => optCall(complete, ())
};
};
runAnimation(List.rev(l), 0);
let playback = {
pause: () => {
switch (currentPlayback^) {
| Some(p) => p.pause()
| None => ()
};
},
stop: () => {
switch (currentPlayback^) {
| Some(p) => p.stop()
| None => ()
};
},
};
playback;
};
};

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 Down
8 changes: 4 additions & 4 deletions src/UI/Hooks.re
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
/* 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,
);
Expand Down
2 changes: 1 addition & 1 deletion src/UI/dune
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
(name Revery_UI)
(public_name Revery_UI)
(preprocess (pps lwt_ppx))
(libraries brisk-reconciler lwt lwt.unix reglfw flex fontkit Revery_Core Revery_Shaders Revery_Geometry Revery_Math))
(libraries brisk-reconciler lwt lwt.unix reglfw flex fontkit rebez.lib Revery_Core Revery_Shaders Revery_Geometry Revery_Math))