Added Animation
This commit is contained in:
14
pom.xml
14
pom.xml
@@ -13,5 +13,19 @@
|
|||||||
<maven.compiler.target>23</maven.compiler.target>
|
<maven.compiler.target>23</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.38</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>6.0.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.openautonomousconnection.oacswing;
|
||||||
|
|
||||||
|
public interface JAnimatedComponent {
|
||||||
|
void play(double speed, boolean loop);
|
||||||
|
|
||||||
|
default void play(double speed) {
|
||||||
|
this.play(speed, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void play() {
|
||||||
|
this.play(1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.openautonomousconnection.oacswing;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class JTitledComponent<C extends JComponent> extends JPanel {
|
||||||
|
@Getter @Setter
|
||||||
|
private JLabel title;
|
||||||
|
|
||||||
|
@Getter @Setter
|
||||||
|
private C component;
|
||||||
|
|
||||||
|
public JTitledComponent(String title, C component) {
|
||||||
|
this.setLayout(new FlowLayout());
|
||||||
|
|
||||||
|
this.title = new JLabel(title);
|
||||||
|
|
||||||
|
this.component = component;
|
||||||
|
|
||||||
|
this.add(this.title);
|
||||||
|
this.add(this.component);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package org.openautonomousconnection.oacswing.animated;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class AnimationPath extends ArrayList<KeyFrame> {
|
||||||
|
@Getter @Setter
|
||||||
|
private int inbetweens;
|
||||||
|
|
||||||
|
private int animationIterator = 0;
|
||||||
|
|
||||||
|
private int subFrameIterator = 0;
|
||||||
|
|
||||||
|
public AnimationPath(int inbetweens, Collection<KeyFrame> keyFrames) {
|
||||||
|
this.addAll(keyFrames);
|
||||||
|
this.inbetweens = inbetweens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnimationPath(int inbetweens, KeyFrame... keyFrames) {
|
||||||
|
this.addAll(Arrays.stream(keyFrames).toList());
|
||||||
|
this.inbetweens = inbetweens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnimationPath(int inbetweens) {
|
||||||
|
this.inbetweens = inbetweens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get next keyframe according to current path iterator, depending on the current frame
|
||||||
|
* @return next keyframe in order, depending on the current frame
|
||||||
|
*/
|
||||||
|
public KeyFrame getNext() {
|
||||||
|
if(this.subFrameIterator >= this.inbetweens) {
|
||||||
|
this.subFrameIterator = 0;
|
||||||
|
this.animationIterator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.animationIterator >= this.size())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
this.subFrameIterator++;
|
||||||
|
|
||||||
|
KeyFrame current = this.get(this.animationIterator);
|
||||||
|
|
||||||
|
KeyFrame next = this.getNextInOrder();
|
||||||
|
|
||||||
|
// How far the transition should be finished
|
||||||
|
|
||||||
|
double transition = (double) this.subFrameIterator / this.inbetweens;
|
||||||
|
|
||||||
|
return inBetween(current, next, transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get next keyframe according to current path iterator, not depending on the current frame
|
||||||
|
* @return next keyframe in order, not depending on the current frame
|
||||||
|
*/
|
||||||
|
public KeyFrame getNextInOrder() {
|
||||||
|
int i = this.animationIterator + 1;
|
||||||
|
|
||||||
|
if(i >= this.size())
|
||||||
|
i--;
|
||||||
|
|
||||||
|
return this.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset animation path iterator to start
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
this.subFrameIterator = 0;
|
||||||
|
this.animationIterator = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method needed to get in-betweens
|
||||||
|
* @param point point to multiply
|
||||||
|
* @param scalar scalar to multiply with
|
||||||
|
* @return scalar product point
|
||||||
|
*/
|
||||||
|
private static Point multiply(Point point, double scalar) {
|
||||||
|
int x = Math.toIntExact(Math.round(point.x * scalar));
|
||||||
|
int y = Math.toIntExact(Math.round(point.y * scalar));
|
||||||
|
|
||||||
|
return new Point(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add two points together; also needed for in-betweens
|
||||||
|
* @param point augend
|
||||||
|
* @param addend addend
|
||||||
|
* @return sum of both points
|
||||||
|
*/
|
||||||
|
private static Point add(Point point, Point addend) {
|
||||||
|
return new Point(point.x + addend.x, point.y + addend.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts one point from another; also needed for in-betweens
|
||||||
|
* @param point minuend
|
||||||
|
* @param subtrahend subtrahend
|
||||||
|
* @return sum of both points
|
||||||
|
*/
|
||||||
|
private static Point subtract(Point point, Point subtrahend) {
|
||||||
|
return new Point(point.x - subtrahend.x, point.y - subtrahend.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
* @return in-between frame
|
||||||
|
*/
|
||||||
|
private static KeyFrame inBetween(KeyFrame kf1, KeyFrame kf2, double scalar) {
|
||||||
|
// position
|
||||||
|
|
||||||
|
Point difference = subtract(kf2.position(), kf1.position());
|
||||||
|
|
||||||
|
Point pos = add(kf1.position(), multiply(difference, scalar));
|
||||||
|
|
||||||
|
// scale
|
||||||
|
|
||||||
|
int width, height;
|
||||||
|
|
||||||
|
int dW = kf2.width() - kf1.width();
|
||||||
|
int dH = kf2.height() - kf1.height();
|
||||||
|
|
||||||
|
width = Math.toIntExact(Math.round(kf1.width() + dW * scalar));
|
||||||
|
|
||||||
|
height = Math.toIntExact(Math.round(kf1.height() + dH * scalar));
|
||||||
|
|
||||||
|
return new KeyFrame(pos, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package org.openautonomousconnection.oacswing.animated;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.openautonomousconnection.oacswing.JAnimatedComponent;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class JAnimatedPanel extends JPanel implements JAnimatedComponent {
|
||||||
|
@Getter @Setter
|
||||||
|
private AnimationPath animationPath;
|
||||||
|
|
||||||
|
private Timer currentRun = null;
|
||||||
|
|
||||||
|
public JAnimatedPanel(AnimationPath animationPath) {
|
||||||
|
this.animationPath = animationPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play(double speed, boolean loop) {
|
||||||
|
|
||||||
|
AtomicInteger ticksPassed = new AtomicInteger();
|
||||||
|
|
||||||
|
this.currentRun = new Timer(0, e -> {
|
||||||
|
|
||||||
|
if(ticksPassed.get() * speed / (100) < 1) {
|
||||||
|
ticksPassed.addAndGet(animationPath.getInbetweens());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyFrame next = animationPath.getNext();
|
||||||
|
|
||||||
|
if(next == null) {
|
||||||
|
((Timer) e.getSource()).stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBounds(next.position().x, next.position().y, next.width(), next.height());
|
||||||
|
|
||||||
|
ticksPassed.set(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.currentRun.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if(this.currentRun != null)
|
||||||
|
if(this.currentRun.isRunning())
|
||||||
|
this.currentRun.stop();
|
||||||
|
|
||||||
|
this.animationPath.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "KeyFrame{" +
|
||||||
|
"position=" + position +
|
||||||
|
", width=" + width +
|
||||||
|
", height=" + height +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.openautonomousconnection.oacswing.test;
|
||||||
|
|
||||||
|
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 javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class AnimationTests {
|
||||||
|
@Test
|
||||||
|
public synchronized void testSimpleAnimatedPanel() throws InterruptedException {
|
||||||
|
JFrame frame = TestUtils.mockFrame();
|
||||||
|
|
||||||
|
AnimationPath animationPath = new AnimationPath(50);
|
||||||
|
|
||||||
|
animationPath.add(new KeyFrame(new Point(0, 0), 400, 400));
|
||||||
|
animationPath.add(new KeyFrame(new Point(0, 0), 800, 600));
|
||||||
|
animationPath.add(new KeyFrame(new Point(0, 0), 400, 400));
|
||||||
|
|
||||||
|
JAnimatedPanel animatedPanel = new JAnimatedPanel(animationPath);
|
||||||
|
|
||||||
|
animatedPanel.setBackground(Color.BLACK);
|
||||||
|
|
||||||
|
frame.add(animatedPanel);
|
||||||
|
|
||||||
|
animatedPanel.play(0.1);
|
||||||
|
|
||||||
|
frame.setVisible(true);
|
||||||
|
|
||||||
|
wait(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.openautonomousconnection.oacswing.test;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class TestUtils {
|
||||||
|
public static JFrame mockFrame() {
|
||||||
|
JFrame frame = new JFrame();
|
||||||
|
|
||||||
|
frame.setBounds(new Rectangle(800, 600));
|
||||||
|
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user