1
0
mirror of https://github.com/aNNiMON/HotaruFX.git synced 2024-09-19 14:14:21 +03:00

Add property animations in timeline

This commit is contained in:
Victor 2017-08-25 10:40:01 +03:00
parent d9001bc106
commit 41550b683f
10 changed files with 182 additions and 42 deletions

View File

@ -3,12 +3,11 @@ package com.annimon.hotarufx;
import com.annimon.hotarufx.visual.Composition; import com.annimon.hotarufx.visual.Composition;
import com.annimon.hotarufx.visual.KeyFrame; import com.annimon.hotarufx.visual.KeyFrame;
import com.annimon.hotarufx.visual.objects.CircleNode; import com.annimon.hotarufx.visual.objects.CircleNode;
import com.annimon.hotarufx.visual.visitors.RenderVisitor;
import javafx.application.Application; import javafx.application.Application;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.paint.Paint; import javafx.scene.paint.Paint;
import javafx.stage.Stage; import javafx.stage.Stage;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.val; import lombok.val;
public class Main extends Application { public class Main extends Application {
@ -16,21 +15,45 @@ public class Main extends Application {
@Override @Override
public void start(Stage primaryStage) { public void start(Stage primaryStage) {
val composition = new Composition(1280, 720, 30); val composition = new Composition(1280, 720, 30);
val scene = composition.newScene(KeyFrame.of(0)); val scene = composition.getScene();
val colors = new Paint[] {Color.GREEN, Color.RED}; val colors = new Paint[] {Color.GREEN, Color.RED};
val halfWidth = scene.getVirtualWidth() / 2; val halfWidth = scene.getVirtualWidth() / 2;
val halfHeight = scene.getVirtualHeight() / 2; val halfHeight = scene.getVirtualHeight() / 2;
val renderVisitor = new RenderVisitor(composition.getTimeline());
for (int y = -1; y <= 1; y++) { for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) { for (int x = -1; x <= 1; x++) {
val circle = new CircleNode(); val node = new CircleNode();
circle.getCircle().setFill(colors[Math.abs(x * y)]); node.circle.setFill(colors[Math.abs(x * y)]);
circle.getCircle().setCenterX(x * halfWidth); node.circle.setCenterX(x * halfWidth);
circle.getCircle().setCenterY(y * halfHeight); node.circle.setCenterY(y * halfHeight);
circle.getCircle().setRadius(50); node.circle.setRadius(50);
circle.render(scene); node.radiusProperty()
.add(KeyFrame.of(30), 70)
.add(KeyFrame.of(90), 20)
.add(KeyFrame.of(300), 70);
if (x == 0 && y == 0) {
node.centerXProperty()
.add(KeyFrame.of(60), 0)
.add(KeyFrame.of(90), -400)
.add(KeyFrame.of(150), 400)
.add(KeyFrame.of(180), 0);
node.centerYProperty()
.add(KeyFrame.of(180), 0)
.add(KeyFrame.of(210), -400)
.add(KeyFrame.of(270), 400)
.add(KeyFrame.of(300), 0);
node.radiusProperty()
.add(KeyFrame.of(320), 180);
}
node.accept(renderVisitor, scene);
} }
} }
primaryStage.setTitle("HotaruFX");
primaryStage.setScene(composition.produceAnimationScene());
composition.getTimeline().getFxTimeline().play();
primaryStage.show();
} }
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -16,33 +16,31 @@ public class Composition {
private final double factor; private final double factor;
@Getter @Getter
private final double frameRate; private final TimeLine timeline;
@Getter @Getter
private final TimeLine timeLine; private final VirtualScene scene;
public Composition(int sceneWidth, int sceneHeight, double frameRate) { public Composition(int sceneWidth, int sceneHeight, double frameRate) {
this.sceneWidth = sceneWidth; this.sceneWidth = sceneWidth;
this.sceneHeight = sceneHeight; this.sceneHeight = sceneHeight;
this.frameRate = frameRate;
virtualHeight = 1080; virtualHeight = 1080;
factor = virtualHeight / (double) sceneHeight; factor = virtualHeight / (double) sceneHeight;
virtualWidth = (int) (sceneWidth * factor); virtualWidth = (int) (sceneWidth * factor);
timeLine = new TimeLine(); timeline = new TimeLine(frameRate);
scene = newScene();
} }
public VirtualScene newScene(KeyFrame keyFrame) { private VirtualScene newScene() {
val group = new Group(); val group = new Group();
group.setScaleX(1d / factor); group.setScaleX(1d / factor);
group.setScaleY(1d / factor); group.setScaleY(1d / factor);
group.setTranslateX(sceneWidth / 2); group.setTranslateX(sceneWidth / 2);
group.setTranslateY(sceneHeight / 2); group.setTranslateY(sceneHeight / 2);
val scene = new VirtualScene(group, virtualWidth, virtualHeight); return new VirtualScene(group, virtualWidth, virtualHeight);
timeLine.add(keyFrame, scene);
return scene;
} }
public Scene produceAnimationScene(VirtualScene scene) { public Scene produceAnimationScene() {
return new Scene(scene.getGroup(), sceneWidth, sceneHeight, Color.WHITE); return new Scene(scene.getGroup(), sceneWidth, sceneHeight, Color.WHITE);
} }
} }

