Skip to content

Commit

Permalink
Add Val.animate(...).
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasMikula committed Feb 13, 2015
1 parent 9a363ba commit 4053012
Show file tree
Hide file tree
Showing 5 changed files with 416 additions and 0 deletions.
49 changes: 49 additions & 0 deletions reactfx-demos/src/main/java/org/reactfx/demo/AnimatedValDemo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.reactfx.demo;

import java.time.Duration;
import java.util.Random;

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

import org.reactfx.value.Val;
import org.reactfx.value.Var;

public class AnimatedValDemo extends Application {
private static final int W = 600;
private static final int H = 600;

@Override
public void start(Stage primaryStage) {
Circle circle = new Circle(30.0, Color.BLUE);
Pane canvas = new Pane(circle);

Var<Point2D> center = Var.newSimpleVar(new Point2D(W/2, H/2));
Val<Point2D> animCenter = center.animate(
(p1, p2) -> Duration.ofMillis((long) p1.distance(p2)),
(p1, p2, frac) -> p1.multiply(1.0-frac).add(p2.multiply(frac)));

circle.centerXProperty().bind(animCenter.map(Point2D::getX));
circle.centerYProperty().bind(animCenter.map(Point2D::getY));

Random random = new Random();

circle.setOnMouseClicked(click -> {
center.setValue(new Point2D(
random.nextInt(W),
random.nextInt(H)));
});

primaryStage.setScene(new Scene(canvas, W, H));
primaryStage.show();
}

public static void main(String[] args) {
launch(args);
}
}
55 changes: 55 additions & 0 deletions reactfx/src/main/java/org/reactfx/util/Interpolator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.reactfx.util;

import static javafx.animation.Interpolator.*;

/**
* Interpolates values between two boundary values.
*
* <p>This is a simpler and more flexible interface than the class
* {@link javafx.animation.Interpolator}. Simpler, because it only interpolates
* values of one type, {@code T}. More flexible, because the values to
* interpolate don't have to be numbers nor implement
* {@linkplain javafx.animation.Interpolatable}.
*
* @param <T> type of the values to interpolate
*/
@FunctionalInterface
public interface Interpolator<T> {
T interpolate(T start, T end, double fraction);

static final Interpolator<Double> LINEAR_DOUBLE =
(a, b, frac) -> LINEAR.interpolate(a.doubleValue(), b.doubleValue(), frac);

static final Interpolator<Integer> LINEAR_INTEGER =
(a, b, frac) -> LINEAR.interpolate(a.intValue(), b.intValue(), frac);

static final Interpolator<Long> LINEAR_LONG =
(a, b, frac) -> LINEAR.interpolate(a.longValue(), b.longValue(), frac);

static final Interpolator<Double> EASE_BOTH_DOUBLE =
(a, b, frac) -> EASE_BOTH.interpolate(a.doubleValue(), b.doubleValue(), frac);

static final Interpolator<Integer> EASE_BOTH_INTEGER =
(a, b, frac) -> EASE_BOTH.interpolate(a.intValue(), b.intValue(), frac);

static final Interpolator<Long> EASE_BOTH_LONG =
(a, b, frac) -> EASE_BOTH.interpolate(a.longValue(), b.longValue(), frac);

static final Interpolator<Double> EASE_IN_DOUBLE =
(a, b, frac) -> EASE_IN.interpolate(a.doubleValue(), b.doubleValue(), frac);

static final Interpolator<Integer> EASE_IN_INTEGER =
(a, b, frac) -> EASE_IN.interpolate(a.intValue(), b.intValue(), frac);

static final Interpolator<Long> EASE_IN_LONG =
(a, b, frac) -> EASE_IN.interpolate(a.longValue(), b.longValue(), frac);

static final Interpolator<Double> EASE_OUT_DOUBLE =
(a, b, frac) -> EASE_OUT.interpolate(a.doubleValue(), b.doubleValue(), frac);

static final Interpolator<Integer> EASE_OUT_INTEGER =
(a, b, frac) -> EASE_OUT.interpolate(a.intValue(), b.intValue(), frac);

static final Interpolator<Long> EASE_OUT_LONG =
(a, b, frac) -> EASE_OUT.interpolate(a.longValue(), b.longValue(), frac);
}
61 changes: 61 additions & 0 deletions reactfx/src/main/java/org/reactfx/value/AnimatedVal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.reactfx.value;

import java.time.Duration;
import java.util.function.BiFunction;

import javafx.animation.Transition;
import javafx.beans.value.ObservableValue;

import org.reactfx.Subscription;
import org.reactfx.util.Interpolator;

