Merge branch 'main' into zeichenfenster

# Conflicts:
#	src/main/java/schule/ngb/zm/media/Sound.java
This commit is contained in:
ngb
2022-07-25 19:07:51 +02:00
26 changed files with 401 additions and 181 deletions

View File

@@ -410,6 +410,7 @@ public class Constants {
* wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der * wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der
* Zeichenmaschine selbst vorgenommen werden. * Zeichenmaschine selbst vorgenommen werden.
*/ */
// TODO: (ngb) volatile ?
/** /**
* Aktuell dargestellte Bilder pro Sekunde. * Aktuell dargestellte Bilder pro Sekunde.

View File

@@ -42,7 +42,8 @@ public final class Options {
STOPPED, STOPPED,
TERMINATED, TERMINATED,
IDLE, IDLE,
DELAYED DELAYED,
DISPATCHING
} }
/** /**

View File

@@ -1,5 +1,7 @@
package schule.ngb.zm; package schule.ngb.zm;
import schule.ngb.zm.layers.DrawableLayer;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
@@ -13,7 +15,7 @@ public class Spielemaschine extends Zeichenmaschine {
private LinkedList<Updatable> updatables; private LinkedList<Updatable> updatables;
private GraphicsLayer mainLayer; private GameLayer mainLayer;
public Spielemaschine( String title ) { public Spielemaschine( String title ) {
this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title); this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title);
@@ -26,7 +28,7 @@ public class Spielemaschine extends Zeichenmaschine {
drawables = new LinkedList<>(); drawables = new LinkedList<>();
updatables = new LinkedList<>(); updatables = new LinkedList<>();
mainLayer = new GraphicsLayer(); mainLayer = new GameLayer();
canvas.addLayer(mainLayer); canvas.addLayer(mainLayer);
} }
@@ -83,7 +85,7 @@ public class Spielemaschine extends Zeichenmaschine {
@Override @Override
public final void update( double delta ) { public final void update( double delta ) {
synchronized( updatables ) { synchronized( updatables ) {
List<Updatable> it = Collections.unmodifiableList(updatables); List<Updatable> it = List.copyOf(updatables);
for( Updatable u: it ) { for( Updatable u: it ) {
if( u.isActive() ) { if( u.isActive() ) {
u.update(delta); u.update(delta);
@@ -96,23 +98,27 @@ public class Spielemaschine extends Zeichenmaschine {
@Override @Override
public final void draw() { public final void draw() {
mainLayer.clear();
synchronized( drawables ) {
List<Drawable> it = Collections.unmodifiableList(drawables);
for( Drawable d: it ) {
if( d.isVisible() ) {
d.draw(mainLayer.getGraphics());
}
}
}
} }
static class GraphicsLayer extends Layer { private class GameLayer extends Layer {
public Graphics2D getGraphics() { public Graphics2D getGraphics() {
return drawing; return drawing;
} }
@Override
public void draw( Graphics2D pGraphics ) {
clear();
List<Drawable> it = List.copyOf(drawables);
for( Drawable d: it ) {
if( d.isVisible() ) {
d.draw(drawing);
}
}
super.draw(pGraphics);
}
} }
} }

View File

@@ -1,6 +1,7 @@
package schule.ngb.zm; package schule.ngb.zm;
import schule.ngb.zm.layers.ColorLayer; import schule.ngb.zm.layers.ColorLayer;
import schule.ngb.zm.util.Log;
import java.awt.Canvas; import java.awt.Canvas;
import java.awt.Graphics; import java.awt.Graphics;
@@ -283,4 +284,6 @@ public class Zeichenleinwand extends Canvas {
} }
} }
private static final Log LOG = Log.getLogger(Zeichenleinwand.class);
} }

View File

@@ -316,17 +316,20 @@ public class Zeichenmaschine extends Constants {
public void windowClosing( WindowEvent e ) { public void windowClosing( WindowEvent e ) {
if( running ) { if( running ) {
running = false; running = false;
teardown(); mainThread.interrupt();
cleanup(); //teardown();
//cleanup();
} }
// Give the app a minimum amount of time to shut down // Give the app a minimum amount of time to shut down
// then kill it. // then kill it.
try { while( state != Options.AppState.TERMINATED ) {
Thread.sleep(5); Thread.yield();
} catch( InterruptedException ex ) { if( Thread.interrupted() ) {
} finally { break;
quit(true); }
} }
// Quit
quit(true);
} }
}); });
@@ -856,7 +859,7 @@ public class Zeichenmaschine extends Constants {
updateState = Options.AppState.DELAYED; updateState = Options.AppState.DELAYED;
Thread.sleep(ms - sub, (int) (timer % 1000000L)); Thread.sleep(ms - sub, (int) (timer % 1000000L));
} catch( InterruptedException ex ) { } catch( InterruptedException ignored ) {
// Nothing // Nothing
} finally { } finally {
updateState = oldState; updateState = oldState;
@@ -1058,10 +1061,12 @@ public class Zeichenmaschine extends Constants {
} }
/* /*
* Mouse handling * Input handling
*/ */
private void enqueueEvent( InputEvent evt ) { private void enqueueEvent( InputEvent evt ) {
eventQueue.add(evt); if( updateState != Options.AppState.DELAYED ) {
eventQueue.add(evt);
}
if( isPaused() || isStopped() ) { if( isPaused() || isStopped() ) {
dispatchEvents(); dispatchEvents();
@@ -1069,7 +1074,7 @@ public class Zeichenmaschine extends Constants {
} }
private void dispatchEvents() { private void dispatchEvents() {
synchronized( eventQueue ) { //synchronized( eventQueue ) {
while( !eventQueue.isEmpty() ) { while( !eventQueue.isEmpty() ) {
InputEvent evt = eventQueue.poll(); InputEvent evt = eventQueue.poll();
@@ -1090,7 +1095,7 @@ public class Zeichenmaschine extends Constants {
break; break;
} }
} }
} //}
} }
private void handleKeyEvent( KeyEvent evt ) { private void handleKeyEvent( KeyEvent evt ) {
@@ -1329,10 +1334,11 @@ public class Zeichenmaschine extends Constants {
// Call to draw() // Call to draw()
updateState = Options.AppState.DRAWING; updateState = Options.AppState.DRAWING;
Zeichenmaschine.this.draw(); Zeichenmaschine.this.draw();
updateState = Options.AppState.IDLE; updateState = Options.AppState.DISPATCHING;
// Send latest input events after finishing draw // Send latest input events after finishing draw
// since these may also block // since these may also block
dispatchEvents(); dispatchEvents();
updateState = Options.AppState.IDLE;
} }
}); });
} }
@@ -1341,6 +1347,11 @@ public class Zeichenmaschine extends Constants {
while( updateThreadExecutor.isRunning() while( updateThreadExecutor.isRunning()
&& !updateThreadExecutor.isWaiting() ) { && !updateThreadExecutor.isWaiting() ) {
Thread.yield(); Thread.yield();
if( Thread.interrupted() ) {
running = false;
break;
}
} }
// Display the current buffer content // Display the current buffer content

View File

@@ -2,15 +2,16 @@ package schule.ngb.zm.anim;
import schule.ngb.zm.Constants; import schule.ngb.zm.Constants;
import schule.ngb.zm.Updatable; import schule.ngb.zm.Updatable;
import schule.ngb.zm.util.Validator;
import schule.ngb.zm.util.events.EventDispatcher; import schule.ngb.zm.util.events.EventDispatcher;
import java.util.function.DoubleUnaryOperator; import java.util.function.DoubleUnaryOperator;
public abstract class Animation<T> implements Updatable { public abstract class Animation<T> extends Constants implements Updatable {
protected int runtime; protected int runtime;
protected int elapsed_time = 0; protected int elapsedTime = 0;
protected boolean running = false, finished = false; protected boolean running = false, finished = false;
@@ -23,7 +24,7 @@ public abstract class Animation<T> implements Updatable {
public Animation( DoubleUnaryOperator easing ) { public Animation( DoubleUnaryOperator easing ) {
this.runtime = Constants.DEFAULT_ANIM_RUNTIME; this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
this.easing = easing; this.easing = Validator.requireNotNull(easing);
} }
public Animation( int runtime ) { public Animation( int runtime ) {
@@ -33,7 +34,7 @@ public abstract class Animation<T> implements Updatable {
public Animation( int runtime, DoubleUnaryOperator easing ) { public Animation( int runtime, DoubleUnaryOperator easing ) {
this.runtime = runtime; this.runtime = runtime;
this.easing = easing; this.easing = Validator.requireNotNull(easing);
} }
public int getRuntime() { public int getRuntime() {
@@ -56,17 +57,17 @@ public abstract class Animation<T> implements Updatable {
public final void start() { public final void start() {
this.initialize(); this.initialize();
elapsed_time = 0; elapsedTime = 0;
running = true; running = true;
finished = false; finished = false;
interpolate(easing.applyAsDouble(0.0)); animate(easing.applyAsDouble(0.0));
initializeEventDispatcher().dispatchEvent("start", this); initializeEventDispatcher().dispatchEvent("start", this);
} }
public final void stop() { public final void stop() {
running = false; running = false;
// Make sure the last animation frame was interpolated correctly // Make sure the last animation frame was interpolated correctly
interpolate(easing.applyAsDouble((double) elapsed_time / (double) runtime)); animate(easing.applyAsDouble((double) elapsedTime / (double) runtime));
this.finish(); this.finish();
finished = true; finished = true;
initializeEventDispatcher().dispatchEvent("stop", this); initializeEventDispatcher().dispatchEvent("stop", this);
@@ -82,11 +83,7 @@ public abstract class Animation<T> implements Updatable {
public final void await() { public final void await() {
while( !finished ) { while( !finished ) {
try { Thread.yield();
Thread.sleep(1);
} catch( InterruptedException ex ) {
// Keep waiting
}
} }
} }
@@ -97,16 +94,16 @@ public abstract class Animation<T> implements Updatable {
@Override @Override
public void update( double delta ) { public void update( double delta ) {
elapsed_time += (int) (delta * 1000); elapsedTime += (int) (delta * 1000);
if( elapsed_time > runtime ) if( elapsedTime > runtime )
elapsed_time = runtime; elapsedTime = runtime;
double t = (double) elapsed_time / (double) runtime; double t = (double) elapsedTime / (double) runtime;
if( t >= 1.0 ) { if( t >= 1.0 ) {
running = false; running = false;
stop(); stop();
} else { } else {
interpolate(easing.applyAsDouble(t)); animate(easing.applyAsDouble(t));
} }
} }
@@ -124,7 +121,7 @@ public abstract class Animation<T> implements Updatable {
* @param e Fortschritt der Animation nachdem die Easingfunktion angewandt * @param e Fortschritt der Animation nachdem die Easingfunktion angewandt
* wurde. * wurde.
*/ */
public abstract void interpolate( double e ); public abstract void animate( double e );
EventDispatcher<Animation, AnimationListener> eventDispatcher; EventDispatcher<Animation, AnimationListener> eventDispatcher;

View File

@@ -19,8 +19,8 @@ public class AnimationFacade<S> extends Animation<S> {
} }
@Override @Override
public void interpolate( double e ) { public void animate( double e ) {
anim.interpolate(e); anim.animate(e);
} }
@Override @Override

View File

@@ -1,76 +1,116 @@
package schule.ngb.zm.anim; package schule.ngb.zm.anim;
import schule.ngb.zm.shapes.Shape; import java.util.Collection;
import java.util.List;
import java.util.Arrays;
import java.util.function.DoubleUnaryOperator; import java.util.function.DoubleUnaryOperator;
public class AnimationGroup extends Animation<Shape> { @SuppressWarnings( "unused" )
public class AnimationGroup<T> extends Animation<T> {
Animation<? extends Shape>[] anims; List<Animation<T>> anims;
private boolean overrideRuntime = false;
public AnimationGroup( DoubleUnaryOperator easing, Animation<? extends Shape>... anims ) { private boolean overrideEasing = false;
super(easing);
this.anims = anims;
int maxRuntime = Arrays.stream(this.anims).mapToInt((a) -> a.getRuntime()).reduce(0, Integer::max); private int overrideRuntime = -1;
setRuntime(maxRuntime);
private int lag = 0;
private int active = 0;
public AnimationGroup( Collection<Animation<T>> anims ) {
this(0, -1, null, anims);
} }
public AnimationGroup( int runtime, DoubleUnaryOperator easing, Animation<? extends Shape>... anims ) { public AnimationGroup( int lag, Collection<Animation<T>> anims ) {
super(runtime, easing); this(lag, -1, null, anims);
this.anims = anims; }
overrideRuntime = true;
public AnimationGroup( DoubleUnaryOperator easing, Collection<Animation<T>> anims ) {
this(0, -1, easing, anims);
}
public AnimationGroup( int lag, DoubleUnaryOperator easing, Collection<Animation<T>> anims ) {
this(lag, -1, easing, anims);
}
public AnimationGroup( int lag, int runtime, DoubleUnaryOperator easing, Collection<Animation<T>> anims ) {
super();
this.anims = List.copyOf(anims);
this.lag = lag;
if( easing != null ) {
this.easing = easing;
overrideEasing = true;
}
if( runtime > 0 ) {
this.runtime = anims.size() * lag + runtime;
this.overrideRuntime = runtime;
} else {
this.runtime = 0;
for( int i = 0; i < this.anims.size(); i++ ) {
if( i * lag + this.anims.get(i).getRuntime() > this.runtime ) {
this.runtime = i * lag + this.anims.get(i).getRuntime();
}
}
}
} }
@Override @Override
public Shape getAnimationTarget() { public T getAnimationTarget() {
return null; for( Animation<T> anim : anims ) {
if( anim.isActive() ) {
return anim.getAnimationTarget();
}
}
return anims.get(anims.size() - 1).getAnimationTarget();
} }
@Override @Override
public void update( double delta ) { public void update( double delta ) {
if( overrideRuntime ) { elapsedTime += (int) (delta * 1000);
synchronized( anims ) { // Animation is done. Stop all Animations.
for( Animation<? extends Shape> anim: anims ) { if( elapsedTime > runtime ) {
if( anim.isActive() ) { for( int i = 0; i < anims.size(); i++ ) {
anim.update(delta); if( anims.get(i).isActive() ) {
} anims.get(i).elapsedTime = anims.get(i).runtime;
anims.get(i).stop();
} }
} }
} else { running = false;
super.update(delta); this.stop();
} }
}
@Override while( active < anims.size() && elapsedTime >= active * lag ) {
public void interpolate( double e ) { anims.get(active).start();
synchronized( anims ) { active += 1;
for( Animation<? extends Shape> anim: anims ) { }
anim.interpolate(e);
for( int i = 0; i < active; i++ ) {
double t = 0.0;
if( overrideRuntime > 0 ) {
t = (double) (elapsedTime - i*lag) / (double) overrideRuntime;
} else {
t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime();
}
if( t >= 1.0 ) {
anims.get(i).elapsedTime = anims.get(i).runtime;
anims.get(i).stop();
} else {
double e = overrideEasing ?
easing.applyAsDouble(t) :
anims.get(i).easing.applyAsDouble(t);
anims.get(i).animate(e);
} }
} }
} }
@Override @Override
public void initialize() { public void animate( double e ) {
synchronized( anims ) {
for( Animation<? extends Shape> anim: anims ) {
anim.initialize();
}
}
}
@Override
public void finish() {
synchronized( anims ) {
for( Animation<? extends Shape> anim: anims ) {
anim.finish();
}
}
} }
} }

View File

@@ -3,7 +3,6 @@ package schule.ngb.zm.anim;
import schule.ngb.zm.Color; import schule.ngb.zm.Color;
import schule.ngb.zm.Constants; import schule.ngb.zm.Constants;
import schule.ngb.zm.Vector; import schule.ngb.zm.Vector;
import schule.ngb.zm.util.tasks.FrameSynchronizedTask;
import schule.ngb.zm.util.tasks.FramerateLimitedTask; import schule.ngb.zm.util.tasks.FramerateLimitedTask;
import schule.ngb.zm.util.tasks.TaskRunner; import schule.ngb.zm.util.tasks.TaskRunner;
import schule.ngb.zm.util.Log; import schule.ngb.zm.util.Log;
@@ -125,28 +124,28 @@ public class Animations {
public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) { public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
Validator.requireNotNull(target); Validator.requireNotNull(target);
Validator.requireNotNull(propSetter); Validator.requireNotNull(propSetter);
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e))); return play(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
} }
public static final <T> Future<T> animateProperty( T target, final Color from, final Color to, int runtime, DoubleUnaryOperator easing, Consumer<Color> propSetter ) { public static final <T> Future<T> animateProperty( T target, final Color from, final Color to, int runtime, DoubleUnaryOperator easing, Consumer<Color> propSetter ) {
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Color.interpolate(from, to, e))); return play(target, runtime, easing, ( e ) -> propSetter.accept(Color.interpolate(from, to, e)));
} }
public static final <T> Future<T> animateProperty( T target, final Vector from, final Vector to, int runtime, DoubleUnaryOperator easing, Consumer<Vector> propSetter ) { public static final <T> Future<T> animateProperty( T target, final Vector from, final Vector to, int runtime, DoubleUnaryOperator easing, Consumer<Vector> propSetter ) {
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Vector.interpolate(from, to, e))); return play(target, runtime, easing, ( e ) -> propSetter.accept(Vector.interpolate(from, to, e)));
} }
public static final <T, R> Future<T> animateProperty( T target, R from, R to, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, Consumer<R> propSetter ) { public static final <T, R> Future<T> animateProperty( T target, R from, R to, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, Consumer<R> propSetter ) {
return animate(target, runtime, easing, interpolator, ( t, r ) -> propSetter.accept(r)); return play(target, runtime, easing, interpolator, ( t, r ) -> propSetter.accept(r));
} }
public static final <T, R> Future<T> animate( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, BiConsumer<T, R> applicator ) { public static final <T, R> Future<T> play( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, BiConsumer<T, R> applicator ) {
return animate(target, runtime, easing, ( e ) -> applicator.accept(target, interpolator.apply(e))); return play(target, runtime, easing, ( e ) -> applicator.accept(target, interpolator.apply(e)));
} }
public static final <T> Future<T> animate( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) { public static final <T> Future<T> play( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
return TaskRunner.run(new FramerateLimitedTask() { return TaskRunner.run(new FramerateLimitedTask() {
double t = 0.0; double t = 0.0;
@@ -167,8 +166,8 @@ public class Animations {
}, target); }, target);
} }
public static final <T> T animateAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) { public static final <T> T playAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
Future<T> future = animate(target, runtime, easing, stepper); Future<T> future = play(target, runtime, easing, stepper);
while( !future.isDone() ) { while( !future.isDone() ) {
try { try {
return future.get(); return future.get();
@@ -191,7 +190,8 @@ public class Animations {
); );
}*/ }*/
public static <T> Future<Animation<T>> animate( Animation<T> animation ) { public static <T> Future<Animation<T>> play( Animation<T> animation ) {
// TODO: (ngb) Don't start when running
return TaskRunner.run(new FramerateLimitedTask() { return TaskRunner.run(new FramerateLimitedTask() {
@Override @Override
protected void initialize() { protected void initialize() {
@@ -206,13 +206,13 @@ public class Animations {
}, animation); }, animation);
} }
public static <T> Animation<T> animateAndWait( Animation<T> animation ) { public static <T> Animation<T> playAndWait( Animation<T> animation ) {
Future<Animation<T>> future = animate(animation); Future<Animation<T>> future = play(animation);
animation.await(); animation.await();
return animation; return animation;
} }
public static <T> Future<Animation<T>> animate( Animation<T> animation, DoubleUnaryOperator easing ) { public static <T> Future<Animation<T>> play( Animation<T> animation, DoubleUnaryOperator easing ) {
final AnimationFacade<T> facade = new AnimationFacade<>(animation, animation.getRuntime(), easing); final AnimationFacade<T> facade = new AnimationFacade<>(animation, animation.getRuntime(), easing);
return TaskRunner.run(new FramerateLimitedTask() { return TaskRunner.run(new FramerateLimitedTask() {
@Override @Override

View File

@@ -0,0 +1,39 @@
package schule.ngb.zm.anim;
import schule.ngb.zm.Constants;
import schule.ngb.zm.Vector;
import schule.ngb.zm.shapes.Shape;
import java.util.function.DoubleUnaryOperator;
public class CircleAnimation extends Animation<Shape> {
private Shape object;
private double centerx, centery, radius, startangle;
public CircleAnimation( Shape target, double cx, double cy, int runtime, DoubleUnaryOperator easing ) {
super(runtime, easing);
object = target;
centerx = cx;
centery = cy;
Vector vec = new Vector(target.getX(), target.getY()).sub(cx, cy);
startangle = vec.heading();
radius = vec.length();
}
@Override
public Shape getAnimationTarget() {
return object;
}
@Override
public void animate( double e ) {
double angle = startangle + Constants.radians(Constants.interpolate(0, 360, e));
double x = centerx + radius * Constants.cos(angle);
double y = centery + radius * Constants.sin(angle);
object.moveTo(x, y);
}
}

View File

@@ -0,0 +1,74 @@
package schule.ngb.zm.anim;
@SuppressWarnings( "unused" )
public class ContinousAnimation<T> extends Animation<T> {
private final Animation<T> baseAnimation;
private int lag = 0;
/**
* Speichert eine Approximation der aktuellen Steigung der Easing-Funktion,
* um im Fall {@code easeInOnly == true} nach dem ersten Durchlauf die
* passende Geschwindigkeit beizubehalten.
*/
private double m = 1.0, lastEase = 0.0;
private boolean easeInOnly = false;
public ContinousAnimation( Animation<T> baseAnimation ) {
this(baseAnimation, 0, false);
}
public ContinousAnimation( Animation<T> baseAnimation, int lag ) {
this(baseAnimation, lag, false);
}
public ContinousAnimation( Animation<T> baseAnimation, boolean easeInOnly ) {
this(baseAnimation, 0, easeInOnly);
}
private ContinousAnimation( Animation<T> baseAnimation, int lag, boolean easeInOnly ) {
super(baseAnimation.getRuntime(), baseAnimation.getEasing());
this.baseAnimation = baseAnimation;
this.lag = lag;
this.easeInOnly = easeInOnly;
}
@Override
public T getAnimationTarget() {
return baseAnimation.getAnimationTarget();
}
@Override
public void update( double delta ) {
elapsedTime += (int) (delta * 1000);
if( elapsedTime >= runtime + lag ) {
elapsedTime %= (runtime + lag);
if( easeInOnly && easing != null ) {
easing = null;
// runtime = (int)((1.0/m)*(runtime + lag));
}
}
double t = (double) elapsedTime / (double) runtime;
if( t >= 1.0 ) {
t = 1.0;
}
if( easing != null ) {
double e = easing.applyAsDouble(t);
animate(e);
m = (e-lastEase)/(delta*1000/(asDouble(runtime)));
lastEase = e;
} else {
animate(t);
}
}
@Override
public void animate( double e ) {
baseAnimation.animate(e);
}
}

View File

@@ -36,7 +36,7 @@ public class FadeAnimation extends Animation<Shape> {
} }
@Override @Override
public void interpolate( double e ) { public void animate( double e ) {
object.setFillColor(new Color(fill, (int) Constants.interpolate(fillAlpha, tAlpha, e))); object.setFillColor(new Color(fill, (int) Constants.interpolate(fillAlpha, tAlpha, e)));
object.setStrokeColor(new Color(stroke, (int) Constants.interpolate(strokeAlpha, tAlpha, e))); object.setStrokeColor(new Color(stroke, (int) Constants.interpolate(strokeAlpha, tAlpha, e)));
} }

View File

@@ -26,7 +26,7 @@ public class FillAnimation extends Animation<Shape> {
} }
@Override @Override
public void interpolate( double e ) { public void animate( double e ) {
object.setFillColor(Color.interpolate(oFill, tFill, e)); object.setFillColor(Color.interpolate(oFill, tFill, e));
} }

View File

@@ -27,7 +27,7 @@ public class MorphAnimation extends Animation<Shape> {
} }
@Override @Override
public void interpolate( double e ) { public void animate( double e ) {
object.setX(Constants.interpolate(original.getX(), target.getX(), e)); object.setX(Constants.interpolate(original.getX(), target.getX(), e));
object.setY(Constants.interpolate(original.getY(), target.getY(), e)); object.setY(Constants.interpolate(original.getY(), target.getY(), e));
object.setFillColor(Color.interpolate(original.getFillColor(), target.getFillColor(), e)); object.setFillColor(Color.interpolate(original.getFillColor(), target.getFillColor(), e));

View File

@@ -31,7 +31,7 @@ public class MoveAnimation extends Animation<Shape> {
} }
@Override @Override
public void interpolate( double e ) { public void animate( double e ) {
object.setX(Constants.interpolate(oX, tX, e)); object.setX(Constants.interpolate(oX, tX, e));
object.setY(Constants.interpolate(oY, tY, e)); object.setY(Constants.interpolate(oY, tY, e));
} }

View File

@@ -25,7 +25,7 @@ public class RotateAnimation extends Animation<Shape> {
} }
@Override @Override
public void interpolate( double e ) { public void animate( double e ) {
object.rotateTo(Constants.interpolate(oA, tA, e)); object.rotateTo(Constants.interpolate(oA, tA, e));
} }

View File

@@ -25,7 +25,7 @@ public class StrokeAnimation extends Animation<Shape> {
} }
@Override @Override
public void interpolate( double e ) { public void animate( double e ) {
object.setStrokeColor(Color.interpolate(oFill, tFill, e)); object.setStrokeColor(Color.interpolate(oFill, tFill, e));
} }

View File

@@ -0,0 +1,37 @@
package schule.ngb.zm.anim;
import schule.ngb.zm.Constants;
import schule.ngb.zm.Options;
import schule.ngb.zm.shapes.Shape;
import java.util.function.DoubleUnaryOperator;
public class WaveAnimation extends Animation<Shape> {
private Shape object;
private double strength, sinOffset, previousDelta = 0.0;
private Options.Direction dir;
public WaveAnimation( Shape target, double strength, Options.Direction dir, double sinOffset, int runtime, DoubleUnaryOperator easing ) {
super(runtime, easing);
this.object = target;
this.dir = dir;
this.strength = strength;
this.sinOffset = sinOffset;
}
@Override
public Shape getAnimationTarget() {
return object;
}
@Override
public void animate( double e ) {
double delta = this.strength * Constants.sin(Constants.interpolate(0.0, Constants.TWO_PI, e) + sinOffset);
object.move((delta - previousDelta) * dir.x, (delta - previousDelta) * dir.y);
previousDelta = delta;
}
}

View File

@@ -116,6 +116,14 @@ public class ShapesLayer extends Layer {
anim.start(); anim.start();
} }
public void play( Animation<? extends Shape>... anims ) {
for( Animation<? extends Shape> anim: anims ) {
this.animations.add(anim);
anim.start();
}
}
public <S extends Shape> void play( Animation<S> anim, int runtime ) { public <S extends Shape> void play( Animation<S> anim, int runtime ) {
play(anim, runtime, Easing.DEFAULT_EASING); play(anim, runtime, Easing.DEFAULT_EASING);
} }

View File

@@ -122,7 +122,7 @@ public class Music implements Audio {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void play() { public synchronized void play() {
if( openLine() ) { if( openLine() ) {
TaskRunner.run(new Runnable() { TaskRunner.run(new Runnable() {
@Override @Override
@@ -147,7 +147,7 @@ public class Music implements Audio {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void loop() { public synchronized void loop() {
looping = true; looping = true;
play(); play();
} }
@@ -156,7 +156,7 @@ public class Music implements Audio {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void stop() { public synchronized void stop() {
playing = false; playing = false;
looping = false; looping = false;
dispose(); dispose();
@@ -166,7 +166,7 @@ public class Music implements Audio {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void dispose() { public synchronized void dispose() {
if( audioLine != null ) { if( audioLine != null ) {
if( audioLine.isRunning() ) { if( audioLine.isRunning() ) {
playing = false; playing = false;
@@ -175,7 +175,6 @@ public class Music implements Audio {
if( audioLine.isOpen() ) { if( audioLine.isOpen() ) {
audioLine.drain(); audioLine.drain();
audioLine.close(); audioLine.close();
} }
} }
try { try {
@@ -189,7 +188,7 @@ public class Music implements Audio {
audioStream = null; audioStream = null;
} }
private void stream() { private synchronized void stream() {
audioLine.start(); audioLine.start();
playing = true; playing = true;
if( eventDispatcher != null ) { if( eventDispatcher != null ) {

View File

@@ -1,8 +1,8 @@
package schule.ngb.zm.media; package schule.ngb.zm.media;
import schule.ngb.zm.util.Log; import schule.ngb.zm.util.Log;
import schule.ngb.zm.util.io.ResourceStreamProvider;
import schule.ngb.zm.util.Validator; import schule.ngb.zm.util.Validator;
import schule.ngb.zm.util.io.ResourceStreamProvider;
import javax.sound.sampled.*; import javax.sound.sampled.*;
import java.io.IOException; import java.io.IOException;
@@ -125,7 +125,7 @@ public class Sound implements Audio {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void stop() { public synchronized void stop() {
looping = false; looping = false;
if( audioClip.isRunning() ) { if( audioClip.isRunning() ) {
audioClip.stop(); audioClip.stop();
@@ -137,7 +137,7 @@ public class Sound implements Audio {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void play() { public synchronized void play() {
if( this.openClip() ) { if( this.openClip() ) {
audioClip.start(); audioClip.start();
playing = true; playing = true;
@@ -176,7 +176,7 @@ public class Sound implements Audio {
* allerdings wird der aufrufende Thread nicht blockiert und * allerdings wird der aufrufende Thread nicht blockiert und
* {@link #dispose()} automatisch am Ende aufgerufen. * {@link #dispose()} automatisch am Ende aufgerufen.
*/ */
public void playOnce() { public synchronized void playOnce() {
disposeAfterPlay = true; disposeAfterPlay = true;
play(); play();
} }
@@ -200,16 +200,17 @@ public class Sound implements Audio {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void loop() { public synchronized void loop() {
loop(Clip.LOOP_CONTINUOUSLY); loop(Clip.LOOP_CONTINUOUSLY);
} }
/** /**
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und stoppt * Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und
* die Wiedergabe dann. * stoppt die Wiedergabe dann.
*
* @param count Anzahl der Wiederholungen. * @param count Anzahl der Wiederholungen.
*/ */
public void loop( int count ) { public synchronized void loop( int count ) {
if( count > 0 ) { if( count > 0 ) {
int loopCount = count; int loopCount = count;
if( loopCount != Clip.LOOP_CONTINUOUSLY ) { if( loopCount != Clip.LOOP_CONTINUOUSLY ) {
@@ -231,7 +232,7 @@ public class Sound implements Audio {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void dispose() { public synchronized void dispose() {
if( audioClip != null ) { if( audioClip != null ) {
if( audioClip.isRunning() ) { if( audioClip.isRunning() ) {
audioClip.stop(); audioClip.stop();

View File

@@ -15,6 +15,8 @@ public class Rectangle extends Shape {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.anchor = Options.Direction.NORTHWEST; this.anchor = Options.Direction.NORTHWEST;
//this.cacheEnabled = getClass().equals(Rectangle.class);
} }
public Rectangle( Rectangle pRechteck ) { public Rectangle( Rectangle pRechteck ) {
@@ -64,12 +66,10 @@ public class Rectangle extends Shape {
public void setWidth( double width ) { public void setWidth( double width ) {
this.width = width; this.width = width;
invalidate();
} }
public void setHeight( double height ) { public void setHeight( double height ) {
this.height = height; this.height = height;
invalidate();
} }
@Override @Override
@@ -92,7 +92,6 @@ public class Rectangle extends Shape {
super.scale(factor); super.scale(factor);
width *= factor; width *= factor;
height *= factor; height *= factor;
invalidate();
} }
@Override @Override

View File

@@ -66,11 +66,6 @@ public abstract class Shape extends FilledShape {
*/ */
protected Options.Direction anchor = Options.Direction.CENTER; protected Options.Direction anchor = Options.Direction.CENTER;
/**
* Zwischenspeicher für die AWT-Shape zu dieser Form.
*/
protected java.awt.Shape awtShape = null;
/** /**
* Setzt die x- und y-Koordinate der Form auf 0. * Setzt die x- und y-Koordinate der Form auf 0.
*/ */
@@ -406,13 +401,6 @@ public abstract class Shape extends FilledShape {
*/ */
public abstract java.awt.Shape getShape(); public abstract java.awt.Shape getShape();
/**
* Interne Methode, um den Zwischenspeicher der Java-AWT Shape zu löschen.
*/
protected void invalidate() {
awtShape = null;
}
/** /**
* Gibt die Begrenzungen der Form zurück. * Gibt die Begrenzungen der Form zurück.
* <p> * <p>
@@ -566,12 +554,8 @@ public abstract class Shape extends FilledShape {
return; return;
} }
if( awtShape == null ) { java.awt.Shape shape = getShape();
awtShape = getShape(); if( shape != null ) {
}
if( awtShape != null ) {
java.awt.Shape shape = awtShape;
if( transform != null ) { if( transform != null ) {
shape = transform.createTransformedShape(shape); shape = transform.createTransformedShape(shape);
} }

View File

@@ -77,7 +77,7 @@ public final class Log {
*/ */
public static void enableGlobalLevel( Level level ) { public static void enableGlobalLevel( Level level ) {
int lvl = Validator.requireNotNull(level).intValue(); int lvl = Validator.requireNotNull(level).intValue();
ensureRootLoggerIntialized(); ensureRootLoggerInitialized();
// Decrease level of root level ConsoleHandlers for output // Decrease level of root level ConsoleHandlers for output
Logger rootLogger = Logger.getLogger(""); Logger rootLogger = Logger.getLogger("");
@@ -116,22 +116,24 @@ public final class Log {
} }
public static Log getLogger( Class<?> clazz ) { public static Log getLogger( Class<?> clazz ) {
ensureRootLoggerIntialized(); ensureRootLoggerInitialized();
return new Log(clazz); return new Log(clazz);
} }
private static void ensureRootLoggerIntialized() { private static void ensureRootLoggerInitialized() {
if( LOGGING_INIT ) { if( LOGGING_INIT ) {
return; return;
} }
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null ) {
System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT);
}
Logger rootLogger = Logger.getLogger(ROOT_LOGGER); Logger rootLogger = Logger.getLogger(ROOT_LOGGER);
rootLogger.setLevel(Level.INFO); rootLogger.setLevel(Level.INFO);
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null if( System.getProperty("java.util.logging.SimpleFormatter.format") == null
&& LogManager.getLogManager().getProperty("java.util.logging.SimpleFormatter.format") == null ) { && LogManager.getLogManager().getProperty("java.util.logging.SimpleFormatter.format") == null ) {
System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT); // System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT);
//rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()));
rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()) { rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()) {
@Override @Override
public synchronized void publish(final LogRecord record) { public synchronized void publish(final LogRecord record) {
@@ -139,7 +141,7 @@ public final class Log {
flush(); flush();
} }
}); });
rootLogger.setUseParentHandlers(false); // rootLogger.setUseParentHandlers(false);
} }
if( rootLogger.getUseParentHandlers() ) { if( rootLogger.getUseParentHandlers() ) {
// This logger was not configured somewhere else // This logger was not configured somewhere else

View File

@@ -4,6 +4,7 @@ import schule.ngb.zm.util.Log;
import java.awt.Font; import java.awt.Font;
import java.awt.FontFormatException; import java.awt.FontFormatException;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Map; import java.util.Map;
@@ -14,20 +15,50 @@ public class FontLoader {
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>(); private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
/**
* Lädt eine Schrift aus einer Datei.
* <p>
* Die Schrift wird unter ihrem Dateinamen in den Schriftenspeicher geladen
* und kann danach in der Zeichenmaschine benutzt werden.
*
* Ein Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
* "Font-Name" geladen und kann danach zum Beispiel in einem
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
* verwendet werden.
*
* @param source
* @return
*/
public static Font loadFont( String source ) { public static Font loadFont( String source ) {
String name = source;
// Dateipfad entfernen
int lastIndex = source.lastIndexOf(File.separatorChar);
if( lastIndex > -1 ) {
source.substring(lastIndex + 1);
}
// Dateiendung entfernen
lastIndex = name.lastIndexOf('.');
if( lastIndex > -1 ) {
name = name.substring(0, lastIndex);
}
return loadFont(name, source);
}
public static Font loadFont( String name, String source ) {
Objects.requireNonNull(source, "Font source may not be null"); Objects.requireNonNull(source, "Font source may not be null");
if( source.length() == 0 ) { if( source.length() == 0 ) {
throw new IllegalArgumentException("Font source may not be empty."); throw new IllegalArgumentException("Font source may not be empty.");
} }
if( fontCache.containsKey(source) ) { if( fontCache.containsKey(name) ) {
LOG.trace("Retrieved font <%s> from font cache.", source); LOG.trace("Retrieved font <%s> from font cache.", name);
return fontCache.get(source); return fontCache.get(name);
} }
// Look for System fonts // Look for System fonts
Font font = Font.decode(source); Font font = Font.decode(source);
if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) { if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
fontCache.put(name, font);
fontCache.put(source, font); fontCache.put(source, font);
LOG.debug("Loaded system font for <%s>.", source); LOG.debug("Loaded system font for <%s>.", source);
return font; return font;
@@ -40,10 +71,11 @@ public class FontLoader {
font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN); font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN);
if( font != null ) { if( font != null ) {
fontCache.put(name, font);
fontCache.put(source, font); fontCache.put(source, font);
//ge.registerFont(font); //ge.registerFont(font);
} }
LOG.debug("Loaded custom font from <%s>.", source); LOG.debug("Loaded custom font from source <%s>.", source);
} catch( IOException ioex ) { } catch( IOException ioex ) {
LOG.error(ioex, "Error loading custom font file from source <%s>.", source); LOG.error(ioex, "Error loading custom font file from source <%s>.", source);
} catch( FontFormatException ffex ) { } catch( FontFormatException ffex ) {

View File

@@ -61,7 +61,7 @@ class AnimationsTest {
private void _animateMove( Shape s, int runtime, DoubleUnaryOperator easing ) { private void _animateMove( Shape s, int runtime, DoubleUnaryOperator easing ) {
s.moveTo(0, 0); s.moveTo(0, 0);
Future<Shape> future = Animations.animate( Future<Shape> future = Animations.play(
s, runtime, s, runtime,
easing, easing,
( e ) -> Constants.interpolate(0, zm.getWidth(), e), ( e ) -> Constants.interpolate(0, zm.getWidth(), e),
@@ -90,25 +90,11 @@ class AnimationsTest {
final int midY = (int) (zm.getHeight() * .5); final int midY = (int) (zm.getHeight() * .5);
final int radius = (int) (zm.getWidth() * .25); final int radius = (int) (zm.getWidth() * .25);
Animator<Shape, Double> ani = new Animator<Shape, Double>() { Future<Shape> future = Animations.play(
@Override s, runtime, easing, (e) -> {
public double easing( double t ) { double rad = Math.toRadians(Constants.interpolate(0, 360, e));
return easing.applyAsDouble(t);
}
@Override
public Double interpolator( double e ) {
return Constants.interpolate(0, 360, e);
}
@Override
public void applicator( Shape s, Double angle ) {
double rad = Math.toRadians(angle);
s.moveTo(midX + radius * Math.cos(rad), midY + radius * Math.sin(rad)); s.moveTo(midX + radius * Math.cos(rad), midY + radius * Math.sin(rad));
} });
};
Future<Shape> future = Animations.animate(s, runtime, ani);
assertNotNull(future); assertNotNull(future);
try { try {
assertEquals(s, future.get()); assertEquals(s, future.get());
@@ -147,7 +133,7 @@ class AnimationsTest {
private void _animateRotate( Shape s, int runtime, DoubleUnaryOperator easing ) { private void _animateRotate( Shape s, int runtime, DoubleUnaryOperator easing ) {
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5); s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
s.rotateTo(0); s.rotateTo(0);
Future<Shape> future = Animations.animate( Future<Shape> future = Animations.play(
s, runtime, s, runtime,
easing, easing,
( e ) -> s.rotateTo(Constants.interpolate(0, 720, e)) ( e ) -> s.rotateTo(Constants.interpolate(0, 720, e))
@@ -179,7 +165,7 @@ class AnimationsTest {
private void _animateColor( Shape s, Color to, int runtime, DoubleUnaryOperator easing ) { private void _animateColor( Shape s, Color to, int runtime, DoubleUnaryOperator easing ) {
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5); s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
final Color from = s.getFillColor(); final Color from = s.getFillColor();
Future<Shape> future = Animations.animate( Future<Shape> future = Animations.play(
s, runtime, s, runtime,
easing, easing,
( e ) -> Color.interpolate(from, to, e), ( e ) -> Color.interpolate(from, to, e),