View File

@ -0,0 +1,28 @@
package com.annimon.hotarufx.visual;
import com.annimon.hotarufx.exceptions.KeyFrameDuplicationException;
import java.util.Map;
import java.util.TreeMap;
import javafx.beans.value.WritableValue;
import lombok.Getter;
import lombok.val;
@Getter
public class PropertyTimeline<T> {
private final WritableValue<T> property;
private final Map<KeyFrame, T> keyFrames;
public PropertyTimeline(WritableValue<T> property) {
this.property = property;
keyFrames = new TreeMap<>();
}
public PropertyTimeline<T> add(KeyFrame keyFrame, T value) {
val previous = keyFrames.put(keyFrame, value);
if (previous != null) {
throw new KeyFrameDuplicationException(keyFrame);
}
return this;
}
}

View File

@ -1,24 +1,29 @@
package com.annimon.hotarufx.visual; package com.annimon.hotarufx.visual;
import com.annimon.hotarufx.exceptions.KeyFrameDuplicationException; import javafx.animation.KeyValue;
import java.util.Map; import javafx.animation.Timeline;
import java.util.TreeMap; import javafx.util.Duration;
import lombok.Getter; import lombok.Getter;
import lombok.val;
public class TimeLine { public class TimeLine {
@Getter @Getter
private final Map<KeyFrame, VirtualScene> keyFrames; private final double frameRate;
public TimeLine() { @Getter
keyFrames = new TreeMap<>(); private final Timeline fxTimeline;
public TimeLine(double frameRate) {
this.frameRate = frameRate;
fxTimeline = new Timeline(frameRate);
} }
public void add(KeyFrame keyFrame, VirtualScene scene) { public void addKeyFrame(KeyFrame keyFrame, KeyValue fxKeyValue) {
val previous = keyFrames.put(keyFrame, scene); fxTimeline.getKeyFrames().add(new javafx.animation.KeyFrame(
if (previous != null) { duration(keyFrame), fxKeyValue));
throw new KeyFrameDuplicationException(keyFrame); }
}
private Duration duration(KeyFrame keyFrame) {
return Duration.millis(1000d * keyFrame.getFrame() / frameRate);
} }
} }

View File