class AnimatedVal<T> extends ValBase<T> {
private final class FractionTransition extends Transition {

@Override
protected void interpolate(double frac) {
fraction = frac;
invalidate();
}

void setDuration(Duration d) {
setCycleDuration(javafx.util.Duration.millis(d.toMillis()));
}
}

private final ObservableValue<T> src;
private final BiFunction<? super T, ? super T, Duration> duration;
private final Interpolator<T> interpolator;
private final FractionTransition transition = new FractionTransition();

private double fraction = 1.0;
private T oldValue = null;

AnimatedVal(
ObservableValue<T> src,
BiFunction<? super T, ? super T, Duration> duration,
Interpolator<T> interpolator) {
this.src = src;
this.duration = duration;
this.interpolator = interpolator;
}

@Override
protected Subscription connect() {
oldValue = src.getValue();
return Val.observeChanges(src, (obs, oldVal, newVal) -> {
oldValue = getValue();
Duration d = duration.apply(oldValue, newVal);
transition.setDuration(d);
transition.playFromStart();
});
}

@Override
protected T computeValue() {
return fraction == 1.0
? src.getValue()
: interpolator.interpolate(oldValue, src.getValue(), fraction);
}

}
94 changes: 94 additions & 0 deletions reactfx/src/main/java/org/reactfx/value/Val.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.reactfx.value;

import java.time.Duration;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
Expand All @@ -19,6 +20,7 @@
import org.reactfx.EventStreamBase;
import org.reactfx.Subscription;
import org.reactfx.util.HexaFunction;
import org.reactfx.util.Interpolator;
import org.reactfx.util.PentaFunction;
import org.reactfx.util.TetraFunction;
import org.reactfx.util.TriFunction;
Expand Down Expand Up @@ -258,6 +260,49 @@ default SuspendableVal<T> suspendable() {
return suspendable(this);
}

/**
* Returns a new {@linkplain Val} that gradually transitions to the value
* of this {@linkplain Val} every time this {@linkplain Val} changes.
*
* <p>When the returned {@linkplain Val} has no observer, there is no
* gradual transition taking place. This means that there is no animation
* running in the background that would consume system resources. This also
* means that in that case {@link #getValue()} always returns the target
* value (i.e. the current value of this {@linkplain Val}), instead of any
* intermediate interpolated value.
*
* @param duration function that calculates the desired duration of the
* transition for two boundary values.
* @param interpolator calculates the interpolated value between two
* boundary values, given a fraction.
*/
default Val<T> animate(
BiFunction<? super T, ? super T, Duration> duration,
Interpolator<T> interpolator) {
return animate(this, duration, interpolator);
}

/**
* Returns a new {@linkplain Val} that gradually transitions to the value
* of this {@linkplain Val} every time this {@linkplain Val} changes.
*
* <p>When the returned {@linkplain Val} has no observer, there is no
* gradual transition taking place. This means that there is no animation
* running in the background that would consume system resources. This also
* means that in that case {@link #getValue()} always returns the target
* value (i.e. the current value of this {@linkplain Val}), instead of any
* intermediate interpolated value.
*
* @param duration the desired duration of the transition
* @param interpolator calculates the interpolated value between two
* boundary values, given a fraction.
*/
default Val<T> animate(
Duration duration,
Interpolator<T> interpolator) {
return animate(this, duration, interpolator);
}


/* ************** *
* Static methods *
Expand Down Expand Up @@ -370,6 +415,55 @@ static <T> SuspendableVal<T> suspendable(ObservableValue<T> obs) {
}
}

/**
* Creates a new {@linkplain Val} that gradually transitions to the value
* of the given {@linkplain ObservableValue} {@code obs} every time
* {@code obs} changes.
*
* <p>When the returned {@linkplain Val} has no observer, there is no
* gradual transition taking place. This means that there is no animation
* running in the background that would consume system resources. This also
* means that in that case {@link #getValue()} always returns the target
* value (i.e. the current value of {@code obs}), instead of any intermediate
* interpolated value.
*
* @param obs observable value to animate
* @param duration function that calculates the desired duration of the
* transition for two boundary values.
* @param interpolator calculates the interpolated value between two
* boundary values, given a fraction.
*/
static <T> Val<T> animate(
ObservableValue<T> obs,
BiFunction<? super T, ? super T, Duration> duration,
Interpolator<T> interpolator) {
return new AnimatedVal<>(obs, duration, interpolator);
}

/**
* Creates a new {@linkplain Val} that gradually transitions to the value
* of the given {@linkplain ObservableValue} {@code obs} every time
* {@code obs} changes.
*
* <p>When the returned {@linkplain Val} has no observer, there is no
* gradual transition taking place. This means that there is no animation
* running in the background that would consume system resources. This also
* means that in that case {@link #getValue()} always returns the target
* value (i.e. the current value of {@code obs}), instead of any intermediate
* interpolated value.
*
* @param obs observable value to animate
* @param duration the desired duration of the transition
* @param interpolator calculates the interpolated value between two
* boundary values, given a fraction.
*/
static <T> Val<T> animate(
ObservableValue<T> obs,
Duration duration,
Interpolator<T> interpolator) {
return animate(obs, (a, b) -> duration, interpolator);
}


static <A, B, R> Val<R> combine(
ObservableValue<A> src1,
Expand Down
Loading

0 comments on commit 4053012

Please sign in to comment.