From e61dbfa53151ae193188dccc540cfb0665a00c57 Mon Sep 17 00:00:00 2001 From: Tinglyyy Date: Sun, 8 Feb 2026 22:31:29 +0100 Subject: [PATCH 1/3] 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); } } From 59d72537b9fffbb3efc82b86ef00584b842682d9 Mon Sep 17 00:00:00 2001 From: Tinglyyy Date: Mon, 9 Feb 2026 22:15:18 +0100 Subject: [PATCH 2/3] Further work on fixing keyframe path methods (still not working), and declared "momentum" functionality for replaying AnimationPaths through AnimatedComponents (not implemented yet). Added Javadocs. --- .../oacswing/animated/AnimatedComponent.java | 35 +++- .../oacswing/animated/AnimationPath.java | 153 +++++++++++++++--- .../oacswing/test/AnimationTests.java | 2 +- 3 files changed, 166 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimatedComponent.java b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimatedComponent.java index 988553b..2736e66 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimatedComponent.java +++ b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimatedComponent.java @@ -7,6 +7,8 @@ package org.openautonomousconnection.oacswing.animated; import javax.swing.*; import java.util.concurrent.atomic.AtomicInteger; +// TODO: Implement pause functionality +// (preferably not interfering with play() always reliably playing the animation from the start; aka define it as "setPaused(bool)" or "pause()" and "unpause()") public interface AnimatedComponent { void setCurrentRun(Timer timer); Timer getCurrentRun(); @@ -16,7 +18,15 @@ public interface AnimatedComponent { void setBounds(int x, int y, int width, int height); - default void play(double speed, boolean loop) { + //TODO: implement momentum + /** + * Plays this object's animation path + * @param speed how fast the animation should play + * @param loop if should the animation should loop + * @param momentum basically how far an object "shoots off" from a specified keyframe when stopping at proportional speed + * (linear keyframes are excluded from this) + */ + default void play(double speed, boolean loop, double momentum) { AtomicInteger ticksPassed = new AtomicInteger(); // cloning the animation path to not mess with the original, @@ -29,8 +39,6 @@ public interface AnimatedComponent { this.setCurrentRun(new Timer(0, e -> { if (!playedPath.anyMore()) { - System.out.println("called"); - if (loop) playedPath.reset(); else @@ -43,8 +51,6 @@ public interface AnimatedComponent { return; } - System.out.println("still executing"); - KeyFrame next = playedPath.getNext(); this.setBounds(next.position().x, next.position().y, next.width(), next.height()); @@ -55,14 +61,33 @@ public interface AnimatedComponent { this.getCurrentRun().start(); } + /** + * Plays this object's animation path + * @param speed how fast the animation should play + * @param loop if should the animation should loop + */ + default void play(double speed, boolean loop) { + this.play(speed, loop, 0); + } + + /** + * Plays this object's animation path + * @param speed how fast the animation should play + */ default void play(double speed) { this.play(speed, false); } + /** + * Plays this object's animation path + */ default void play() { this.play(1, false); } + /** + * Stops and resets replay of this object's animation path + */ default void stop() { if(this.getCurrentRun() != null) if(this.getCurrentRun().isRunning()) diff --git a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java index 75c5ec6..fe5b520 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java +++ b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java @@ -84,28 +84,95 @@ public class AnimationPath extends ArrayList { double transition = this.linear(subIterator); - KeyFrame.PathMethod method; + CombinedPathMethod method; + + KeyFrame.PathMethod + currentMethod = current.pathMethod(), + nextMethod = next.pathMethod(); // 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(currentMethod.equals(KeyFrame.PathMethod.EASE_OUT) || currentMethod.equals(KeyFrame.PathMethod.EASE_IN_AND_OUT)) + currentMethod = KeyFrame.PathMethod.EASE_OUT; - 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(); + if(nextMethod.equals(KeyFrame.PathMethod.EASE_OUT) || nextMethod.equals(KeyFrame.PathMethod.EASE_IN_AND_OUT)) + nextMethod = KeyFrame.PathMethod.EASE_OUT; - // Else-case would be linear, which doesn't change anything + method = switch (currentMethod) { + case LINEAR -> switch (nextMethod) { + case LINEAR -> CombinedPathMethod.LINEAR; + case EASE_IN -> CombinedPathMethod.LINEAR_EASE_IN; + case EASE_OUT -> CombinedPathMethod.LINEAR_EASE_OUT; + default -> throw new IllegalStateException("Unexpected value: " + nextMethod); + }; - if(method.equals(KeyFrame.PathMethod.EASE_IN)) - transition = this.easeIn(subIterator); + case EASE_IN -> switch (nextMethod) { + case LINEAR -> CombinedPathMethod.EASE_IN_LINEAR; + case EASE_IN -> CombinedPathMethod.EASE_IN; + case EASE_OUT -> CombinedPathMethod.EASE_OUT_LINEAR; + default -> throw new IllegalStateException("Unexpected value: " + nextMethod); + }; - else if(method.equals(KeyFrame.PathMethod.EASE_OUT)) - transition = this.easeOut(subIterator); + case EASE_OUT -> switch (nextMethod) { + case LINEAR -> CombinedPathMethod.EASE_OUT_LINEAR; + case EASE_IN -> CombinedPathMethod.EASE_OUT_AND_IN; + case EASE_OUT -> CombinedPathMethod.EASE_OUT; + default -> throw new IllegalStateException("Unexpected value: " + nextMethod); + }; + default -> throw new IllegalStateException("Unexpected value: " + currentMethod); + }; - return inBetween(current, next, transition, method); + boolean overHalf = transition > 0.5; + + if(overHalf) + transition = 2 * transition - 1; + + transition = switch (method) { + case LINEAR -> transition; + + case LINEAR_EASE_IN -> overHalf ? easeIn(subIterator) : transition; + + case EASE_IN -> easeIn(subIterator); + + case EASE_IN_LINEAR -> overHalf ? transition : easeIn(subIterator); + + case LINEAR_EASE_OUT -> overHalf ? easeOut(subIterator) : transition; + + case EASE_OUT -> easeOut(subIterator); + + case EASE_OUT_LINEAR -> overHalf ? transition : easeOut(subIterator); + + case EASE_OUT_AND_IN -> overHalf ? easeOut(subIterator) : easeIn(subIterator); + + }; + + System.out.println(method + " " + transition + " - linear: " + linear(subIterator)); + + return inBetween(current, next, transition, overHalf); + + +// 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); } @@ -122,6 +189,19 @@ public class AnimationPath extends ArrayList { return this.get(i); } + /** + * Get previous keyframe according to current path iterator, not depending on the current frame + * @return previous keyframe in order, not depending on the current frame + */ + public KeyFrame getPreviousInOrder() { + int i = this.animationIterator - 1; + + if(i < 0) + i++; + + return this.get(i); + } + /** * @return true if there are any more frames coming */ @@ -171,14 +251,36 @@ public class AnimationPath extends ArrayList { return new Point(point.x - subtrahend.x, point.y - subtrahend.y); } + /** + * Calculate point between both points + * @param p1 first point + * @param p2 second point + * @return point in the middle between both points + */ + private static Point middle(Point p1, Point p2) { + return new Point( + multiply( + add(p1, p2), + 0.5 + ) + ); + } + /** * Find in-between with given scalar * @param kf1 first frame * @param kf2 next frame * @param scalar factor (ideally between 0 and 1, representing 0% transition to 100% transition) + * @param overHalf if this inbetween is over halfway through the transition process * @return in-between frame */ - private static KeyFrame inBetween(KeyFrame kf1, KeyFrame kf2, double scalar, KeyFrame.PathMethod method) { + private static KeyFrame inBetween(KeyFrame kf1, KeyFrame kf2, double scalar, boolean overHalf) { + // create halfway marked point + if(overHalf) + kf1.position().setLocation( + middle(kf1.position(), kf2.position()) + ); + // position Point difference = subtract(kf2.position(), kf1.position()); @@ -196,7 +298,7 @@ public class AnimationPath extends ArrayList { height = Math.toIntExact(Math.round(kf1.height() + dH * scalar)); - return new KeyFrame(pos, width, height, method); + return new KeyFrame(pos, width, height, kf2.pathMethod()); } @Override @@ -208,11 +310,26 @@ public class AnimationPath extends ArrayList { return (double) subIterator/2 / this.inbetweens; } - private double easeIn(int subIterator) { - return this.linear(subIterator/2) * this.linear(subIterator/2); + 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)); } + + /** + * Enumerator purely to describe two combined PathMethods, like a 2-dimensional PathMethod enum + */ + private enum CombinedPathMethod { + LINEAR, + LINEAR_EASE_IN, + EASE_IN, + EASE_IN_LINEAR, + LINEAR_EASE_OUT, + EASE_OUT, + EASE_OUT_LINEAR, + EASE_OUT_AND_IN; + + } } diff --git a/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java b/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java index d365a83..4b5519c 100644 --- a/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java +++ b/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java @@ -39,7 +39,7 @@ public class AnimationTests { frame.add(animatedPanel); - animatedPanel.play(10, true); + animatedPanel.play(5, true); frame.setVisible(true); From 2d47dfc28c8fe45a47c12357a2755d86702ff34e Mon Sep 17 00:00:00 2001 From: Tinglyyy Date: Tue, 10 Feb 2026 22:30:57 +0100 Subject: [PATCH 3/3] Added javadoc --- .../oacswing/animated/AnimationPath.java | 68 +++++++------------ .../oacswing/animated/KeyFrame.java | 57 ++++++++++++++-- 2 files changed, 77 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java index fe5b520..e2e9829 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java +++ b/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java @@ -102,16 +102,18 @@ public class AnimationPath extends ArrayList { case LINEAR -> switch (nextMethod) { case LINEAR -> CombinedPathMethod.LINEAR; case EASE_IN -> CombinedPathMethod.LINEAR_EASE_IN; - case EASE_OUT -> CombinedPathMethod.LINEAR_EASE_OUT; + // Removed this case, as ease-out doesn't make sense right before a keyframe +// case EASE_OUT -> CombinedPathMethod.LINEAR_EASE_OUT; default -> throw new IllegalStateException("Unexpected value: " + nextMethod); }; - case EASE_IN -> switch (nextMethod) { - case LINEAR -> CombinedPathMethod.EASE_IN_LINEAR; - case EASE_IN -> CombinedPathMethod.EASE_IN; - case EASE_OUT -> CombinedPathMethod.EASE_OUT_LINEAR; - default -> throw new IllegalStateException("Unexpected value: " + nextMethod); - }; + // Removed this case, as ease-in doesn't make sense right after a keyframe +// case EASE_IN -> switch (nextMethod) { +// case LINEAR -> CombinedPathMethod.EASE_IN_LINEAR; +// case EASE_IN -> CombinedPathMethod.EASE_IN; +// case EASE_OUT -> CombinedPathMethod.EASE_OUT_LINEAR; +// default -> throw new IllegalStateException("Unexpected value: " + nextMethod); +// }; case EASE_OUT -> switch (nextMethod) { case LINEAR -> CombinedPathMethod.EASE_OUT_LINEAR; @@ -122,57 +124,37 @@ public class AnimationPath extends ArrayList { default -> throw new IllegalStateException("Unexpected value: " + currentMethod); }; - boolean overHalf = transition > 0.5; + double threshold = Math.min(current.transition(), next.transition()); - if(overHalf) + boolean thresholdReached; + + if(current.transition() < next.transition()) + thresholdReached = transition > threshold; + else + thresholdReached = transition <= next.transition(); + + if(thresholdReached) transition = 2 * transition - 1; transition = switch (method) { case LINEAR -> transition; - case LINEAR_EASE_IN -> overHalf ? easeIn(subIterator) : transition; + case LINEAR_EASE_IN -> thresholdReached ? easeIn(subIterator) : transition; - case EASE_IN -> easeIn(subIterator); - - case EASE_IN_LINEAR -> overHalf ? transition : easeIn(subIterator); - - case LINEAR_EASE_OUT -> overHalf ? easeOut(subIterator) : transition; +// case EASE_IN -> easeIn(subIterator); case EASE_OUT -> easeOut(subIterator); - case EASE_OUT_LINEAR -> overHalf ? transition : easeOut(subIterator); + case EASE_OUT_LINEAR -> thresholdReached ? transition : easeOut(subIterator); - case EASE_OUT_AND_IN -> overHalf ? easeOut(subIterator) : easeIn(subIterator); + case EASE_OUT_AND_IN -> thresholdReached ? easeOut(subIterator) : easeIn(subIterator); }; System.out.println(method + " " + transition + " - linear: " + linear(subIterator)); - return inBetween(current, next, transition, overHalf); + return inBetween(current, next, transition, thresholdReached); - -// 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); } @@ -324,9 +306,7 @@ public class AnimationPath extends ArrayList { private enum CombinedPathMethod { LINEAR, LINEAR_EASE_IN, - EASE_IN, - EASE_IN_LINEAR, - LINEAR_EASE_OUT, +// EASE_IN, EASE_OUT, EASE_OUT_LINEAR, EASE_OUT_AND_IN; diff --git a/src/main/java/org/openautonomousconnection/oacswing/animated/KeyFrame.java b/src/main/java/org/openautonomousconnection/oacswing/animated/KeyFrame.java index 2f3f325..f9c9332 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/animated/KeyFrame.java +++ b/src/main/java/org/openautonomousconnection/oacswing/animated/KeyFrame.java @@ -7,18 +7,67 @@ package org.openautonomousconnection.oacswing.animated; import javax.swing.*; import java.awt.*; -public record KeyFrame(Point position, int width, int height, PathMethod pathMethod) { +/** + * Record that contains certain vectors and factors for animating in {@link AnimatedComponent} + * @param position positional vector (where is the object at this moment?) + * @param width first transformational vector (how wide is the object at this moment) + * @param height second transformational vector (how tall is the object at this moment) + * @param pathMethod how the path from this KeyFrame to the next should look like + * @param transition adds to pathMethod in how much of the path should be defined by this decision (default 0% for linear and 50% for all other methods) + */ +public record KeyFrame(Point position, int width, int height, PathMethod pathMethod, double transition) { - public KeyFrame(Point position, int width, int height) { - this(position, width, height, PathMethod.LINEAR); + /** + * Record that contains certain vectors and factors for animating in {@link AnimatedComponent}. + * @param position positional vector (where is the object at this moment?) + * @param width first transformational vector (how wide is the object at this moment) + * @param height second transformational vector (how tall is the object at this moment) + * @param pathMethod how the path from this KeyFrame to the next should look like + */ + public KeyFrame(Point position, int width, int height, PathMethod pathMethod) { + this(position, width, height, pathMethod, pathMethod.equals(PathMethod.LINEAR) ? 0 : 0.5); } + /** + * Record that contains certain vectors and factors for animating in {@link AnimatedComponent}. + * Defaults pathMethod to linear and transition percentage to 0% + * @param position positional vector (where is the object at this moment?) + * @param width first transformational vector (how wide is the object at this moment) + * @param height second transformational vector (how tall is the object at this moment) + */ + public KeyFrame(Point position, int width, int height) { + this(position, width, height, PathMethod.LINEAR, 0); + } + + /** + * Record that contains certain vectors and factors for animating in {@link AnimatedComponent}. + * @param component component that contains necessary vectors + * @param pathMethod how the path from this KeyFrame to the next should look like + * @param transition adds to pathMethod in how much of the path should be defined by this decision (default 50%) + */ + public KeyFrame(JComponent component, PathMethod pathMethod, double transition) { + this( + new Point(component.getX(), component.getY()), + component.getBounds().width, component.getBounds().height, pathMethod, transition); + } + + /** + * Record that contains certain vectors and factors for animating in {@link AnimatedComponent}. + * @param component component that contains necessary vectors + * @param pathMethod how the path from this KeyFrame to the next should look like + */ public KeyFrame(JComponent component, PathMethod pathMethod) { this( new Point(component.getX(), component.getY()), - component.getBounds().width, component.getBounds().height, pathMethod); + component.getBounds().width, component.getBounds().height, pathMethod, + pathMethod.equals(PathMethod.LINEAR) ? 0 : 0.5); } + /** + * Record that contains certain vectors and factors for animating in {@link AnimatedComponent}. + * Defaults pathMethod to linear and transition percentage to 50% + * @param component component that contains necessary vectors + */ public KeyFrame(JComponent component) { this(component, PathMethod.LINEAR); }