From e61dbfa53151ae193188dccc540cfb0665a00c57 Mon Sep 17 00:00:00 2001 From: Tinglyyy Date: Sun, 8 Feb 2026 22:31:29 +0100 Subject: [PATCH] Fixed issue with looping animation pausing during transition, created new issue because of KeyFrame PathMethods (eases) --- .../oacswing/animated/AnimatedComponent.java | 31 ++++--- .../oacswing/animated/AnimationPath.java | 82 +++++++++++++++++-- .../oacswing/animated/KeyFrame.java | 33 +++++--- .../component/design/DesignManager.java | 4 +- .../oacswing/test/AnimationTests.java | 19 +++-- 5 files changed, 134 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimatedComponent.java b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimatedComponent.java index 5061724..988553b 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimatedComponent.java +++ b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimatedComponent.java @@ -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); diff --git a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java index db9c37c..75c5ec6 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java +++ b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java @@ -33,9 +33,32 @@ public class AnimationPath extends ArrayList { 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 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 { 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 { * @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 { 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)); } } diff --git a/src/main/java/org/openautonomousconnection/oacswing/animated/KeyFrame.java b/src/main/java/org/openautonomousconnection/oacswing/animated/KeyFrame.java index ee0ed6e..2f3f325 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/animated/KeyFrame.java +++ b/src/main/java/org/openautonomousconnection/oacswing/animated/KeyFrame.java @@ -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 } } diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java b/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java index cc0277f..a9cf2e9 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java @@ -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); } diff --git a/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java b/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java index 0639af9..d365a83 100644 --- a/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java +++ b/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java @@ -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); } }