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:
parent
d9001bc106
commit
41550b683f
@ -3,12 +3,11 @@ package com.annimon.hotarufx;
|
||||
import com.annimon.hotarufx.visual.Composition;
|
||||
import com.annimon.hotarufx.visual.KeyFrame;
|
||||
import com.annimon.hotarufx.visual.objects.CircleNode;
|
||||
import com.annimon.hotarufx.visual.visitors.RenderVisitor;
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.stage.Stage;
|
||||
import lombok.Data;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.val;
|
||||
|
||||
public class Main extends Application {
|
||||
@ -16,21 +15,45 @@ public class Main extends Application {
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
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 halfWidth = scene.getVirtualWidth() / 2;
|
||||
val halfHeight = scene.getVirtualHeight() / 2;
|
||||
val renderVisitor = new RenderVisitor(composition.getTimeline());
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
val circle = new CircleNode();
|
||||
circle.getCircle().setFill(colors[Math.abs(x * y)]);
|
||||
circle.getCircle().setCenterX(x * halfWidth);
|
||||
circle.getCircle().setCenterY(y * halfHeight);
|
||||
circle.getCircle().setRadius(50);
|
||||
circle.render(scene);
|
||||
val node = new CircleNode();
|
||||
node.circle.setFill(colors[Math.abs(x * y)]);
|
||||
node.circle.setCenterX(x * halfWidth);
|
||||
node.circle.setCenterY(y * halfHeight);
|
||||
node.circle.setRadius(50);
|
||||
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) {
|
||||
|
@ -16,33 +16,31 @@ public class Composition {
|
||||
private final double factor;
|
||||
|
||||
@Getter
|
||||
private final double frameRate;
|
||||
private final TimeLine timeline;
|
||||
|
||||
@Getter
|
||||
private final TimeLine timeLine;
|
||||
private final VirtualScene scene;
|
||||
|
||||
public Composition(int sceneWidth, int sceneHeight, double frameRate) {
|
||||
this.sceneWidth = sceneWidth;
|
||||
this.sceneHeight = sceneHeight;
|
||||
this.frameRate = frameRate;
|
||||
virtualHeight = 1080;
|
||||
factor = virtualHeight / (double) sceneHeight;
|
||||
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();
|
||||
group.setScaleX(1d / factor);
|
||||
group.setScaleY(1d / factor);
|
||||
group.setTranslateX(sceneWidth / 2);
|
||||
group.setTranslateY(sceneHeight / 2);
|
||||
val scene = new VirtualScene(group, virtualWidth, virtualHeight);
|
||||
timeLine.add(keyFrame, scene);
|
||||
return scene;
|
||||
return new VirtualScene(group, virtualWidth, virtualHeight);
|
||||
}
|
||||
|
||||
public Scene produceAnimationScene(VirtualScene scene) {
|
||||
public Scene produceAnimationScene() {
|
||||
return new Scene(scene.getGroup(), sceneWidth, sceneHeight, Color.WHITE);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,24 +1,29 @@
|
||||
package com.annimon.hotarufx.visual;
|
||||
|
||||
import com.annimon.hotarufx.exceptions.KeyFrameDuplicationException;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.util.Duration;
|
||||
import lombok.Getter;
|
||||
import lombok.val;
|
||||
|
||||
public class TimeLine {
|
||||
|
||||
@Getter
|
||||
private final Map<KeyFrame, VirtualScene> keyFrames;
|
||||
private final double frameRate;
|
||||
|
||||
public TimeLine() {
|
||||
keyFrames = new TreeMap<>();
|
||||
@Getter
|
||||
private final Timeline fxTimeline;
|
||||
|
||||
public TimeLine(double frameRate) {
|
||||
this.frameRate = frameRate;
|
||||
fxTimeline = new Timeline(frameRate);
|
||||
}
|
||||
|
||||
public void add(KeyFrame keyFrame, VirtualScene scene) {
|
||||
val previous = keyFrames.put(keyFrame, scene);
|
||||
if (previous != null) {
|
||||
throw new KeyFrameDuplicationException(keyFrame);
|
||||
public void addKeyFrame(KeyFrame keyFrame, KeyValue fxKeyValue) {
|
||||
fxTimeline.getKeyFrames().add(new javafx.animation.KeyFrame(
|
||||
duration(keyFrame), fxKeyValue));
|
||||
}
|
||||
|
||||
private Duration duration(KeyFrame keyFrame) {
|
||||
return Duration.millis(1000d * keyFrame.getFrame() / frameRate);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,54 @@
|
||||
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 lombok.Getter;
|
||||
|
||||
public class CircleNode implements ObjectNode {
|
||||
public class CircleNode extends ObjectNode {
|
||||
|
||||
@Getter
|
||||
private final Circle circle;
|
||||
public final Circle circle;
|
||||
|
||||
private Optional<PropertyTimeline<Number>> centerX, centerY, radius;
|
||||
|
||||
public CircleNode() {
|
||||
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
|
||||
public void render(VirtualScene scene) {
|
||||
scene.add(circle);
|
||||
public <R, T> R accept(NodeVisitor<R, T> visitor, T input) {
|
||||
return visitor.visit(this, input);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
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 {
|
||||
|
||||
void render(VirtualScene scene);
|
||||
public abstract <R, T> R accept(NodeVisitor<R, T> visitor, T input);
|
||||
|
||||
public void buildTimeline(TimeLine timeline) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -4,13 +4,12 @@ import lombok.val;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class TimeLineTest {
|
||||
|
||||
@Test
|
||||
void add() {
|
||||
val timeline = new TimeLine();
|
||||
val timeline = new PropertyTimeline<String>(null);
|
||||
timeline.add(KeyFrame.of(20), null);
|
||||
timeline.add(KeyFrame.of(10), null);
|
||||
timeline.add(KeyFrame.of(0), null);
|
||||
|
Loading…
Reference in New Issue
Block a user