diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6b7ab68..69cb1ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## Added
- Interface `Audio` extrahiert, mit Basisfunktionen von `Sound` und `Music`.
- Klasse `Mixer` steuert mehrere Audio-Objekte gleichzeitig.
+- Klasse `tasks.RateLimitedTask`, `tasks.FramerateLimitedTask`, `tasks.FrameSynchronizedTask` und `tasks.DelayedTask`.
## Changed
- Neue Package-Struktur:
diff --git a/src/main/java/schule/ngb/zm/Zeichenmaschine.java b/src/main/java/schule/ngb/zm/Zeichenmaschine.java
index 0979b6a..8cd16cf 100644
--- a/src/main/java/schule/ngb/zm/Zeichenmaschine.java
+++ b/src/main/java/schule/ngb/zm/Zeichenmaschine.java
@@ -1281,10 +1281,37 @@ public class Zeichenmaschine extends Constants {
// Zeichenthread
////
+ /**
+ * Globaler Monitor, der einmal pro Frame vom Zeichenthread freigegeben
+ * wird. Andere Threads können {@link Object#wait()} auf dem Monitor
+ * aufrufen, um sich mit dem Zeichenthread zu synchronisieren. Der
+ * {@code wait()} Aufruf sollte sich zur Sicherheit in einer Schleife
+ * befinden, die prüft, ob sich der Aktuelle {@link #tick} erhöht hat.
+ *
+ * int lastTick = Constants.tick;
+ *
+ * // Do some work
+ *
+ * while( lastTick >= Constants.tick ) {
+ * synchronized( Zeichenmaschine.globalSyncLock ) {
+ * try {
+ * Zeichenmaschine.globalSyncLock.wait();
+ * } catch( InterruptedException ex ) {}
+ * }
+ * }
+ * // Next frame has started
+ *
+ *
+ * Die {@link schule.ngb.zm.tasks.FrameSynchronizedTask} implementiert eine
+ * {@link schule.ngb.zm.tasks.Task}, die sich automatisch auf diese Wiese
+ * mit dem Zeichenthread synchronisiert.
+ */
+ public static final Object globalSyncLock = new Object[0];
+
class Zeichenthread extends Thread {
public Zeichenthread() {
- super(Zeichenthread.class.getSimpleName());
+ super(APP_NAME);
//super(APP_NAME + " " + APP_VERSION);
}
@@ -1333,6 +1360,9 @@ public class Zeichenmaschine extends Constants {
}
runTasks();
+ synchronized( globalSyncLock ) {
+ globalSyncLock.notifyAll();
+ }
// delta time in ns
long afterTime = System.nanoTime();
@@ -1418,7 +1448,8 @@ public class Zeichenmaschine extends Constants {
}
- class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener{
+ class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener {
+
@Override
public void mouseClicked( MouseEvent e ) {
enqueueEvent(e);
diff --git a/src/main/java/schule/ngb/zm/anim/Animation.java b/src/main/java/schule/ngb/zm/anim/Animation.java
new file mode 100644
index 0000000..793f352
--- /dev/null
+++ b/src/main/java/schule/ngb/zm/anim/Animation.java
@@ -0,0 +1,26 @@
+package schule.ngb.zm.anim;
+
+import schule.ngb.zm.events.EventDispatcher;
+
+public class Animation {
+
+ EventDispatcher eventDispatcher;
+
+ private EventDispatcher initializeEventDispatcher() {
+ if( eventDispatcher == null ) {
+ eventDispatcher = new EventDispatcher<>();
+ eventDispatcher.registerEventType("start", ( a, l ) -> l.animationStarted(a));
+ eventDispatcher.registerEventType("stop", ( a, l ) -> l.animationStopped(a));
+ }
+ return eventDispatcher;
+ }
+
+ public void addListener( AnimationListener listener ) {
+ initializeEventDispatcher().addListener(listener);
+ }
+
+ public void removeListener( AnimationListener listener ) {
+ initializeEventDispatcher().removeListener(listener);
+ }
+
+}
diff --git a/src/main/java/schule/ngb/zm/anim/AnimationListener.java b/src/main/java/schule/ngb/zm/anim/AnimationListener.java
new file mode 100644
index 0000000..13e62da
--- /dev/null
+++ b/src/main/java/schule/ngb/zm/anim/AnimationListener.java
@@ -0,0 +1,11 @@
+package schule.ngb.zm.anim;
+
+import schule.ngb.zm.events.Listener;
+
+public interface AnimationListener extends Listener {
+
+ void animationStarted( Animation anim );
+
+ void animationStopped( Animation anim );
+
+}
diff --git a/src/main/java/schule/ngb/zm/anim/Animations.java b/src/main/java/schule/ngb/zm/anim/Animations.java
new file mode 100644
index 0000000..d9f63a2
--- /dev/null
+++ b/src/main/java/schule/ngb/zm/anim/Animations.java
@@ -0,0 +1,200 @@
+package schule.ngb.zm.anim;
+
+import schule.ngb.zm.Color;
+import schule.ngb.zm.Constants;
+import schule.ngb.zm.Vector;
+import schule.ngb.zm.tasks.FrameSynchronizedTask;
+import schule.ngb.zm.tasks.TaskRunner;
+import schule.ngb.zm.util.Log;
+import schule.ngb.zm.util.Validator;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.Future;
+import java.util.function.*;
+
+public class Animations {
+
+ public static final Future animateProperty( String propName, T target, double to, int runtime, DoubleUnaryOperator easing ) {
+ double from;
+ try {
+ from = callGetter(target, propName, double.class);
+ } catch( InvocationTargetException | NoSuchMethodException |
+ IllegalAccessException ex ) {
+ throw new RuntimeException("Can't access property getter for animation.", ex);
+ }
+
+ Method propSetter;
+ try {
+ propSetter = findSetter(target, propName, double.class);
+ } catch( NoSuchMethodException ex ) {
+ throw new RuntimeException("Can't find property setter for animation.", ex);
+ }
+
+ return animateProperty(target, from, to, runtime, easing, ( d ) -> {
+ try {
+ propSetter.invoke(target, d);
+ } catch( IllegalAccessException | InvocationTargetException e ) {
+ throw new RuntimeException("Can't access property setter for animation.", e);
+ }
+ });
+ }
+
+ public static final Future animateProperty( String propName, T target, Color to, int runtime, DoubleUnaryOperator easing ) {
+ Color from;
+ try {
+ from = callGetter(target, propName, Color.class);
+ } catch( InvocationTargetException | NoSuchMethodException |
+ IllegalAccessException ex ) {
+ throw new RuntimeException("Can't access property getter for animation.", ex);
+ }
+
+ Method propSetter;
+ try {
+ propSetter = findSetter(target, propName, Color.class);
+ } catch( NoSuchMethodException ex ) {
+ throw new RuntimeException("Can't find property setter for animation.", ex);
+ }
+
+ return animateProperty(target, from, to, runtime, easing, ( d ) -> {
+ try {
+ propSetter.invoke(target, d);
+ } catch( IllegalAccessException | InvocationTargetException e ) {
+ throw new RuntimeException("Can't access property setter for animation.", e);
+ }
+ });
+ }
+
+ public static final Future animateProperty( String propName, T target, Vector to, int runtime, DoubleUnaryOperator easing ) {
+ Vector from;
+ try {
+ from = callGetter(target, propName, Vector.class);
+ } catch( InvocationTargetException | NoSuchMethodException |
+ IllegalAccessException ex ) {
+ throw new RuntimeException("Can't access property getter for animation.", ex);
+ }
+
+ Method propSetter;
+ try {
+ propSetter = findSetter(target, propName, Vector.class);
+ } catch( NoSuchMethodException ex ) {
+ throw new RuntimeException("Can't find property setter for animation.", ex);
+ }
+
+ return animateProperty(target, from, to, runtime, easing, ( d ) -> {
+ try {
+ propSetter.invoke(target, d);
+ } catch( IllegalAccessException | InvocationTargetException e ) {
+ throw new RuntimeException("Can't access property setter for animation.", e);
+ }
+ });
+ }
+
+ private static final R callGetter( T target, String propName, Class propType ) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ String getterName = makeMethodName("get", propName);
+ Method getter = target.getClass().getMethod(getterName);
+ if( getter != null && getter.getReturnType().equals(propType) ) {
+ return (R) getter.invoke(target);
+ } else {
+ throw new NoSuchMethodException(String.format("No getter for property <%s> found.", propName));
+ }
+ }
+
+ private static final Method findSetter( T target, String propName, Class propType ) throws NoSuchMethodException {
+ String setterName = makeMethodName("set", propName);
+ Method setter = target.getClass().getMethod(setterName, propType);
+ if( setter != null && setter.getReturnType().equals(void.class) && setter.getParameterCount() == 1 ) {
+ return setter;
+ } else {
+ throw new NoSuchMethodException(String.format("No setter for property <%s> found.", propName));
+ }
+ }
+
+ private static final String makeMethodName( String prefix, String propName ) {
+ String firstChar = propName.substring(0, 1).toUpperCase();
+ String tail = "";
+ if( propName.length() > 1 ) {
+ tail = propName.substring(1);
+ }
+ return prefix + firstChar + tail;
+ }
+
+ public static final Future animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
+ Validator.requireNotNull(target);
+ Validator.requireNotNull(propSetter);
+ return animate(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
+ }
+
+ public static final Future animateProperty( T target, final Color from, final Color to, int runtime, DoubleUnaryOperator easing, Consumer propSetter ) {
+ return animate(target, runtime, easing, ( e ) -> propSetter.accept(Color.interpolate(from, to, e)));
+ }
+
+
+ public static final Future animateProperty( T target, final Vector from, final Vector to, int runtime, DoubleUnaryOperator easing, Consumer propSetter ) {
+ return animate(target, runtime, easing, ( e ) -> propSetter.accept(Vector.interpolate(from, to, e)));
+ }
+
+ public static final Future animateProperty( T target, R from, R to, int runtime, DoubleUnaryOperator easing, DoubleFunction interpolator, Consumer propSetter ) {
+ return animate(target, runtime, easing, interpolator, ( t, r ) -> propSetter.accept(r));
+ }
+
+
+ public static final Future animate( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction interpolator, BiConsumer applicator ) {
+ return animate(target, runtime, easing, ( e ) -> applicator.accept(target, interpolator.apply(e)));
+ }
+
+ public static final Future animate( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
+ /*final long starttime = System.currentTimeMillis();
+ return TaskRunner.run(() -> {
+ double t = 0.0;
+ do {
+ // One animation step for t in [0,1]
+ stepper.accept(easing.applyAsDouble(t));
+ try {
+ Thread.sleep(1000 / Constants.framesPerSecond);
+ } catch( InterruptedException ex ) {
+ }
+ t = (double) (System.currentTimeMillis() - starttime) / (double) runtime;
+ } while( t < 1.0 );
+ stepper.accept(easing.applyAsDouble(1.0));
+ }, target);*/
+ return TaskRunner.run(new FrameSynchronizedTask() {
+ double t = 0.0;
+ final long starttime = System.currentTimeMillis();
+ @Override
+ public void update( double delta ) {
+ // One animation step for t in [0,1]
+ stepper.accept(easing.applyAsDouble(t));
+ t = (double) (System.currentTimeMillis() - starttime) / (double) runtime;
+ running = (t <= 1.0);
+ }
+
+ @Override
+ protected void finish() {
+ stepper.accept(easing.applyAsDouble(1.0));
+ }
+ }, target);
+ }
+
+ public static final Future animate( T target, int runtime, Animator animator ) {
+ return animate(
+ target, runtime,
+ animator::easing,
+ animator::interpolator,
+ animator::applicator
+ );
+ }
+
+ /*public static Future> animate( Animation animation ) {
+ animation.start();
+ return null;
+ }
+
+ public static Future> animate( Animation animation, DoubleUnaryOperator easing ) {
+ animation.start(easing);
+ return null;
+ }*/
+
+ public static final Log LOG = Log.getLogger(Animations.class);
+
+}
diff --git a/src/main/java/schule/ngb/zm/anim/Animator.java b/src/main/java/schule/ngb/zm/anim/Animator.java
new file mode 100644
index 0000000..951279d
--- /dev/null
+++ b/src/main/java/schule/ngb/zm/anim/Animator.java
@@ -0,0 +1,11 @@
+package schule.ngb.zm.anim;
+
+public interface Animator {
+
+ double easing(double t);
+
+ R interpolator(double e);
+
+ void applicator(T target, R value);
+
+}
diff --git a/src/main/java/schule/ngb/zm/anim/Easing.java b/src/main/java/schule/ngb/zm/anim/Easing.java
new file mode 100644
index 0000000..7679e23
--- /dev/null
+++ b/src/main/java/schule/ngb/zm/anim/Easing.java
@@ -0,0 +1,320 @@
+package schule.ngb.zm.anim;
+
+import java.util.function.DoubleUnaryOperator;
+
+/**
+ * @see Cheat Sheet für Easing-Funktionen
+ */
+public class Easing {
+
+ public static final DoubleUnaryOperator DEFAULT_EASING = Easing::smooth;
+
+ public static final DoubleUnaryOperator thereAndBack() {
+ return Easing::thereAndBack;
+ }
+
+ public static final DoubleUnaryOperator thereAndBack( final DoubleUnaryOperator baseEasing ) {
+ return ( t ) -> thereAndBack(t, baseEasing);
+ }
+
+ public static final double thereAndBack( double t ) {
+ return thereAndBack(t, DEFAULT_EASING);
+ }
+
+ public static final double thereAndBack( double t, DoubleUnaryOperator baseEasing ) {
+ if( t < 0.5 ) {
+ return baseEasing.applyAsDouble(2 * t);
+ } else {
+ return baseEasing.applyAsDouble(2 - 2 * t);
+ }
+ }
+
+ public static final DoubleUnaryOperator halfAndHalf( final DoubleUnaryOperator firstEasing, final DoubleUnaryOperator secondEasing ) {
+ return ( t ) -> halfAndHalf(t, firstEasing, secondEasing);
+ }
+
+ public static final DoubleUnaryOperator halfAndHalf( final DoubleUnaryOperator firstEasing, final DoubleUnaryOperator secondEasing, final double split ) {
+ return ( t ) -> halfAndHalf(t, firstEasing, secondEasing, split);
+ }
+
+ public static final double halfAndHalf( double t, DoubleUnaryOperator firstEasing, DoubleUnaryOperator secondEasing ) {
+ return halfAndHalf(t, firstEasing, secondEasing, 0.5);
+ }
+
+ public static final double halfAndHalf( double t, DoubleUnaryOperator firstEasing, DoubleUnaryOperator secondEasing, double split ) {
+ if( t < split ) {
+ return firstEasing.applyAsDouble(2 * t);
+ } else {
+ return secondEasing.applyAsDouble(1 - 2 * t);
+ }
+ }
+
+
+ /*
+ * Functions taken from easings.net
+ */
+
+ public static final DoubleUnaryOperator linear() {
+ return Easing::linear;
+ }
+
+ public static final double linear( double t ) {
+ return t;
+ }
+
+ public static final DoubleUnaryOperator quadIn() {
+ return Easing::quadIn;
+ }
+
+ public static final double quadIn( double t ) {
+ return t * t;
+ }
+
+ public static final DoubleUnaryOperator quadOut() {
+ return Easing::quadOut;
+ }
+
+ public static final double quadOut( double t ) {
+ return 1 - (1 - t) * (1 - t);
+ }
+
+ public static final DoubleUnaryOperator quadInOut() {
+ return Easing::quadInOut;
+ }
+
+ public static final double quadInOut( double t ) {
+ return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
+ }
+
+ public static final DoubleUnaryOperator cubicIn() {
+ return Easing::cubicIn;
+ }
+
+ public static final double cubicIn( double t ) {
+ return t * t * t;
+ }
+
+ public static final DoubleUnaryOperator cubicOut() {
+ return Easing::cubicOut;
+ }
+
+ public static final double cubicOut( double t ) {
+ return 1 - Math.pow(1 - t, 3);
+ }
+
+ public static final DoubleUnaryOperator cubicInOut() {
+ return Easing::cubicInOut;
+ }
+
+ public static final double cubicInOut( double t ) {
+ return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
+ }
+
+ public static final DoubleUnaryOperator sineIn() {
+ return Easing::sineIn;
+ }
+
+ public static final double sineIn( double t ) {
+ return 1 - Math.cos((t * Math.PI) / 2);
+ }
+
+ public static final DoubleUnaryOperator sineOut() {
+ return Easing::sineOut;
+ }
+
+ public static final double sineOut( double t ) {
+ return Math.sin((t * Math.PI) / 2);
+ }
+
+ public static final DoubleUnaryOperator sineInOut() {
+ return Easing::sineInOut;
+ }
+
+ public static final double sineInOut( double t ) {
+ return -(Math.cos(Math.PI * t) - 1) / 2;
+ }
+
+ public static final DoubleUnaryOperator elasticIn() {
+ return Easing::elasticIn;
+ }
+
+ public static final double elasticIn( double t ) {
+ double c4 = (2 * Math.PI) / 3;
+
+ return t == 0
+ ? 0
+ : t == 1
+ ? 1
+ : -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
+ }
+
+ public static final DoubleUnaryOperator elasticOut() {
+ return Easing::elasticOut;
+ }
+
+ public static final double elasticOut( double t ) {
+ double c4 = (2 * Math.PI) / 3;
+
+ return t == 0
+ ? 0
+ : t == 1
+ ? 1
+ : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
+ }
+
+ public static final DoubleUnaryOperator elasticInOut() {
+ return Easing::elasticInOut;
+ }
+
+ public static final double elasticInOut( double t ) {
+ double c5 = (2 * Math.PI) / 4.5;
+
+ return t == 0
+ ? 0
+ : t == 1
+ ? 1
+ : t < 0.5
+ ? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2
+ : (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1;
+ }
+
+ public static final DoubleUnaryOperator bounceIn() {
+ return Easing::bounceIn;
+ }
+
+ public static final double bounceIn( double t ) {
+ return 1 - bounceOut(1 - t);
+ }
+
+ public static final DoubleUnaryOperator bounceOut() {
+ return Easing::bounceOut;
+ }
+
+ public static final double bounceOut( double t ) {
+ double n1 = 7.5625;
+ double d1 = 2.75;
+
+ if( t < 1.0 / d1 ) {
+ return n1 * t * t;
+ } else if( t < 2.0 / d1 ) {
+ return n1 * (t -= 1.5 / d1) * t + 0.75;
+ } else if( t < 2.5 / d1 ) {
+ return n1 * (t -= 2.25 / d1) * t + 0.9375;
+ } else {
+ return n1 * (t -= 2.625 / d1) * t + 0.984375;
+ }
+ }
+
+ public static final DoubleUnaryOperator bounceInOut() {
+ return Easing::bounceInOut;
+ }
+
+ public static final double bounceInOut( double t ) {
+ return t < 0.5
+ ? (1 - bounceOut(1 - 2 * t)) / 2
+ : (1 + bounceOut(2 * t - 1)) / 2;
+ }
+
+ public static final DoubleUnaryOperator backIn() {
+ return Easing::backIn;
+ }
+
+ public static final double backIn( double t ) {
+ double c1 = 1.70158;
+ double c3 = c1 + 1;
+
+ return c3 * t * t * t - c1 * t * t;
+ }
+
+ public static final DoubleUnaryOperator backOut() {
+ return Easing::backOut;
+ }
+
+ public static final double backOut( double t ) {
+ double c1 = 1.70158;
+ double c3 = c1 + 1;
+
+ return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
+ }
+
+ public static final DoubleUnaryOperator backInOut() {
+ return Easing::backInOut;
+ }
+
+ public static final double backInOut( double t ) {
+ double c1 = 1.70158;
+ double c2 = c1 * 1.525;
+
+ return t < 0.5
+ ? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
+ : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
+ }
+
+
+ /*
+ * Functions from manim community
+ */
+
+ public static final DoubleUnaryOperator smooth() {
+ return Easing::smooth;
+ }
+
+ public static final double smooth( double t ) {
+ double error = sigmoid(-INFLECTION / 2.0);
+ return Math.min(
+ Math.max(
+ (sigmoid(INFLECTION * (t - 0.5)) - error) / (1 - 2 * error),
+ 0
+ ),
+ 1.0
+ );
+ }
+
+ public static final double rushIn( double t ) {
+ return 2 * smooth(t / 2.0);
+ }
+
+ public static final double rushOut( double t ) {
+ return 2 * smooth(t / 2.0 + 0.5) - 1;
+ }
+
+ public static final double doubleSmooth( double t ) {
+ if( t < 0.5 )
+ return 0.5 * smooth(2 * t);
+ else
+ return 0.5 * (1 + smooth(2 * t - 1));
+ }
+
+ public static final double hobbit( double t ) {
+ double new_t = t < 0.5 ? 2 * t : 2 * (1 - t);
+ return smooth(new_t);
+ }
+
+
+ public static final DoubleUnaryOperator wiggle() {
+ return Easing::wiggle;
+ }
+
+ public static final DoubleUnaryOperator wiggle( final int wiggles ) {
+ return (t) -> Easing.wiggle(t, wiggles);
+ }
+
+ public static final double wiggle( double t ) {
+ return wiggle(t, 2);
+ }
+
+ public static final double wiggle( double t, int wiggles ) {
+ return hobbit(t) * Math.sin(wiggles * Math.PI * t);
+ }
+
+ public static double INFLECTION = 10.0;
+
+ public static final double sigmoid( double x ) {
+ return 1.0 / (1 + Math.exp(-x));
+ }
+
+
+ private Easing() {
+ }
+
+}
diff --git a/src/main/java/schule/ngb/zm/events/EventDispatcher.java b/src/main/java/schule/ngb/zm/events/EventDispatcher.java
new file mode 100644
index 0000000..61722c5
--- /dev/null
+++ b/src/main/java/schule/ngb/zm/events/EventDispatcher.java
@@ -0,0 +1,58 @@
+package schule.ngb.zm.events;
+
+import schule.ngb.zm.util.Validator;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.function.BiConsumer;
+
+public class EventDispatcher> {
+
+ private CopyOnWriteArraySet listeners;
+
+ private ConcurrentMap> eventRegistry;
+
+ public EventDispatcher() {
+ listeners = new CopyOnWriteArraySet<>();
+ eventRegistry = new ConcurrentHashMap<>();
+ }
+
+ public void registerEventType( String eventKey, BiConsumer dispatcher ) {
+ Validator.requireNotNull(eventKey);
+ Validator.requireNotNull(dispatcher);
+
+ if( !eventRegistered(eventKey) ) {
+ eventRegistry.put(eventKey, dispatcher);
+ }
+ }
+
+ public void addListener( L listener ) {
+ listeners.add(listener);
+ }
+
+ public void removeListener( L listener ) {
+ listeners.remove(listener);
+ }
+
+ public boolean hasListeners() {
+ return !listeners.isEmpty();
+ }
+
+ public boolean eventRegistered( String eventKey ) {
+ return eventRegistry.containsKey(eventKey);
+ }
+
+ public void dispatchEvent( String eventKey, final E event ) {
+ Validator.requireNotNull(eventKey);
+ Validator.requireNotNull(event);
+
+ if( eventRegistered(eventKey) ) {
+ final BiConsumer dispatcher = eventRegistry.get(eventKey);
+ listeners.forEach(( listener ) -> {
+ dispatcher.accept(event, listener);
+ });
+ }
+ }
+
+}
diff --git a/src/main/java/schule/ngb/zm/events/Listener.java b/src/main/java/schule/ngb/zm/events/Listener.java
new file mode 100644
index 0000000..54c6025
--- /dev/null
+++ b/src/main/java/schule/ngb/zm/events/Listener.java
@@ -0,0 +1,7 @@
+package schule.ngb.zm.events;
+
+public interface Listener {
+
+
+
+}
diff --git a/src/main/java/schule/ngb/zm/media/Audio.java b/src/main/java/schule/ngb/zm/media/Audio.java
index fa64162..6e8640b 100644
--- a/src/main/java/schule/ngb/zm/media/Audio.java
+++ b/src/main/java/schule/ngb/zm/media/Audio.java
@@ -5,6 +5,8 @@ package schule.ngb.zm.media;
*/
public interface Audio {
+ String getSource();
+
/**
* Prüft, ob das Medium gerade abgespielt wird.
*
diff --git a/src/main/java/schule/ngb/zm/media/AudioListener.java b/src/main/java/schule/ngb/zm/media/AudioListener.java
new file mode 100644
index 0000000..1600e07
--- /dev/null
+++ b/src/main/java/schule/ngb/zm/media/AudioListener.java
@@ -0,0 +1,11 @@
+package schule.ngb.zm.media;
+
+import schule.ngb.zm.events.Listener;
+
+public interface AudioListener extends Listener