Skip to content

Commit

Permalink
[moved] NumericWobbleSpring to core as NumericSpring
Browse files Browse the repository at this point in the history
Summary: Now that we have a small spring implementation that isn't encumbered by Facebook's patent grant, there's no reason for spring adaptors to be pluggable.  It will be much simpler to directly depend on Wobble.  This also ensures bugs don't creep in where there are subtle inconsistencies in the behaviors of Rebound and Wobble (e.g. whether `onStart` is dispatched synchronously)`

Reviewers: O2 Material Motion, O3 Material JavaScript platform reviewers, #material_motion, featherless

Reviewed By: O2 Material Motion, #material_motion, featherless

Tags: #material_motion

Differential Revision: http://codereview.cc/D3336
  • Loading branch information
appsforartists committed Sep 28, 2017
1 parent c9d7603 commit e45bf26
Show file tree
Hide file tree
Showing 23 changed files with 392 additions and 1,194 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ This repo houses the JavaScript implementation of Material Motion. For more inf

This repo houses all the packages that comprise the Material Motion implementation for JavaScript. They are available in [`packages`](https://github.com/material-motion/material-motion-js/tree/develop/packages/):

- [`core`](https://github.com/material-motion/material-motion-experiments-js/tree/develop/packages/core/) houses the core implementation of Material Motion; including [`MotionObservable`](https://github.com/material-motion/material-motion-js/blob/develop/packages/core/src/observables/MotionObservable.ts), [`MotionRuntime`](https://github.com/material-motion/material-motion-js/blob/develop/packages/core/src/MotionRuntime.ts), and [`ReactiveProperty`](https://github.com/material-motion/material-motion-js/blob/develop/packages/core/src/properties/ReactiveProperty.ts). To use it in an application, you'll also need an adapter such as `material-motion-views-react` or `material-motion-views-react`. This package is published on NPM as [`material-motion`](https://www.npmjs.com/package/material-motion).

- [`springs-rebound`](https://github.com/material-motion/material-motion-experiments-js/tree/develop/packages/springs-rebound/) houses the adapter that enables Material Motion interactions to make use of [Rebound.js](https://github.com/facebook/rebound-js/) springs.
- [`core`](https://github.com/material-motion/material-motion-experiments-js/tree/develop/packages/core/) houses the core implementation of Material Motion; including [`MotionObservable`](https://github.com/material-motion/material-motion-js/blob/develop/packages/core/src/observables/MotionObservable.ts), and [`ReactiveProperty`](https://github.com/material-motion/material-motion-js/blob/develop/packages/core/src/properties/ReactiveProperty.ts). To use it in an application, you'll also need an adapter such as `material-motion-views-dom` or `material-motion-views-react`. This package is published on NPM as [`material-motion`](https://www.npmjs.com/package/material-motion).

- [`views-dom`](https://github.com/material-motion/material-motion-experiments-js/tree/develop/packages/views-dom/) houses the adapter that supports using Material Motion in the DOM with `connectInteractionToElements()`.

Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"dependencies": {
"deep-equal": "1.0.1",
"indefinite-observable": "1.0.1",
"tslib": "^1.2.0"
"tslib": "^1.2.0",
"wobble": "1.1.0"
},
"devDependencies": {
"@types/deep-equal": "0.0.30",
Expand Down
154 changes: 152 additions & 2 deletions packages/core/src/interactions/NumericSpring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,19 @@
* under the License.
*/

import {
PartialSpringConfig,
Spring,
} from 'wobble';

import {
createProperty,
} from '../observables/createProperty';

import {
MotionObservable,
} from '../observables/MotionObservable';

import {
MotionProperty,
} from '../observables/MotionProperty';
Expand All @@ -28,13 +37,15 @@ import {

import {
ObservableWithMotionOperators,
Observer,
Subscription,
} from '../types';

// Springs are a core primitive in Material Motion, yet, the implementations we
// have are all from 3rd party libraries. Thus, the common
// getters/setters/properties live here, and each implementation can extend it
// to implement its own `value$`.
export abstract class NumericSpring {
export class NumericSpring {
readonly destination$: MotionProperty<number> = createProperty<number>({
initialValue: 0,
});
Expand Down Expand Up @@ -127,6 +138,145 @@ export abstract class NumericSpring {
return this.state$.read();
}

abstract value$: ObservableWithMotionOperators<number>;
value$ = new MotionObservable<number>(
(observer: Observer<number>) => {
const spring: Spring = new Spring();
const updateSpringConfig = spring.updateConfig.bind(spring);

let initialized = false;

let initialValueChangedWhileDisabled = false;
let initialVelocityChangedWhileDisabled = false;

this.state$.write(State.AT_REST);

spring.onStart(
() => {
this.state$.write(State.ACTIVE);
}
);

spring.onUpdate(
() => {
observer.next(spring.currentValue);
}
);

spring.onStop(
() => {
this.state$.write(State.AT_REST);
}
);

// During initialization, the configuration values will be set in the
// order they are subscribed to; hence,
const subscriptions: Array<Subscription> = [
// properties that configure the spring
this.stiffness$._map(toValueWithKey('stiffness')).subscribe(updateSpringConfig),
this.damping$._map(toValueWithKey('damping')).subscribe(updateSpringConfig),
this.threshold$._map(toValueWithKey('restDisplacementThreshold')).subscribe(updateSpringConfig),

// properties that initialize the spring
this.initialVelocity$.subscribe(
(initialVelocity: number) => {
if (this.enabled$.read()) {
updateSpringConfig({ initialVelocity });
} else {
initialVelocityChangedWhileDisabled = true;
}
}
),
this.initialValue$.subscribe(
(initialValue: number) => {
// We need to figure out what the right interplay is between
// `initialValue` and `value$`. It's probably more convenient for
// an author if changes to `initialValue` are passed through to
// `value$`. This means you can do something like
// `spring.value$.subscribe(location$)` and trust `location$` is
// always up-to-date. However, it's unclear how that would affect
// `state$`. Should `state$` go at_rest -> active -> at_rest every
// time something touches `initialValue`? Should `value$` be able
// to dispatch `initialValue$` without touching `value$`? Should we
// require authors to worry about connecting the two?
//
// Punting on this for now and forcing authors to deal with it.
if (this.enabled$.read()) {
updateSpringConfig({ fromValue: initialValue });
} else {
initialValueChangedWhileDisabled = true;
}
}
),

// properties that can start/stop the spring
this.enabled$.subscribe(
(enabled: boolean) => {
if (initialized) {
if (enabled) {
// For simple cases, the spring can manage its own state;
// however, we provide property APIs (like initialValue$) for
// situations where it should be managed externally.
//
// The ability to manage this state externally shouldn't require
// every author to do so; hence, we use dirty-checking to only
// initialize the spring's values if the author has used these
// features.
//
// There's probably a more elegant way to do this (queueing up
// changes as they come through and then applying the most
// recent one when enabled is set), but this is the simplest
// quick solution.

const springConfig: PartialSpringConfig = {
toValue: this.destination$.read(),
};

if (initialValueChangedWhileDisabled) {
springConfig.fromValue = this.initialValue$.read();
}

if (initialVelocityChangedWhileDisabled) {
springConfig.initialVelocity = this.initialVelocity$.read();
}

updateSpringConfig(springConfig);
spring.start();

initialValueChangedWhileDisabled = false;
initialVelocityChangedWhileDisabled = false;
} else if (spring.normalizedVelocity !== 0) {
spring.stop();
}
}
}
),

this.destination$.subscribe(
(destination: number) => {
if (this.enabled$.read()) {
updateSpringConfig({ toValue: destination });
spring.start();
};
}
),
];

initialized = true;

return function disconnect() {
subscriptions.forEach(
subscription => subscription.unsubscribe()
);
};
}
)._multicast();
}
export default NumericSpring;

function toValueWithKey<T>(key: string) {
return function (value: T) {
return {
[key]: value,
};
};
}
Loading

0 comments on commit e45bf26

Please sign in to comment.