Fixed issue with looping animation pausing during transition, created new issue because of KeyFrame PathMethods (eases)
This commit is contained in:
@@ -19,23 +19,34 @@ public interface AnimatedComponent {
|
||||
default void play(double speed, boolean loop) {
|
||||
AtomicInteger ticksPassed = new AtomicInteger();
|
||||
|
||||
// cloning the animation path to not mess with the original,
|
||||
// if an extra frame is to be added because loop is set to true
|
||||
|
||||
AnimationPath playedPath = this.getAnimationPath().clone();
|
||||
|
||||
if(loop)
|
||||
playedPath.add(playedPath.getNext());
|
||||
|
||||
this.setCurrentRun(new Timer(0, e -> {
|
||||
if (!playedPath.anyMore()) {
|
||||
System.out.println("called");
|
||||
|
||||
if(ticksPassed.get() * speed / (100) < 1) {
|
||||
ticksPassed.addAndGet(this.getAnimationPath().getInbetweens());
|
||||
return;
|
||||
}
|
||||
|
||||
KeyFrame next = this.getAnimationPath().getNext();
|
||||
|
||||
if(next == null) {
|
||||
if(loop)
|
||||
this.getAnimationPath().reset();
|
||||
if (loop)
|
||||
playedPath.reset();
|
||||
else
|
||||
((Timer) e.getSource()).stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if(ticksPassed.get() * speed / (100) < 1) {
|
||||
ticksPassed.addAndGet(playedPath.getInbetweens());
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("still executing");
|
||||
|
||||
KeyFrame next = playedPath.getNext();
|
||||
|
||||
this.setBounds(next.position().x, next.position().y, next.width(), next.height());
|
||||
|
||||
ticksPassed.set(0);
|
||||
|
||||
@@ -33,9 +33,32 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
||||
public AnimationPath(int inbetweens) {
|
||||
this.inbetweens = inbetweens;
|
||||
}
|
||||
/**
|
||||
* Get next keyframe according to current path iterator, depending on the current frame without increasing the animation iterators
|
||||
* @return next keyframe in order, depending on the current frame
|
||||
*/
|
||||
public KeyFrame peekNext() {
|
||||
int tempSubIterator = this.subFrameIterator, tempAnimationIterator = this.animationIterator;
|
||||
|
||||
if(tempSubIterator >= this.inbetweens) {
|
||||
tempSubIterator = 0;
|
||||
tempAnimationIterator++;
|
||||
}
|
||||
|
||||
if(tempAnimationIterator >= this.size())
|
||||
return null;
|
||||
|
||||
tempSubIterator++;
|
||||
|
||||
KeyFrame current = this.get(tempAnimationIterator);
|
||||
|
||||
KeyFrame next = this.getNextInOrder();
|
||||
|
||||
return getKeyFrame(current, next, tempSubIterator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next keyframe according to current path iterator, depending on the current frame
|
||||
* Get next keyframe according to current path iterator, depending on the current frame, and increase the animation iterators
|
||||
* @return next keyframe in order, depending on the current frame
|
||||
*/
|
||||
public KeyFrame getNext() {
|
||||
@@ -53,11 +76,36 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
||||
|
||||
KeyFrame next = this.getNextInOrder();
|
||||
|
||||
return getKeyFrame(current, next, this.subFrameIterator);
|
||||
}
|
||||
|
||||
private KeyFrame getKeyFrame(KeyFrame current, KeyFrame next, int subIterator) {
|
||||
// How far the transition should be finished
|
||||
|
||||
double transition = (double) this.subFrameIterator / this.inbetweens;
|
||||
double transition = this.linear(subIterator);
|
||||
|
||||
return inBetween(current, next, transition);
|
||||
KeyFrame.PathMethod method;
|
||||
|
||||
// Translate EASE_IN_AND_OUT to its respective currently relevant counterpart
|
||||
|
||||
//TODO: non-linear keyframes can't behandled this way. This just makes it bug around
|
||||
|
||||
if(transition < 0.5)
|
||||
method = current.pathMethod().equals(KeyFrame.PathMethod.EASE_IN_AND_OUT) ?
|
||||
KeyFrame.PathMethod.EASE_OUT : current.pathMethod();
|
||||
else
|
||||
method = next.pathMethod().equals(KeyFrame.PathMethod.EASE_IN_AND_OUT) ?
|
||||
KeyFrame.PathMethod.EASE_OUT : next.pathMethod();
|
||||
|
||||
// Else-case would be linear, which doesn't change anything
|
||||
|
||||
if(method.equals(KeyFrame.PathMethod.EASE_IN))
|
||||
transition = this.easeIn(subIterator);
|
||||
|
||||
else if(method.equals(KeyFrame.PathMethod.EASE_OUT))
|
||||
transition = this.easeOut(subIterator);
|
||||
|
||||
return inBetween(current, next, transition, method);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +122,13 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
||||
return this.get(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if there are any more frames coming
|
||||
*/
|
||||
public boolean anyMore() {
|
||||
return this.animationIterator + 1 < this.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset animation path iterator to start
|
||||
*/
|
||||
@@ -123,7 +178,7 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
||||
* @param scalar factor (ideally between 0 and 1, representing 0% transition to 100% transition)
|
||||
* @return in-between frame
|
||||
*/
|
||||
private static KeyFrame inBetween(KeyFrame kf1, KeyFrame kf2, double scalar) {
|
||||
private static KeyFrame inBetween(KeyFrame kf1, KeyFrame kf2, double scalar, KeyFrame.PathMethod method) {
|
||||
// position
|
||||
|
||||
Point difference = subtract(kf2.position(), kf1.position());
|
||||
@@ -141,6 +196,23 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
||||
|
||||
height = Math.toIntExact(Math.round(kf1.height() + dH * scalar));
|
||||
|
||||
return new KeyFrame(pos, width, height);
|
||||
return new KeyFrame(pos, width, height, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationPath clone() {
|
||||
return new AnimationPath(this.inbetweens, new ArrayList<>(this));
|
||||
}
|
||||
|
||||
private double linear(int subIterator) {
|
||||
return (double) subIterator/2 / this.inbetweens;
|
||||
}
|
||||
|
||||
private double easeIn(int subIterator) {
|
||||
return this.linear(subIterator/2) * this.linear(subIterator/2);
|
||||
}
|
||||
|
||||
public double easeOut(int subIterator) {
|
||||
return Math.sqrt(this.linear(subIterator*2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,28 @@ package org.openautonomousconnection.oacswing.animated;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public record KeyFrame(Point position, int width, int height) {
|
||||
public KeyFrame(JComponent component) {
|
||||
this(
|
||||
new Point(component.getX(), component.getY()),
|
||||
component.getBounds().width, component.getBounds().height);
|
||||
public record KeyFrame(Point position, int width, int height, PathMethod pathMethod) {
|
||||
|
||||
public KeyFrame(Point position, int width, int height) {
|
||||
this(position, width, height, PathMethod.LINEAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeyFrame{" +
|
||||
"position=" + position +
|
||||
", width=" + width +
|
||||
", height=" + height +
|
||||
'}';
|
||||
public KeyFrame(JComponent component, PathMethod pathMethod) {
|
||||
this(
|
||||
new Point(component.getX(), component.getY()),
|
||||
component.getBounds().width, component.getBounds().height, pathMethod);
|
||||
}
|
||||
|
||||
public KeyFrame(JComponent component) {
|
||||
this(component, PathMethod.LINEAR);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public enum PathMethod {
|
||||
LINEAR,
|
||||
EASE_IN,
|
||||
EASE_OUT,
|
||||
EASE_IN_AND_OUT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,9 +134,7 @@ public class DesignManager {
|
||||
Design.DARK.getElements().put(OACRadioButton.class, new DesignFlags(OACColor.DARK_BUTTON));
|
||||
Design.DARK.getElements().put(OACTitleBar.class, new DesignFlags(OACColor.DARK_SECTION));
|
||||
|
||||
Design.DARK.border = new RoundedBorder(OACColor.DARK_BORDERS.getColor(), 20, 2);
|
||||
|
||||
//Design.DARK.border = new BevelBorder(BevelBorder.LOWERED, OACColor.DARK_BORDERS.getColor(), OACColor.DARK_SHADOW.getColor());
|
||||
Design.DARK.border = new RoundedBorder(OACColor.DARK_BORDERS.getColor(), 8, 1);
|
||||
|
||||
DesignManager.getInstance().registerDesign(Design.DARK);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.openautonomousconnection.oacswing.animated.AnimationPath;
|
||||
import org.openautonomousconnection.oacswing.animated.JAnimatedPanel;
|
||||
import org.openautonomousconnection.oacswing.animated.KeyFrame;
|
||||
import org.openautonomousconnection.oacswing.component.OACFrame;
|
||||
import org.openautonomousconnection.oacswing.component.design.Design;
|
||||
import org.openautonomousconnection.oacswing.component.design.DesignManager;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -15,14 +18,20 @@ import java.awt.*;
|
||||
public class AnimationTests {
|
||||
@Test
|
||||
public synchronized void testSimpleAnimatedPanel() throws InterruptedException {
|
||||
JFrame frame = TestUtils.mockFrame();
|
||||
DesignManager.setGlobalDesign(Design.DARK);
|
||||
|
||||
OACFrame frame = TestUtils.mockOacFrame();
|
||||
|
||||
AnimationPath animationPath = new AnimationPath(50);
|
||||
|
||||
animationPath.add(new KeyFrame(new Point(400, 400), 400, 400));
|
||||
// This test was too simple
|
||||
// animationPath.add(new KeyFrame(new Point(400, 400), 400, 400));
|
||||
// animationPath.add(new KeyFrame(new Point(400, 300), 400, 400));
|
||||
// animationPath.add(new KeyFrame(new Point(100, 100), 400, 400));
|
||||
// animationPath.add(new KeyFrame(new Point(400, 400), 400, 400));
|
||||
|
||||
animationPath.add(new KeyFrame(new Point(400, 400), 400, 400, KeyFrame.PathMethod.EASE_OUT));
|
||||
animationPath.add(new KeyFrame(new Point(400, 300), 400, 400));
|
||||
animationPath.add(new KeyFrame(new Point(100, 100), 400, 400));
|
||||
animationPath.add(new KeyFrame(new Point(400, 400), 400, 400));
|
||||
|
||||
JAnimatedPanel animatedPanel = new JAnimatedPanel(animationPath);
|
||||
|
||||
@@ -34,6 +43,6 @@ public class AnimationTests {
|
||||
|
||||
frame.setVisible(true);
|
||||
|
||||
wait(15000);
|
||||
wait(10000);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user