Skip to content

Commit

Permalink
[added] motionObservable.distanceFrom(location)
Browse files Browse the repository at this point in the history
Summary: Closes #176

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/D3066
  • Loading branch information
appsforartists committed Apr 20, 2017
1 parent 90ed2fc commit a382ccc
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

export * from './types';
export * from './typeGuards';
export * from './properties';
export * from './observables';
export * from './interactions';
Expand Down
90 changes: 90 additions & 0 deletions packages/core/src/operators/__tests__/distanceFrom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/** @license
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

import { expect } from 'chai';

import {
beforeEach,
describe,
it,
} from 'mocha-sugar-free';

import {
stub,
} from 'sinon';

declare function require(name: string);

// chai really doesn't like being imported as an ES2015 module; will be fixed in v4
require('chai').use(
require('sinon-chai')
);

import {
createMockObserver,
} from 'material-motion-testing-utils';

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

describe('motionObservable.distanceFrom',
() => {
let stream;
let mockObserver;
let listener;

beforeEach(
() => {
mockObserver = createMockObserver();
stream = new MotionObservable(mockObserver.connect);
listener = stub();
}
);

it('should work on numbers',
() => {
stream.distanceFrom(10).subscribe(listener);

mockObserver.next(15);

expect(listener).to.have.been.calledWith(5);
}
);

it('should be positive, even if the next value is smaller',
() => {
stream.distanceFrom(10).subscribe(listener);

mockObserver.next(5);

expect(listener).to.have.been.calledWith(5);
}
);

it('should work on points',
() => {
stream.distanceFrom({ x: 100, y: 100 }).subscribe(listener);

mockObserver.next({ x: 0, y: 0 });

const hypoteneuse = 100 / Math.cos(Math.PI / 4);
const valueInLastCall = listener.lastCall.args[0];
expect(valueInLastCall).to.be.closeTo(hypoteneuse, 1);
}
);
}
);
51 changes: 51 additions & 0 deletions packages/core/src/operators/distanceFrom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/** @license
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

import {
Constructor,
MotionMappable,
Observable,
Point2D,
} from '../types';

import {
isPoint2D,
} from '../typeGuards';

export interface MotionMeasurable<T> {
distanceFrom(origin: T): Observable<number>;
}

export function withDistanceFrom<T, S extends Constructor<MotionMappable<T>>>(superclass: S): S & Constructor<MotionMeasurable<T>> {
return class extends superclass implements MotionMeasurable<T> {
/**
* Dispatches the distance that each upstream value is from a given origin.
* The origin may be a number or a point, but the dispatched value will
* always be a number; distance is computed using Pythagorean theorem.
*/
distanceFrom(origin: T): Observable<number> {
if (isPoint2D(origin)) {
return this._map(
(value: Point2D) => Math.sqrt((origin.x - value.x) ** 2 + (origin.y - value.y) ** 2)
);
} else {
return this._map(
(value: number) => Math.abs(origin - value)
);
}
}
};
}
12 changes: 9 additions & 3 deletions packages/core/src/operators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ import {
withDelayBy,
} from './delayBy';

import {
MotionMeasurable,
withDistanceFrom,
} from './distanceFrom';

import {
ObservableWithFoundationalMotionOperators,
withFoundationalMotionOperators,
Expand Down Expand Up @@ -103,24 +108,25 @@ export type ObservableWithMotionOperators<T> = ObservableWithFoundationalMotionO
& MotionPluckable<T> & MotionLoggable<T> & MotionDeduplicable<T> & MotionInvertible<T>
& MotionMergeable<T> & MotionRewritable<T> & MotionRewriteToable<T> & MotionRewriteRangeable<T>
& MotionThresholdable & MotionThresholdRangeable & MotionUpperBoundable & MotionLowerBoundable
& MotionOffsetable & MotionScalable;
& MotionOffsetable & MotionScalable & MotionDelayable<T> & MotionMeasurable<T>;

export function withMotionOperators<T, S extends Constructor<Observable<T>>>(superclass: S): S
& Constructor<ObservableWithMotionOperators<T>> {

return withThresholdRange(withThreshold(withRewriteRange(withRewriteTo(withRewrite(
withMerge(withInverted(withDedupe(withLog(withUpperBound(withLowerBound(
withOffsetBy(withScaledBy(withDelayBy(
withOffsetBy(withScaledBy(withDelayBy(withDistanceFrom(
withPluck<T, ObservableWithFoundationalMotionOperators<T>>(
withFoundationalMotionOperators<T, Constructor<Observable<T>>>(superclass)
)
)))
))))
)))))))
))));
}

export * from './dedupe';
export * from './delayBy';
export * from './distanceFrom';
export * from './foundation';
export * from './inverted';
export * from './log';
Expand Down
26 changes: 26 additions & 0 deletions packages/core/src/typeGuards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @license
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

import {
Point2D,
} from './types';

/**
* Checks if an object has numeric values for both `x` and `y`.
*/
export function isPoint2D(value: any): value is Point2D {
return typeof value.x === 'number' && typeof value.y === 'number';
}
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export {
MotionLoggable,
MotionLowerBoundable,
MotionMappable,
MotionMeasurable,
MotionMemorable,
MotionMergeable,
MotionNextOperable,
Expand Down

0 comments on commit a382ccc

Please sign in to comment.