Skip to content
This repository has been archived by the owner on Aug 13, 2021. It is now read-only.

Commit

Permalink
Introduce MotionObservableConvertible.
Browse files Browse the repository at this point in the history
Summary:
This type makes it possible to create meta-types that have streams. By extending MotionObservableConvertible with operators, we can use operators on meta-types. We can also accept meta-types anywhere that we'd normally accept a MotionObservable.

This change sets the foundation for dramatically simplifying the overall usage of streams. Some examples of possible changes:

```
// Before
runtime.write(someProperty.stream, to: someProperty)
// After
runtime.write(someProperty, to: someProperty)

// Before
someProperty.stream.x()
// After
someProperty.x()
```

Depends on D2562.
Depends on D2561.

Reviewers: chuga, O4 Material Apple platform reviewers, O2 Material Motion, #material_motion

Reviewed By: chuga, O4 Material Apple platform reviewers

Subscribers: appsforartists, chuga

Tags: #material_motion

Differential Revision: http://codereview.cc/D2563
  • Loading branch information
jverkoey committed Jan 26, 2017
1 parent a428452 commit 23ab805
Show file tree
Hide file tree
Showing 24 changed files with 97 additions and 97 deletions.
27 changes: 11 additions & 16 deletions src/MotionObservable.swift
Expand Up @@ -38,7 +38,7 @@ public typealias CoreAnimationChannel = (CoreAnimationChannelEvent) -> Void
Throughout this documentation we will treat the words "observable" and "stream" as synonyms.
*/
public final class MotionObservable<T>: IndefiniteObservable<MotionObserver<T>>, ExtendableMotionObservable {
public final class MotionObservable<T>: IndefiniteObservable<MotionObserver<T>> {
/** Sugar for subscribing a MotionObserver. */
public func subscribe(next: @escaping NextChannel<T>,
state: @escaping StateChannel,
Expand Down Expand Up @@ -89,21 +89,16 @@ public final class MotionObserver<T>: Observer {
public let coreAnimation: CoreAnimationChannel
}

/**
This type is used for extending MotionObservable using generics.
This is required to be able to do extensions where T == some value, such as CGPoint. See
https://twitter.com/dgregor79/status/646167048645554176 for discussion of what appears to be a
bug in swift.
*/
public protocol ExtendableMotionObservable {
/** A MotionObservableConvertible has a canonical MotionObservable that it can return. */
public protocol MotionObservableConvertible {
associatedtype T

/**
We define this only so that T can be inferred by the compiler so that we don't have to
introduce a new generic type such as Value in the associatedtype here.
*/
func subscribe(next: @escaping NextChannel<T>,
state: @escaping StateChannel,
coreAnimation: @escaping CoreAnimationChannel) -> Subscription
/** Returns the canonical MotionObservable for this object. */
func asStream() -> MotionObservable<T>
}

extension MotionObservable: MotionObservableConvertible {
public func asStream() -> MotionObservable<T> {
return self
}
}
4 changes: 2 additions & 2 deletions src/MotionRuntime.swift
Expand Up @@ -42,9 +42,9 @@ public class MotionRuntime {
}

/** Subscribes to the stream, writes its output to the given property, and observes its state. */
public func write<O: ExtendableMotionObservable, T>(_ stream: O, to property: ReactiveProperty<T>) where O.T == T {
public func write<O: MotionObservableConvertible, T>(_ stream: O, to property: ReactiveProperty<T>) where O.T == T {
let token = NSUUID().uuidString
subscriptions.append(stream.subscribe(next: property.setValue, state: { [weak self] state in
subscriptions.append(stream.asStream().subscribe(next: property.setValue, state: { [weak self] state in
property.state(state)

guard let strongSelf = self else { return }
Expand Down
6 changes: 3 additions & 3 deletions src/operators/CGPoint.swift
Expand Up @@ -14,11 +14,11 @@
limitations under the License.
*/

extension ExtendableMotionObservable where T == CGPoint {
extension MotionObservableConvertible where T == CGPoint {

/** Extract the x value from a CGPoint. */
public func x() -> MotionObservable<CGFloat> { return _map { $0.x } }
public func x() -> MotionObservable<CGFloat> { return asStream()._map { $0.x } }

/** Extract the y value from a CGPoint. */
public func y() -> MotionObservable<CGFloat> { return _map { $0.y } }
public func y() -> MotionObservable<CGFloat> { return asStream()._map { $0.y } }
}
14 changes: 7 additions & 7 deletions src/operators/arithmetic.swift
Expand Up @@ -16,26 +16,26 @@

import Foundation

extension ExtendableMotionObservable where T == CGFloat {
extension MotionObservableConvertible where T == CGFloat {

/** Emits the incoming value + amount. */
public func offset(by amount: CGFloat) -> MotionObservable<CGFloat> { return _map { $0 + amount } }
public func offset(by amount: CGFloat) -> MotionObservable<CGFloat> { return asStream()._map { $0 + amount } }

/** Emits the incoming value * amount. */
public func scaled(by amount: CGFloat) -> MotionObservable<CGFloat> { return _map { $0 * amount } }
public func scaled(by amount: CGFloat) -> MotionObservable<CGFloat> { return asStream()._map { $0 * amount } }

/** Emits the incoming value / amount. */
public func normalized(by amount: CGFloat) -> MotionObservable<CGFloat> { return _map { $0 / amount } }
public func normalized(by amount: CGFloat) -> MotionObservable<CGFloat> { return asStream()._map { $0 / amount } }

/** Subtract the incoming value from the provided value. */
public func subtracted(from value: CGFloat) -> MotionObservable<CGFloat> { return _map { value - $0 } }
public func subtracted(from value: CGFloat) -> MotionObservable<CGFloat> { return asStream()._map { value - $0 } }
}

extension ExtendableMotionObservable where T == CGPoint {
extension MotionObservableConvertible where T == CGPoint {

/** Emits the incoming value / amount. */
public func normalized(by amount: CGSize) -> MotionObservable<CGPoint> {
return _map {
return asStream()._map {
return CGPoint(x: $0.x / amount.width,
y: $0.y / amount.height)
}
Expand Down
4 changes: 2 additions & 2 deletions src/operators/debugging.swift
Expand Up @@ -16,11 +16,11 @@

import Foundation

extension ExtendableMotionObservable {
extension MotionObservableConvertible {

/** Writes any incoming value to the console and then passes the value on. */
public func log(_ context: String? = nil) -> MotionObservable<T> {
return _nextOperator({ value, next in
return asStream()._nextOperator({ value, next in
if let context = context {
print(context, value)
} else {
Expand Down
8 changes: 4 additions & 4 deletions src/operators/distance.swift
Expand Up @@ -16,21 +16,21 @@

import Foundation

extension ExtendableMotionObservable where T == CGFloat {
extension MotionObservableConvertible where T == CGFloat {

/** Emits the distance between the incoming value and the location. */
public func distance(from location: CGFloat) -> MotionObservable<CGFloat> {
return _map {
return asStream()._map {
fabs($0 - location)
}
}
}

extension ExtendableMotionObservable where T == CGPoint {
extension MotionObservableConvertible where T == CGPoint {

/** Emits the distance between the incoming value and the location. */
public func distance(from location: CGPoint) -> MotionObservable<CGFloat> {
return _map {
return asStream()._map {
let xDelta = $0.x - location.x
let yDelta = $0.y - location.y
return sqrt(xDelta * xDelta + yDelta * yDelta)
Expand Down
4 changes: 2 additions & 2 deletions src/operators/foundation/_filter.swift
Expand Up @@ -16,11 +16,11 @@

import Foundation

extension ExtendableMotionObservable {
extension MotionObservableConvertible {

/** Only emit those items from an Observable that pass a test. */
public func _filter(_ predicate: @escaping (T) -> Bool) -> MotionObservable<T> {
return _nextOperator { value, next in
return asStream()._nextOperator { value, next in
if predicate(value) {
next(value)
}
Expand Down
4 changes: 2 additions & 2 deletions src/operators/foundation/_map.swift
Expand Up @@ -16,11 +16,11 @@

import Foundation

extension ExtendableMotionObservable {
extension MotionObservableConvertible {

/** Transform the items emitted by an Observable by applying a function to each item. */
func _map<U>(_ transform: @escaping (T) -> U) -> MotionObservable<U> {
return _nextOperator({ value, next in
return asStream()._nextOperator({ value, next in
next(transform(value))

}, coreAnimation: { event, coreAnimation in
Expand Down
6 changes: 3 additions & 3 deletions src/operators/foundation/_nextOperator.swift
Expand Up @@ -16,7 +16,7 @@

import Foundation

extension ExtendableMotionObservable {
extension MotionObservableConvertible {

/**
A light-weight operator builder.
Expand All @@ -26,7 +26,7 @@ extension ExtendableMotionObservable {
*/
func _nextOperator<U>(_ operation: @escaping (T, (U) -> Void) -> Void) -> MotionObservable<U> {
return MotionObservable<U> { observer in
return self.subscribe(next: {
return self.asStream().subscribe(next: {
return operation($0, observer.next)
}, state: observer.state, coreAnimation: { _ in
assertionFailure("Core animation is not supported by this operator.")
Expand All @@ -43,7 +43,7 @@ extension ExtendableMotionObservable {
*/
func _nextOperator<U>(_ operation: @escaping (T, (U) -> Void) -> Void, coreAnimation: @escaping (CoreAnimationChannelEvent, CoreAnimationChannel) -> Void) -> MotionObservable<U> {
return MotionObservable<U> { observer in
return self.subscribe(next: {
return self.asStream().subscribe(next: {
return operation($0, observer.next)
}, state: observer.state, coreAnimation: {
return coreAnimation($0, observer.coreAnimation)
Expand Down
4 changes: 2 additions & 2 deletions src/operators/gestures/centroid.swift
Expand Up @@ -16,11 +16,11 @@

import Foundation

extension ExtendableMotionObservable where T: UIGestureRecognizer {
extension MotionObservableConvertible where T: UIGestureRecognizer {

/** Extract centroid from the incoming gesture recognizer. */
public func centroid(in view: UIView) -> MotionObservable<CGPoint> {
return _map { value in
return asStream()._map { value in
value.location(in: view)
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/operators/gestures/onRecognitionState.swift
Expand Up @@ -16,18 +16,18 @@

import Foundation

extension ExtendableMotionObservable where T: UIGestureRecognizer {
extension MotionObservableConvertible where T: UIGestureRecognizer {

/** Only forwards the gesture recognizer if its state matches the provided value. */
public func onRecognitionState(_ state: UIGestureRecognizerState) -> MotionObservable<T> {
return _filter { value in
return asStream()._filter { value in
return value.state == state
}
}

/** Only forwards the gesture recognizer if its state matches any of the provided values. */
public func onRecognitionStates(_ states: [UIGestureRecognizerState]) -> MotionObservable<T> {
return _filter { value in
return asStream()._filter { value in
return states.contains(value.state)
}
}
Expand Down
9 changes: 5 additions & 4 deletions src/operators/gestures/rotated.swift
Expand Up @@ -16,17 +16,18 @@

import Foundation

extension ExtendableMotionObservable where T: UIRotationGestureRecognizer {
extension MotionObservableConvertible where T: UIRotationGestureRecognizer {

/**
Adds the current translation to the initial position and emits the result while the gesture
recognizer is active.
*/
func rotated(from initialRotation: MotionObservable<CGFloat>) -> MotionObservable<CGFloat> {
func rotated<O: MotionObservableConvertible>(from initialRotation: O) -> MotionObservable<CGFloat> where O.T == CGFloat {
let initialRotationStream = initialRotation.asStream()
var cachedInitialRotation: CGFloat?
return _nextOperator { value, next in
return asStream()._nextOperator { value, next in
if value.state == .began || (value.state == .changed && cachedInitialRotation == nil) {
cachedInitialRotation = initialRotation.read()
cachedInitialRotation = initialRotationStream.read()
} else if value.state != .began && value.state != .changed {
cachedInitialRotation = nil
}
Expand Down
18 changes: 10 additions & 8 deletions src/operators/gestures/scaled.swift
Expand Up @@ -16,21 +16,23 @@

import Foundation

extension ExtendableMotionObservable where T: UIPinchGestureRecognizer {
extension MotionObservableConvertible where T: UIPinchGestureRecognizer {

/**
Multiplies the current scale by the initial scale and emits the result while the gesture
recognizer is active.
*/
func scaled(from initialScale: MotionObservable<CGFloat>) -> MotionObservable<CGFloat> {
var cachedInitialScale: CGFloat?
return _nextOperator { value, next in
if value.state == .began || (value.state == .changed && cachedInitialScale == nil) {
cachedInitialScale = initialScale.read()
func scaled<O: MotionObservableConvertible>(from initialScale: O) -> MotionObservable<CGFloat> where O.T == CGFloat {
let initialScaleStream = initialScale.asStream()
var initialScale: CGFloat?
return asStream()._nextOperator { value, next in
if value.state == .began || (value.state == .changed && initialScale == nil) {
initialScale = initialScaleStream.read()

} else if value.state != .began && value.state != .changed {
cachedInitialScale = nil
initialScale = nil
}
if let cachedInitialScale = cachedInitialScale {
if let cachedInitialScale = initialScale {
let scale = value.scale
next(cachedInitialScale * scale)
}
Expand Down
10 changes: 6 additions & 4 deletions src/operators/gestures/translated.swift
Expand Up @@ -16,17 +16,19 @@

import Foundation

extension ExtendableMotionObservable where T: UIPanGestureRecognizer {
extension MotionObservableConvertible where T: UIPanGestureRecognizer {

/**
Adds the current translation to the initial position and emits the result while the gesture
recognizer is active.
*/
func translated(from initialPosition: MotionObservable<CGPoint>, in view: UIView) -> MotionObservable<CGPoint> {
func translated<O: MotionObservableConvertible>(from initialPosition: O, in view: UIView) -> MotionObservable<CGPoint> where O.T == CGPoint {
let initialPositionStream = initialPosition.asStream()
var cachedInitialPosition: CGPoint?
return _nextOperator { value, next in
return asStream()._nextOperator { value, next in
if value.state == .began || (value.state == .changed && cachedInitialPosition == nil) {
cachedInitialPosition = initialPosition.read()
cachedInitialPosition = initialPositionStream.read()

} else if value.state != .began && value.state != .changed {
cachedInitialPosition = nil
}
Expand Down
12 changes: 6 additions & 6 deletions src/operators/gestures/velocity.swift
Expand Up @@ -16,28 +16,28 @@

import Foundation

extension ExtendableMotionObservable where T: UIPanGestureRecognizer {
extension MotionObservableConvertible where T: UIPanGestureRecognizer {

/** Extract translational velocity from the incoming pan gesture recognizer. */
public func velocity(in view: UIView) -> MotionObservable<CGPoint> {
return _map { value in
return asStream()._map { value in
value.velocity(in: view)
}
}
}

extension ExtendableMotionObservable where T: UIRotationGestureRecognizer {
extension MotionObservableConvertible where T: UIRotationGestureRecognizer {

/** Extract rotational velocity from the incoming rotation gesture recognizer. */
public func velocity() -> MotionObservable<CGFloat> {
return _map { value in value.velocity }
return asStream()._map { value in value.velocity }
}
}

extension ExtendableMotionObservable where T: UIPinchGestureRecognizer {
extension MotionObservableConvertible where T: UIPinchGestureRecognizer {

/** Extract scale velocity from the incoming pinch gesture recognizer. */
public func velocity() -> MotionObservable<CGFloat> {
return _map { value in value.velocity }
return asStream()._map { value in value.velocity }
}
}
4 changes: 2 additions & 2 deletions src/operators/mapRange.swift
Expand Up @@ -16,7 +16,7 @@

import Foundation

extension ExtendableMotionObservable where T: Subtractable, T: Lerpable {
extension MotionObservableConvertible where T: Subtractable, T: Lerpable {

/** Linearly interpolate the incoming value along the given range to the destination range. */
public func mapRange<U>(
Expand All @@ -25,7 +25,7 @@ extension ExtendableMotionObservable where T: Subtractable, T: Lerpable {
destinationStart: U,
destinationEnd: U) -> MotionObservable<U>
where U: Lerpable, U: Subtractable, U: Addable {
return _map {
return asStream()._map {
let position = $0 - rangeStart

let vector = rangeEnd - rangeStart
Expand Down
4 changes: 2 additions & 2 deletions src/operators/mapTo.swift
Expand Up @@ -16,10 +16,10 @@

import Foundation

extension ExtendableMotionObservable {
extension MotionObservableConvertible {

/** Emit a constant value each time this operator receives a value. */
public func mapTo<U>(_ value: U) -> MotionObservable<U> {
return _map { _ in value }
return asStream()._map { _ in value }
}
}
4 changes: 2 additions & 2 deletions src/operators/multicast.swift
Expand Up @@ -16,7 +16,7 @@

import IndefiniteObservable

extension ExtendableMotionObservable {
extension MotionObservableConvertible {

/**
Turns a stream into a multicast stream.
Expand All @@ -33,7 +33,7 @@ extension ExtendableMotionObservable {
var lastCoreAnimationEvent: CoreAnimationChannelEvent?

let subscribe = {
subscription = self.subscribe(next: { value in
subscription = self.asStream().subscribe(next: { value in
lastValue = value
for observer in observers {
observer.next(value)
Expand Down

0 comments on commit 23ab805

Please sign in to comment.