@ -1,20 +1,54 @@
package com.annimon.hotarufx.visual.objects; package com.annimon.hotarufx.visual.objects;
import com.annimon.hotarufx.visual.VirtualScene; import com.annimon.hotarufx.visual.PropertyTimeline;
import com.annimon.hotarufx.visual.TimeLine;
import com.annimon.hotarufx.visual.visitors.NodeVisitor;
import java.util.Optional;
import javafx.scene.shape.Circle; import javafx.scene.shape.Circle;
import lombok.Getter;
public class CircleNode implements ObjectNode { public class CircleNode extends ObjectNode {
@Getter public final Circle circle;
private final Circle circle;
private Optional<PropertyTimeline<Number>> centerX, centerY, radius;
public CircleNode() { public CircleNode() {
circle = new Circle(); circle = new Circle();
centerX = Optional.empty();
centerY = Optional.empty();
radius = Optional.empty();
}
public PropertyTimeline<Number> centerXProperty() {
if (!centerX.isPresent()) {
centerX = Optional.of(new PropertyTimeline<>(circle.centerXProperty()));
}
return centerX.get();
}
public PropertyTimeline<Number> centerYProperty() {
if (!centerY.isPresent()) {
centerY = Optional.of(new PropertyTimeline<>(circle.centerYProperty()));
}
return centerY.get();
}
public PropertyTimeline<Number> radiusProperty() {
if (!radius.isPresent()) {
radius = Optional.of(new PropertyTimeline<>(circle.radiusProperty()));
}
return radius.get();
}
public void buildTimeline(TimeLine timeline) {
super.buildTimeline(timeline);
centerX.ifPresent(PropertyConsumers.numberConsumer(timeline));
centerY.ifPresent(PropertyConsumers.numberConsumer(timeline));
radius.ifPresent(PropertyConsumers.numberConsumer(timeline));
} }
@Override @Override
public void render(VirtualScene scene) { public <R, T> R accept(NodeVisitor<R, T> visitor, T input) {
scene.add(circle); return visitor.visit(this, input);
} }
} }

View File

@ -1,8 +1,13 @@
package com.annimon.hotarufx.visual.objects; package com.annimon.hotarufx.visual.objects;
import com.annimon.hotarufx.visual.VirtualScene; import com.annimon.hotarufx.visual.TimeLine;
import com.annimon.hotarufx.visual.visitors.NodeVisitor;
public interface ObjectNode { public abstract class ObjectNode {
public abstract <R, T> R accept(NodeVisitor<R, T> visitor, T input);
public void buildTimeline(TimeLine timeline) {
void render(VirtualScene scene); }
} }

View File

@ -0,0 +1,21 @@
package com.annimon.hotarufx.visual.objects;
import com.annimon.hotarufx.visual.PropertyTimeline;
import com.annimon.hotarufx.visual.TimeLine;
import java.util.function.Consumer;
import javafx.animation.KeyValue;
public class PropertyConsumers {
public static Consumer<PropertyTimeline<Number>> numberConsumer(TimeLine timeline) {
return genericConsumer(timeline);
}
public static <T> Consumer<PropertyTimeline<T>> genericConsumer(TimeLine timeline) {
return t -> {
t.getKeyFrames().forEach((keyFrame, value) -> {
timeline.addKeyFrame(keyFrame, new KeyValue(t.getProperty(), value));
});
};
}
}

View File

@ -0,0 +1,8 @@
package com.annimon.hotarufx.visual.visitors;
import com.annimon.hotarufx.visual.objects.CircleNode;
public interface NodeVisitor<R, T> {
R visit(CircleNode node, T input);
}

View File

@ -0,0 +1,19 @@
package com.annimon.hotarufx.visual.visitors;
import com.annimon.hotarufx.visual.TimeLine;
import com.annimon.hotarufx.visual.VirtualScene;
import com.annimon.hotarufx.visual.objects.CircleNode;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class RenderVisitor implements NodeVisitor<Void, VirtualScene> {
private final TimeLine timeline;
@Override
public Void visit(CircleNode node, VirtualScene scene) {
node.buildTimeline(timeline);
scene.add(node.circle);
return null;
}
}

View File

@ -4,13 +4,12 @@ import lombok.val;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.junit.jupiter.api.Assertions.*;
class TimeLineTest { class TimeLineTest {
@Test @Test
void add() { void add() {
val timeline = new TimeLine(); val timeline = new PropertyTimeline<String>(null);
timeline.add(KeyFrame.of(20), null); timeline.add(KeyFrame.of(20), null);
timeline.add(KeyFrame.of(10), null); timeline.add(KeyFrame.of(10), null);
timeline.add(KeyFrame.of(0), null); timeline.add(KeyFrame.of(0), null);