mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-10-12 09:56:33 +02:00
Merge branch 'main' into charts
This commit is contained in:
@@ -28,7 +28,7 @@ public class Constants {
|
||||
/**
|
||||
* Patchversion der Zeichenmaschine.
|
||||
*/
|
||||
public static final int APP_VERSION_REV = 12;
|
||||
public static final int APP_VERSION_REV = 21;
|
||||
|
||||
/**
|
||||
* Version der Zeichenmaschine als Text-String.
|
||||
@@ -71,6 +71,11 @@ public class Constants {
|
||||
*/
|
||||
public static final int STD_FONTSIZE = 14;
|
||||
|
||||
/**
|
||||
* Standardwert für den Abstand von Formen.
|
||||
*/
|
||||
public static final int STD_BUFFER = 10;
|
||||
|
||||
public static final Options.StrokeType SOLID = Options.StrokeType.SOLID;
|
||||
|
||||
public static final Options.StrokeType DASHED = Options.StrokeType.DASHED;
|
||||
@@ -242,6 +247,11 @@ public class Constants {
|
||||
* Zeichenmaschine selbst vorgenommen werden.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Aktuell dargestellte Bilder pro Sekunde.
|
||||
*/
|
||||
public static int framesPerSecond = STD_FPS;
|
||||
|
||||
/**
|
||||
* Anzahl der Ticks (Frames), die das Programm bisher läuft.
|
||||
*/
|
||||
@@ -279,6 +289,18 @@ public class Constants {
|
||||
*/
|
||||
public static double pmouseY = 0.0;
|
||||
|
||||
/**
|
||||
* Die aktuelle (<em>current</em>) {@code x}-Koordinate der Maus
|
||||
* (wird bei jeder Mausbewegung aktualisiert).
|
||||
*/
|
||||
public static double cmouseX = 0.0;
|
||||
|
||||
/**
|
||||
* Die aktuelle (<em>current</em>) {@code y}-Koordinate der Maus
|
||||
* (wird bei jeder Mausbewegung aktualisiert).
|
||||
*/
|
||||
public static double cmouseY = 0.0;
|
||||
|
||||
/**
|
||||
* Gibt an, ob ein Mausknopf derzeit gedrückt ist.
|
||||
*/
|
||||
@@ -364,8 +386,8 @@ public class Constants {
|
||||
// Farben
|
||||
|
||||
/**
|
||||
* Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen
|
||||
* Grauwert zwischen <code>0</code> und <code>255</code> an, wobei
|
||||
* Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen Grauwert
|
||||
* zwischen <code>0</code> und <code>255</code> an, wobei
|
||||
* <code>0</code> schwarz und <code>255</code> weiß ist.
|
||||
*
|
||||
* @param gray Grauwert zwischen <code>0</code> und <code>255</code>.
|
||||
@@ -376,8 +398,8 @@ public class Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen
|
||||
* Grauwert zwischen <code>0</code> und <code>255</code> an, wobei
|
||||
* Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen Grauwert
|
||||
* zwischen <code>0</code> und <code>255</code> an, wobei
|
||||
* <code>0</code> schwarz und <code>255</code> weiß ist.
|
||||
* {@code alpha} gibt den den Transparentwert an (auch zwischen
|
||||
* <code>0</code> und <code>255</code>), wobei
|
||||
@@ -395,8 +417,8 @@ public class Constants {
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe. Die Parameter {@code red}, {@code green} und
|
||||
* {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die
|
||||
* Werte liegen zwischen <code>0</code> und <code>255</code>.
|
||||
* {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
||||
* liegen zwischen <code>0</code> und <code>255</code>.
|
||||
*
|
||||
* @param red Rotwert zwischen <code>0</code> und <code>255</code>.
|
||||
* @param green Grünwert zwischen <code>0</code> und <code>255</code>.
|
||||
@@ -409,10 +431,10 @@ public class Constants {
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe. Die Parameter {@code red}, {@code green} und
|
||||
* {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die
|
||||
* Werte liegen zwischen <code>0</code> und <code>255</code>.
|
||||
* {@code alpha} gibt den den Transparentwert an (auch zwischen
|
||||
* code>0</code> und <code>255</code>), wobei
|
||||
* {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
||||
* liegen zwischen <code>0</code> und <code>255</code>. {@code alpha} gibt
|
||||
* den den Transparentwert an (auch zwischen code>0</code> und
|
||||
* <code>255</code>), wobei
|
||||
* <code>0</code> komplett durchsichtig ist und <code>255</code> komplett
|
||||
* deckend.
|
||||
*
|
||||
@@ -705,9 +727,9 @@ public class Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Beschränkt die Zahl {@code x} auf das Intervall <code>[min,
|
||||
* max]</code>. Liegt {@code x} außerhalb des Intervalls, wird eine der
|
||||
* Grenzen zurückgegeben.
|
||||
* Beschränkt die Zahl {@code x} auf das Intervall <code>[min, max]</code>.
|
||||
* Liegt {@code x} außerhalb des Intervalls, wird eine der Grenzen
|
||||
* zurückgegeben.
|
||||
*
|
||||
* @param x Eine Zahl.
|
||||
* @param max Das Maximum des Intervalls.
|
||||
@@ -724,9 +746,9 @@ public class Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Beschränkt die Zahl {@code x} auf das Intervall <code>[min,
|
||||
* max]</code>. Liegt {@code x} außerhalb des Intervalls, wird eine der
|
||||
* Grenzen zurückgegeben.
|
||||
* Beschränkt die Zahl {@code x} auf das Intervall <code>[min, max]</code>.
|
||||
* Liegt {@code x} außerhalb des Intervalls, wird eine der Grenzen
|
||||
* zurückgegeben.
|
||||
*
|
||||
* @param x Eine Zahl.
|
||||
* @param max Das Maximum des Intervalls.
|
||||
@@ -743,8 +765,8 @@ public class Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpoliert einen Wert zwischen {@code from} und {@code to}
|
||||
* linear, abhängig von {@code t}. Das Ergebnis entspricht der Formel
|
||||
* Interpoliert einen Wert zwischen {@code from} und {@code to} linear,
|
||||
* abhängig von {@code t}. Das Ergebnis entspricht der Formel
|
||||
*
|
||||
* <pre>
|
||||
* from - t * (from + to)
|
||||
@@ -759,6 +781,10 @@ public class Constants {
|
||||
return from + t * (to - from);
|
||||
}
|
||||
|
||||
public static final double morph( double from, double to, double t ) {
|
||||
return interpolate(from, to, limit(t, 0.0, 1.0));
|
||||
}
|
||||
|
||||
public static final double map( double value, double fromMin, double fromMax, double toMin, double toMax ) {
|
||||
return interpolate(toMin, toMax, (value - fromMin) / (fromMax - fromMin));
|
||||
}
|
||||
@@ -1038,4 +1064,320 @@ public class Constants {
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
// Konstants for Key events (Copied from KeyEvent)
|
||||
|
||||
/**
|
||||
* Constant for the ENTER virtual key.
|
||||
*/
|
||||
public static final int KEY_ENTER = KeyEvent.VK_ENTER;
|
||||
|
||||
/**
|
||||
* Constant for the BACK_SPACE virtual key.
|
||||
*/
|
||||
public static final int KEY_BACK_SPACE = KeyEvent.VK_BACK_SPACE;
|
||||
|
||||
/**
|
||||
* Constant for the TAB virtual key.
|
||||
*/
|
||||
public static final int KEY_TAB = KeyEvent.VK_TAB;
|
||||
|
||||
/**
|
||||
* Constant for the CANCEL virtual key.
|
||||
*/
|
||||
public static final int KEY_CANCEL = KeyEvent.VK_CANCEL;
|
||||
|
||||
/**
|
||||
* Constant for the CLEAR virtual key.
|
||||
*/
|
||||
public static final int KEY_CLEAR = KeyEvent.VK_CLEAR;
|
||||
|
||||
/**
|
||||
* Constant for the SHIFT virtual key.
|
||||
*/
|
||||
public static final int KEY_SHIFT = KeyEvent.VK_SHIFT;
|
||||
|
||||
/**
|
||||
* Constant for the CONTROL virtual key.
|
||||
*/
|
||||
public static final int KEY_CONTROL = KeyEvent.VK_CONTROL;
|
||||
|
||||
/**
|
||||
* Constant for the ALT virtual key.
|
||||
*/
|
||||
public static final int KEY_ALT = KeyEvent.VK_ALT;
|
||||
|
||||
/**
|
||||
* Constant for the PAUSE virtual key.
|
||||
*/
|
||||
public static final int KEY_PAUSE = KeyEvent.VK_PAUSE;
|
||||
|
||||
/**
|
||||
* Constant for the CAPS_LOCK virtual key.
|
||||
*/
|
||||
public static final int KEY_CAPS_LOCK = KeyEvent.VK_CAPS_LOCK;
|
||||
|
||||
/**
|
||||
* Constant for the ESCAPE virtual key.
|
||||
*/
|
||||
public static final int KEY_ESCAPE = KeyEvent.VK_ESCAPE;
|
||||
|
||||
/**
|
||||
* Constant for the SPACE virtual key.
|
||||
*/
|
||||
public static final int KEY_SPACE = KeyEvent.VK_SPACE;
|
||||
|
||||
/**
|
||||
* Constant for the PAGE_UP virtual key.
|
||||
*/
|
||||
public static final int KEY_PAGE_UP = KeyEvent.VK_PAGE_UP;
|
||||
|
||||
/**
|
||||
* Constant for the PAGE_DOWN virtual key.
|
||||
*/
|
||||
public static final int KEY_PAGE_DOWN = KeyEvent.VK_PAGE_DOWN;
|
||||
|
||||
/**
|
||||
* Constant for the END virtual key.
|
||||
*/
|
||||
public static final int KEY_END = KeyEvent.VK_END;
|
||||
|
||||
/**
|
||||
* Constant for the HOME virtual key.
|
||||
*/
|
||||
public static final int KEY_HOME = KeyEvent.VK_HOME;
|
||||
|
||||
/**
|
||||
* Constant for the non-numpad <b>left</b> arrow key.
|
||||
*/
|
||||
public static final int KEY_LEFT = KeyEvent.VK_LEFT;
|
||||
|
||||
/**
|
||||
* Constant for the non-numpad <b>up</b> arrow key.
|
||||
*/
|
||||
public static final int KEY_UP = KeyEvent.VK_UP;
|
||||
|
||||
/**
|
||||
* Constant for the non-numpad <b>right</b> arrow key.
|
||||
*/
|
||||
public static final int KEY_RIGHT = KeyEvent.VK_RIGHT;
|
||||
|
||||
/**
|
||||
* Constant for the non-numpad <b>down</b> arrow key.
|
||||
*/
|
||||
public static final int KEY_DOWN = KeyEvent.VK_DOWN;
|
||||
|
||||
/**
|
||||
* Constant for the comma key, ","
|
||||
*/
|
||||
public static final int KEY_COMMA = KeyEvent.VK_COMMA;
|
||||
|
||||
/**
|
||||
* Constant for the minus key, "-"
|
||||
*
|
||||
* @since 1.2
|
||||
*/
|
||||
public static final int KEY_MINUS = KeyEvent.VK_MINUS;
|
||||
|
||||
/**
|
||||
* Constant for the period key, "."
|
||||
*/
|
||||
public static final int KEY_PERIOD = KeyEvent.VK_PERIOD;
|
||||
|
||||
/**
|
||||
* Constant for the forward slash key, "/"
|
||||
*/
|
||||
public static final int KEY_SLASH = KeyEvent.VK_SLASH;
|
||||
|
||||
/**
|
||||
* Constant for the "0" key.
|
||||
*/
|
||||
public static final int KEY_0 = KeyEvent.VK_0;
|
||||
|
||||
/**
|
||||
* Constant for the "1" key.
|
||||
*/
|
||||
public static final int KEY_1 = KeyEvent.VK_1;
|
||||
|
||||
/**
|
||||
* Constant for the "2" key.
|
||||
*/
|
||||
public static final int KEY_2 = KeyEvent.VK_2;
|
||||
|
||||
/**
|
||||
* Constant for the "3" key.
|
||||
*/
|
||||
public static final int KEY_3 = KeyEvent.VK_3;
|
||||
|
||||
/**
|
||||
* Constant for the "4" key.
|
||||
*/
|
||||
public static final int KEY_4 = KeyEvent.VK_4;
|
||||
|
||||
/**
|
||||
* Constant for the "5" key.
|
||||
*/
|
||||
public static final int KEY_5 = KeyEvent.VK_5;
|
||||
|
||||
/**
|
||||
* Constant for the "6" key.
|
||||
*/
|
||||
public static final int KEY_6 = KeyEvent.VK_6;
|
||||
|
||||
/**
|
||||
* Constant for the "7" key.
|
||||
*/
|
||||
public static final int KEY_7 = KeyEvent.VK_7;
|
||||
|
||||
/**
|
||||
* Constant for the "8" key.
|
||||
*/
|
||||
public static final int KEY_8 = KeyEvent.VK_8;
|
||||
|
||||
/**
|
||||
* Constant for the "9" key.
|
||||
*/
|
||||
public static final int KEY_9 = KeyEvent.VK_9;
|
||||
|
||||
/**
|
||||
* Constant for the semicolon key, ";"
|
||||
*/
|
||||
public static final int KEY_SEMICOLON = KeyEvent.VK_SEMICOLON;
|
||||
|
||||
/**
|
||||
* Constant for the equals key, "="
|
||||
*/
|
||||
public static final int KEY_EQUALS = KeyEvent.VK_EQUALS;
|
||||
|
||||
/** VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */
|
||||
|
||||
/**
|
||||
* Constant for the "A" key.
|
||||
*/
|
||||
public static final int KEY_A = KeyEvent.VK_A;
|
||||
|
||||
/**
|
||||
* Constant for the "B" key.
|
||||
*/
|
||||
public static final int KEY_B = KeyEvent.VK_B;
|
||||
|
||||
/**
|
||||
* Constant for the "C" key.
|
||||
*/
|
||||
public static final int KEY_C = KeyEvent.VK_C;
|
||||
|
||||
/**
|
||||
* Constant for the "D" key.
|
||||
*/
|
||||
public static final int KEY_D = KeyEvent.VK_D;
|
||||
|
||||
/**
|
||||
* Constant for the "E" key.
|
||||
*/
|
||||
public static final int KEY_E = KeyEvent.VK_E;
|
||||
|
||||
/**
|
||||
* Constant for the "F" key.
|
||||
*/
|
||||
public static final int KEY_F = KeyEvent.VK_F;
|
||||
|
||||
/**
|
||||
* Constant for the "G" key.
|
||||
*/
|
||||
public static final int KEY_G = KeyEvent.VK_G;
|
||||
|
||||
/**
|
||||
* Constant for the "H" key.
|
||||
*/
|
||||
public static final int KEY_H = KeyEvent.VK_H;
|
||||
|
||||
/**
|
||||
* Constant for the "I" key.
|
||||
*/
|
||||
public static final int KEY_I = KeyEvent.VK_I;
|
||||
|
||||
/**
|
||||
* Constant for the "J" key.
|
||||
*/
|
||||
public static final int KEY_J = KeyEvent.VK_J;
|
||||
|
||||
/**
|
||||
* Constant for the "K" key.
|
||||
*/
|
||||
public static final int KEY_K = KeyEvent.VK_K;
|
||||
|
||||
/**
|
||||
* Constant for the "L" key.
|
||||
*/
|
||||
public static final int KEY_L = KeyEvent.VK_L;
|
||||
|
||||
/**
|
||||
* Constant for the "M" key.
|
||||
*/
|
||||
public static final int KEY_M = KeyEvent.VK_M;
|
||||
|
||||
/**
|
||||
* Constant for the "N" key.
|
||||
*/
|
||||
public static final int KEY_N = KeyEvent.VK_N;
|
||||
|
||||
/**
|
||||
* Constant for the "O" key.
|
||||
*/
|
||||
public static final int KEY_O = KeyEvent.VK_O;
|
||||
|
||||
/**
|
||||
* Constant for the "P" key.
|
||||
*/
|
||||
public static final int KEY_P = KeyEvent.VK_P;
|
||||
|
||||
/**
|
||||
* Constant for the "Q" key.
|
||||
*/
|
||||
public static final int KEY_Q = KeyEvent.VK_Q;
|
||||
|
||||
/**
|
||||
* Constant for the "R" key.
|
||||
*/
|
||||
public static final int KEY_R = KeyEvent.VK_R;
|
||||
|
||||
/**
|
||||
* Constant for the "S" key.
|
||||
*/
|
||||
public static final int KEY_S = KeyEvent.VK_S;
|
||||
|
||||
/**
|
||||
* Constant for the "T" key.
|
||||
*/
|
||||
public static final int KEY_T = KeyEvent.VK_T;
|
||||
|
||||
/**
|
||||
* Constant for the "U" key.
|
||||
*/
|
||||
public static final int KEY_U = KeyEvent.VK_U;
|
||||
|
||||
/**
|
||||
* Constant for the "V" key.
|
||||
*/
|
||||
public static final int KEY_V = KeyEvent.VK_V;
|
||||
|
||||
/**
|
||||
* Constant for the "W" key.
|
||||
*/
|
||||
public static final int KEY_W = KeyEvent.VK_W;
|
||||
|
||||
/**
|
||||
* Constant for the "X" key.
|
||||
*/
|
||||
public static final int KEY_X = KeyEvent.VK_X;
|
||||
|
||||
/**
|
||||
* Constant for the "Y" key.
|
||||
*/
|
||||
public static final int KEY_Y = KeyEvent.VK_Y;
|
||||
|
||||
/**
|
||||
* Constant for the "Z" key.
|
||||
*/
|
||||
public static final int KEY_Z = KeyEvent.VK_Z;
|
||||
|
||||
}
|
||||
|
||||
11
src/schule/ngb/zm/GraphicsLayer.java
Normal file
11
src/schule/ngb/zm/GraphicsLayer.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
public class GraphicsLayer extends Layer {
|
||||
|
||||
public Graphics2D getGraphics() {
|
||||
return drawing;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Puffer für dei Ebene mit der angegebenen Größe
|
||||
* Erstellt einen neuen Puffer für die Ebene mit der angegebenen Größe
|
||||
* und kopiert den Inhalt des alten Puffers in den Neuen.
|
||||
*
|
||||
* @param width Width des neuen Puffers.
|
||||
|
||||
@@ -41,43 +41,58 @@ public final class Options {
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
CENTER(0),
|
||||
NORTH(1 << 0),
|
||||
EAST(1 << 1),
|
||||
SOUTH(1 << 2),
|
||||
WEST(1 << 3),
|
||||
CENTER(0, 0),
|
||||
NORTH(0, -1),
|
||||
EAST(1, 0),
|
||||
SOUTH(0, 1),
|
||||
WEST(-1, 0),
|
||||
|
||||
NORTHEAST(NORTH.mask | EAST.mask),
|
||||
SOUTHEAST(SOUTH.mask | EAST.mask),
|
||||
NORTHWEST(NORTH.mask | WEST.mask),
|
||||
SOUTHWEST(SOUTH.mask | WEST.mask),
|
||||
NORTHEAST(1, -1),
|
||||
SOUTHEAST(1, 1),
|
||||
NORTHWEST(-1, -1),
|
||||
SOUTHWEST(-1, 1),
|
||||
|
||||
MIDDLE(CENTER.mask),
|
||||
UP(NORTH.mask),
|
||||
DOWN(SOUTH.mask),
|
||||
LEFT(WEST.mask),
|
||||
RIGHT(EAST.mask);
|
||||
MIDDLE(CENTER),
|
||||
UP(NORTH),
|
||||
DOWN(SOUTH),
|
||||
LEFT(WEST),
|
||||
RIGHT(EAST);
|
||||
|
||||
public final byte mask;
|
||||
public final byte x, y;
|
||||
|
||||
Direction( int mask ) {
|
||||
this.mask = (byte) mask;
|
||||
Direction( int x, int y ) {
|
||||
this.x = (byte)x;
|
||||
this.y = (byte)y;
|
||||
}
|
||||
|
||||
public boolean in( int mask ) {
|
||||
return (mask & this.mask) == this.mask;
|
||||
Direction( Direction original ) {
|
||||
this.x = original.x;
|
||||
this.y = original.y;
|
||||
}
|
||||
|
||||
public boolean in( Direction dir ) {
|
||||
return in(dir.mask);
|
||||
}
|
||||
|
||||
public boolean contains( int mask ) {
|
||||
return (mask & this.mask) == mask;
|
||||
return (this.x == dir.x && this.y == 0) || (this.y == dir.y && this.x == 0) || (this.x == dir.x && this.y == dir.y);
|
||||
}
|
||||
|
||||
public boolean contains( Direction dir ) {
|
||||
return contains(dir.mask);
|
||||
return dir.in(this);
|
||||
}
|
||||
|
||||
public Vector asVector() {
|
||||
return new Vector(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die entgegengesetzte Richtung zu dieser zurück.
|
||||
* @return
|
||||
*/
|
||||
public Direction inverse() {
|
||||
for( Direction dir: Direction.values() ) {
|
||||
if( dir.x == -this.x && dir.y == -this.y ) {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
return CENTER;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
104
src/schule/ngb/zm/Spielemaschine.java
Normal file
104
src/schule/ngb/zm/Spielemaschine.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class Spielemaschine extends Zeichenmaschine {
|
||||
|
||||
private LinkedList<Drawable> drawables;
|
||||
|
||||
private LinkedList<Updatable> updatables;
|
||||
|
||||
private GraphicsLayer mainLayer;
|
||||
|
||||
public Spielemaschine( String title ) {
|
||||
this(STD_WIDTH, STD_HEIGHT, title);
|
||||
}
|
||||
|
||||
|
||||
public Spielemaschine( int width, int height, String title ) {
|
||||
super(width, height, title, false);
|
||||
|
||||
drawables = new LinkedList<>();
|
||||
updatables = new LinkedList<>();
|
||||
|
||||
mainLayer = new GraphicsLayer();
|
||||
canvas.addLayer(mainLayer);
|
||||
}
|
||||
|
||||
public final void add( Object... pObjects ) {
|
||||
for( Object obj : pObjects ) {
|
||||
if( obj instanceof Drawable ) {
|
||||
addDrawable((Drawable) obj);
|
||||
}
|
||||
if( obj instanceof Updatable ) {
|
||||
addUpdatable((Updatable) obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final void addDrawable( Drawable pDrawable ) {
|
||||
synchronized( drawables ) {
|
||||
drawables.add(pDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
public final void addUpdatable( Updatable pUpdatable ) {
|
||||
synchronized( updatables ) {
|
||||
updatables.add(pUpdatable);
|
||||
}
|
||||
}
|
||||
|
||||
public final void remove( Object... pObjects ) {
|
||||
for( Object obj : pObjects ) {
|
||||
if( obj instanceof Drawable ) {
|
||||
removeDrawable((Drawable) obj);
|
||||
}
|
||||
if( obj instanceof Updatable ) {
|
||||
removeUpdatable((Updatable) obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final void removeDrawable( Drawable pDrawable ) {
|
||||
synchronized( drawables ) {
|
||||
drawables.remove(pDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
public final void removeUpdatable( Updatable pUpdatable ) {
|
||||
synchronized( updatables ) {
|
||||
updatables.remove(pUpdatable);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateGame( double delta ) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void update( double delta ) {
|
||||
synchronized( updatables ) {
|
||||
for( Updatable updatable : updatables ) {
|
||||
if( updatable.isActive() ) {
|
||||
updatable.update(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateGame(delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void draw() {
|
||||
mainLayer.clear();
|
||||
synchronized( drawables ) {
|
||||
for( Drawable drawable : drawables ) {
|
||||
if( drawable.isVisible() ) {
|
||||
drawable.draw(mainLayer.getGraphics());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -361,6 +361,9 @@ public class Vector extends Point2D.Double {
|
||||
}
|
||||
|
||||
public Vector div( double scalar ) {
|
||||
if( scalar == 0.0 ) {
|
||||
throw new IllegalArgumentException("Can't divide by zero.");
|
||||
}
|
||||
x /= scalar;
|
||||
y /= scalar;
|
||||
return this;
|
||||
|
||||
@@ -29,6 +29,7 @@ public class Zeichenleinwand extends Canvas {
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Zeichenleinwand mit einer festen Größe.
|
||||
*
|
||||
* @param width Breite der Zeichenleinwand.
|
||||
* @param height Höhe der Zeichenleinwand.
|
||||
*/
|
||||
@@ -42,8 +43,6 @@ public class Zeichenleinwand extends Canvas {
|
||||
layers = new LinkedList<>();
|
||||
synchronized( layers ) {
|
||||
layers.add(new ColorLayer(width, height, Constants.STD_BACKGROUND));
|
||||
layers.add(new DrawingLayer(width, height));
|
||||
layers.add(new ShapesLayer(width, height));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +52,7 @@ public class Zeichenleinwand extends Canvas {
|
||||
* Bei einer Größenänderung wird auch die Größe aller bisher hinzugefügter
|
||||
* {@link Layer Ebenen} angepasst, sodass sie die gesamte Leinwand füllen.
|
||||
*
|
||||
* @param width Neue Width der Leinwand in Pixeln.
|
||||
* @param width Neue Width der Leinwand in Pixeln.
|
||||
* @param height Neue Höhe der Leinwand in Pixeln.
|
||||
*/
|
||||
@Override
|
||||
@@ -85,16 +84,24 @@ public class Zeichenleinwand extends Canvas {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt der Zeichenleinwand eine Ebene an einer bestimmten Stelle hinzu.
|
||||
* Fügt der Zeichenleinwand eine Ebene an einem bestimmten Index hinzu. Wenn
|
||||
* der Index noch nicht existiert (also größer als die
|
||||
* {@link #getLayerCount() Anzahl der Ebenen} ist, dann wird die neue Ebene
|
||||
* als letzte eingefügt. Die aufrufende Methode kann also nicht sicher sein,
|
||||
* dass die neue Ebene am Ende wirklich am Index {@code i} steht.
|
||||
*
|
||||
* @param i Index der Ebene, beginnend mit <code>0</code>.
|
||||
* @param i Index der Ebene, beginnend mit <code>0</code>.
|
||||
* @param layer Die neue Ebene.
|
||||
*/
|
||||
public void addLayer( int i, Layer layer ) {
|
||||
if( layer != null ) {
|
||||
synchronized( layers ) {
|
||||
layer.setSize(getWidth(), getHeight());
|
||||
layers.add(i, layer);
|
||||
if( i > layers.size() ) {
|
||||
layers.add(layer);
|
||||
} else {
|
||||
layers.add(i, layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,6 +175,20 @@ public class Zeichenleinwand extends Canvas {
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean removeLayer( Layer pLayer ) {
|
||||
return layers.remove(pLayer);
|
||||
}
|
||||
|
||||
public void removeLayers( Layer... pLayers ) {
|
||||
for( Layer layer : pLayers ) {
|
||||
layers.remove(layer);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearLayers() {
|
||||
layers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine passende {@link BufferStrategy} für diese Ebene.
|
||||
*/
|
||||
@@ -193,6 +214,7 @@ public class Zeichenleinwand extends Canvas {
|
||||
|
||||
/**
|
||||
* Zeichnet den Inhalt aller {@link Layer Ebenen} in den Grafik-Kontext.
|
||||
*
|
||||
* @param graphics
|
||||
*/
|
||||
public void draw( Graphics graphics ) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.shapes.ShapesLayer;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
import schule.ngb.zm.util.Log;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
@@ -11,6 +13,8 @@ import java.awt.event.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Hauptklasse der Zeichenmaschine.
|
||||
@@ -19,17 +23,45 @@ import java.io.IOException;
|
||||
* Die Klasse übernimmt die Initialisierung eines Programmfensters und der
|
||||
* nötigen Komponenten.
|
||||
*/
|
||||
public class Zeichenmaschine extends Constants implements MouseInputListener, KeyListener {
|
||||
public class Zeichenmaschine extends Constants {
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine aus BlueJ heraus gestartet wurde.
|
||||
*/
|
||||
public static boolean IN_BLUEJ;
|
||||
public static final boolean IN_BLUEJ;
|
||||
|
||||
static {
|
||||
IN_BLUEJ = System.getProperty("java.class.path").contains("bluej");
|
||||
}
|
||||
|
||||
public static final boolean MACOS;
|
||||
|
||||
public static final boolean WINDOWS;
|
||||
|
||||
public static final boolean LINUX;
|
||||
|
||||
static {
|
||||
final String name = System.getProperty("os.name");
|
||||
|
||||
if( name.contains("Mac") ) {
|
||||
MACOS = true;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
} else if( name.contains("Windows") ) {
|
||||
MACOS = false;
|
||||
WINDOWS = true;
|
||||
LINUX = false;
|
||||
} else if( name.equals("Linux") ) { // true for the ibm vm
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = true;
|
||||
} else {
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Objektvariablen, die von Unterklassen benutzt werden können.
|
||||
*/
|
||||
@@ -106,11 +138,17 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
private boolean stop_after_update = false, run_once = true;
|
||||
|
||||
// Aktuelle Frames pro Sekunde der Zeichenmaschine.
|
||||
private int framesPerSecond;
|
||||
private int framesPerSecondInternal;
|
||||
|
||||
// Hauptthread der Zeichenmaschine.
|
||||
private Thread mainThread;
|
||||
|
||||
// Queue für geplante Aufgaben
|
||||
private DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
|
||||
|
||||
// Queue für abgefangene InputEvents
|
||||
private BlockingQueue<InputEvent> eventQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
// Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
|
||||
// oder das Zeichenfenster weiter geöffnet bleibt.
|
||||
private boolean quitAfterTeardown = false;
|
||||
@@ -125,7 +163,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* Erstellt eine neue Zeichenmaschine mit Standardwerten für Titel und
|
||||
* Größe.
|
||||
* <p>
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details.
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr
|
||||
* Details.
|
||||
*/
|
||||
public Zeichenmaschine() {
|
||||
this(APP_NAME + " " + APP_VERSION);
|
||||
@@ -135,10 +174,11 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* Erstellt eine neue Zeichenmaschine mit Standardwerten für Titel und
|
||||
* Größe.
|
||||
* <p>
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details.
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr
|
||||
* Details.
|
||||
*
|
||||
* @param run_once {@code true} beendet die Zeichenmaschine nach einem
|
||||
* Aufruf von {@code draw()}.
|
||||
* Aufruf von {@code draw()}.
|
||||
*/
|
||||
public Zeichenmaschine( boolean run_once ) {
|
||||
this(APP_NAME + " " + APP_VERSION, run_once);
|
||||
@@ -148,7 +188,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* Erstellt eine neue Zeichenmaschine mit dem angegebene Titel und
|
||||
* Standardwerten für die Größe.
|
||||
* <p>
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details.
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr
|
||||
* Details.
|
||||
*
|
||||
* @param title Der Titel, der oben im Fenster steht.
|
||||
*/
|
||||
@@ -160,11 +201,12 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* Erstellt eine neue Zeichenmaschine mit dem angegebene Titel und
|
||||
* Standardwerten für die Größe.
|
||||
* <p>
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details.
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr
|
||||
* Details.
|
||||
*
|
||||
* @param title Der Titel, der oben im Fenster steht.
|
||||
* @param run_once {@code true} beendet die Zeichenmaschine nach einem
|
||||
* Aufruf von {@code draw()}.
|
||||
* Aufruf von {@code draw()}.
|
||||
*/
|
||||
public Zeichenmaschine( String title, boolean run_once ) {
|
||||
this(STD_WIDTH, STD_HEIGHT, title, run_once);
|
||||
@@ -174,11 +216,12 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* Erstellt eine neue zeichenmaschine mit einer Leinwand der angegebenen
|
||||
* Größe und dem angegebenen Titel.
|
||||
* <p>
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details.
|
||||
* Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr
|
||||
* Details.
|
||||
*
|
||||
* @param width Breite der {@link Zeichenleinwand Zeichenleinwand}.
|
||||
* @param width Breite der {@link Zeichenleinwand Zeichenleinwand}.
|
||||
* @param height Höhe der {@link Zeichenleinwand Zeichenleinwand}.
|
||||
* @param title Der Titel, der oben im Fenster steht.
|
||||
* @param title Der Titel, der oben im Fenster steht.
|
||||
*/
|
||||
public Zeichenmaschine( int width, int height, String title ) {
|
||||
this(width, height, title, true);
|
||||
@@ -191,28 +234,30 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* Die Zeichenmaschine öffnet automatisch ein Fenster mit einer
|
||||
* {@link Zeichenleinwand}, die {@code width} Pixel breit und {@code height}
|
||||
* Pixel hoch ist. Die Leinwand hat immer eine Mindestgröße von 100x100
|
||||
* Pixeln und kann nicht größer als der aktuelle Bildschirm werden.
|
||||
* Das Fenster bekommt den angegebenen Titel.
|
||||
* Pixeln und kann nicht größer als der aktuelle Bildschirm werden. Das
|
||||
* Fenster bekommt den angegebenen Titel.
|
||||
* <p>
|
||||
* Falls {@code run_once} gleich {@code false} ist, werden
|
||||
* {@link #update(double)} und {@link #draw()} entsprechend der
|
||||
* {@link #framesPerSecond} kontinuierlich aufgerufen.
|
||||
* Falls das Programm als Unterklasse der Zeichenmaschine verfasst wird,
|
||||
* dann kann auch, {@code update(double)} überschrieben werden, damit die
|
||||
* Maschine nicht automatisch beendet.
|
||||
* {@link #framesPerSecond} kontinuierlich aufgerufen. Falls das Programm
|
||||
* als Unterklasse der Zeichenmaschine verfasst wird, dann kann auch,
|
||||
* {@code update(double)} überschrieben werden, damit die Maschine nicht
|
||||
* automatisch beendet.
|
||||
*
|
||||
* @param width Breite der {@link Zeichenleinwand Zeichenleinwand}.
|
||||
* @param width Breite der {@link Zeichenleinwand Zeichenleinwand}.
|
||||
* @param height Höhe der {@link Zeichenleinwand Zeichenleinwand}.
|
||||
* @param title Der Titel, der oben im Fenster steht.
|
||||
* @param title Der Titel, der oben im Fenster steht.
|
||||
* @param run_once {@code true} beendet die Zeichenmaschine nach einem
|
||||
* Aufruf von {@code draw()}.
|
||||
* Aufruf von {@code draw()}.
|
||||
*/
|
||||
public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
|
||||
LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
|
||||
|
||||
// Setzen des "Look&Feel"
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch( Exception e ) {
|
||||
System.err.println("Error setting the look and feel: " + e.getMessage());
|
||||
} catch( Exception ex ) {
|
||||
LOG.log(Level.SEVERE, "Error setting the look and feel: " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
|
||||
@@ -244,16 +289,22 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
|
||||
// Das Icon des Fensters ändern
|
||||
|
||||
try {
|
||||
ImageIcon icon = new ImageIcon(ImageIO.read(new File("res/icon_64.png")));
|
||||
frame.setIconImage(icon.getImage());
|
||||
|
||||
// Dock Icon in macOS setzen
|
||||
Taskbar taskbar = Taskbar.getTaskbar();
|
||||
taskbar.setIconImage(icon.getImage());
|
||||
if( MACOS ) {
|
||||
// Dock Icon in macOS setzen
|
||||
Taskbar taskbar = Taskbar.getTaskbar();
|
||||
taskbar.setIconImage(icon.getImage());
|
||||
} else {
|
||||
// Kleines Icon des Frames setzen
|
||||
frame.setIconImage(icon.getImage());
|
||||
}
|
||||
} catch( IOException e ) {
|
||||
}
|
||||
|
||||
|
||||
// Erstellen der Leinwand
|
||||
canvas = new Zeichenleinwand(width, height);
|
||||
frame.add(canvas);
|
||||
@@ -264,7 +315,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
shapes = getShapesLayer();
|
||||
|
||||
// FPS setzen
|
||||
framesPerSecond = STD_FPS;
|
||||
framesPerSecondInternal = STD_FPS;
|
||||
this.run_once = run_once;
|
||||
|
||||
// Settings der Unterklasse aufrufen, falls das Fenster vor dem Öffnen
|
||||
@@ -273,9 +324,13 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
settings();
|
||||
|
||||
// Listener hinzufügen, um auf Maus- und Tastatureingaben zu hören.
|
||||
canvas.addMouseListener(this);
|
||||
canvas.addMouseMotionListener(this);
|
||||
canvas.addKeyListener(this);
|
||||
InputListener inputListener = new InputListener();
|
||||
canvas.addMouseListener(inputListener);
|
||||
canvas.addMouseMotionListener(inputListener);
|
||||
canvas.addMouseWheelListener(inputListener);
|
||||
canvas.addKeyListener(inputListener);
|
||||
|
||||
// Programm beenden, wenn Fenster geschlossen wird
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing( WindowEvent e ) {
|
||||
@@ -343,9 +398,10 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* wiederhergestellt.
|
||||
*
|
||||
* @param pEnable Wenn {@code true}, wird der Vollbildmodus aktiviert,
|
||||
* ansonsten deaktiviert.
|
||||
* ansonsten deaktiviert.
|
||||
*/
|
||||
public final void setFullscreen( boolean pEnable ) {
|
||||
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
|
||||
if( displayDevice.isFullScreenSupported() ) {
|
||||
if( pEnable && !fullscreen ) {
|
||||
// frame.setUndecorated(true);
|
||||
@@ -354,24 +410,33 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// Update width / height
|
||||
initialWidth = width;
|
||||
initialHeight = height;
|
||||
setSize(screenWidth, screenHeight);
|
||||
changeSize(screenWidth, screenHeight);
|
||||
// Register ESC as exit fullscreen
|
||||
canvas.addKeyListener(fullscreenExitListener);
|
||||
|
||||
fullscreen = true;
|
||||
fullscreenChanged();
|
||||
} else if( !pEnable && fullscreen ) {
|
||||
fullscreen = false;
|
||||
|
||||
canvas.removeKeyListener(fullscreenExitListener);
|
||||
displayDevice.setFullScreenWindow(null);
|
||||
setSize(initialWidth, initialHeight);
|
||||
changeSize(initialWidth, initialHeight);
|
||||
frame.pack();
|
||||
// frame.setUndecorated(false);
|
||||
fullscreenChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFullscreen() {
|
||||
Window win = displayDevice.getFullScreenWindow();
|
||||
return fullscreen && win != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den aktuellen {@link Options.AppState Zustand} der Zeichenmaschine zurück.
|
||||
* Gibt den aktuellen {@link Options.AppState Zustand} der Zeichenmaschine
|
||||
* zurück.
|
||||
*
|
||||
* @return Der Zustand der Zeichenmaschine.
|
||||
*/
|
||||
@@ -416,8 +481,9 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* Pausiert die Ausführung von {@link #update(double)} und {@link #draw()}
|
||||
* nach dem nächsten vollständigen Frame.
|
||||
* <p>
|
||||
* Die Zeichenmaschine wechselt in den Zustand {@link Options.AppState#PAUSED},
|
||||
* sobald der aktuelle Frame beendet wurde.
|
||||
* Die Zeichenmaschine wechselt in den Zustand
|
||||
* {@link Options.AppState#PAUSED}, sobald der aktuelle Frame beendet
|
||||
* wurde.
|
||||
*/
|
||||
public final void pause() {
|
||||
pause_pending = true;
|
||||
@@ -436,6 +502,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
|
||||
/**
|
||||
* Prüft, ob die Zeichenmaschine gerade pausiert ist.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public final boolean isPaused() {
|
||||
@@ -446,15 +513,32 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* Stoppt die Zeichenmaschine.
|
||||
* <p>
|
||||
* Nachdem der aktuelle Frame gezeichnet wurde wechselt die Zeichenmaschine
|
||||
* in den Zustand {@link Options.AppState#STOPPED} und ruft {@link #teardown()}
|
||||
* auf. Nachdem {@code teardown()} ausgeführt wurde wechselt der Zustand zu
|
||||
* {@link Options.AppState#TERMINATED}. Das Zeichenfenster bleibt weiter
|
||||
* geöffnet.
|
||||
* in den Zustand {@link Options.AppState#STOPPED} und ruft
|
||||
* {@link #teardown()} auf. Nachdem {@code teardown()} ausgeführt wurde
|
||||
* wechselt der Zustand zu {@link Options.AppState#TERMINATED}. Das
|
||||
* Zeichenfenster bleibt weiter geöffnet.
|
||||
*/
|
||||
public final void stop() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt interne Aufräumarbeiten durch.
|
||||
* <p>
|
||||
* Wird nach dem {@link #stop() Stopp} der Zeichenmaschine aufgerufen und
|
||||
* verbleibende Threads, Tasks, etc. zu stoppen und aufzuräumen. Die
|
||||
* Äquivalente Methode für Unterklassen ist {@link #teardown()}, die direkt
|
||||
* vor {@code cleanup()} aufgerufen wird.
|
||||
*/
|
||||
private void cleanup() {
|
||||
// Alle noch nicht ausgelösten Events werden entfernt
|
||||
eventQueue.clear();
|
||||
// Alle noch nicht ausgeführten Tasks werden entfernt
|
||||
taskQueue.clear();
|
||||
// TaskRunner stoppen
|
||||
TaskRunner.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Beendet die Zeichenmaschine vollständig.
|
||||
* <p>
|
||||
@@ -480,8 +564,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
}
|
||||
|
||||
/**
|
||||
* Beendet das Programm. Falls {@code exit} gleich {@code true} ist,
|
||||
* wird die komplette VM beendet.
|
||||
* Beendet das Programm. Falls {@code exit} gleich {@code true} ist, wird
|
||||
* die komplette VM beendet.
|
||||
*
|
||||
* @param exit Ob die VM beendet werden soll.
|
||||
* @see System#exit(int)
|
||||
@@ -496,6 +580,28 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interne Methode um die Größe der Zeichenfläche zu ändern.
|
||||
* <p>
|
||||
* Die Methode berücksichtigt nicht den Zustand des Fensters (z.B.
|
||||
* Vollbildmodus) und geht davon aus, dass die aufrufende Methode
|
||||
* sichergestellt hat, dass eine Änderung der Größe der Zeichenfläche
|
||||
* zulässig und sinnvoll ist.
|
||||
*
|
||||
* @param newWidth Neue Breite der Zeichenleinwand.
|
||||
* @param newHeight Neue Höhe der Zeichenleinwand.
|
||||
* @see #setSize(int, int)
|
||||
* @see #setFullscreen(boolean)
|
||||
*/
|
||||
private void changeSize( int newWidth, int newHeight ) {
|
||||
width = Math.min(Math.max(newWidth, 100), screenWidth);
|
||||
height = Math.min(Math.max(newHeight, 100), screenHeight);
|
||||
|
||||
if( canvas != null ) {
|
||||
canvas.setSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ändert die Größe der {@link Zeichenleinwand}.
|
||||
*
|
||||
@@ -509,11 +615,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
initialHeight = Math.min(Math.max(height, 100), screenHeight);
|
||||
setFullscreen(false);
|
||||
} else {
|
||||
if( canvas != null ) {
|
||||
canvas.setSize(width, height);
|
||||
}
|
||||
this.width = Math.min(Math.max(width, 100), screenWidth);
|
||||
this.height = Math.min(Math.max(height, 100), screenHeight);
|
||||
changeSize(width, height);
|
||||
|
||||
//frame.setSize(width, height);
|
||||
frame.pack();
|
||||
@@ -557,8 +659,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt der {@link Zeichenleinwand} eine weitere {@link Layer Ebene}
|
||||
* hinzu.
|
||||
* Fügt der {@link Zeichenleinwand} eine weitere {@link Layer Ebene} hinzu.
|
||||
*
|
||||
* @param layer Die neue Ebene.
|
||||
*/
|
||||
@@ -605,9 +706,9 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die {@link ColorLayer Ebene} mit der Hintergrundfarbe zurück. Gibt es
|
||||
* keine solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand}
|
||||
* hinzugefügt.
|
||||
* Gibt die {@link ColorLayer Ebene} mit der Hintergrundfarbe zurück. Gibt
|
||||
* es keine solche Ebene, so wird eine erstellt und der
|
||||
* {@link Zeichenleinwand} hinzugefügt.
|
||||
* <p>
|
||||
* In der Regel sollte dies dieselbe Ebene sein wie {@link #background}.
|
||||
*
|
||||
@@ -623,8 +724,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Standard-{@link DrawingLayer Zeichenebene} zurück. Gibt es
|
||||
* keine solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand}
|
||||
* Gibt die Standard-{@link DrawingLayer Zeichenebene} zurück. Gibt es keine
|
||||
* solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand}
|
||||
* hinzugefügt.
|
||||
* <p>
|
||||
* In der Regel sollte dies dieselbe Ebene sein wie {@link #drawing}.
|
||||
@@ -641,8 +742,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Standard-{@link ShapesLayer Formenebene} zurück. Gibt es
|
||||
* keine solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand}
|
||||
* Gibt die Standard-{@link ShapesLayer Formenebene} zurück. Gibt es keine
|
||||
* solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand}
|
||||
* hinzugefügt.
|
||||
* <p>
|
||||
* In der Regel sollte dies dieselbe Ebene sein wie {@link #shapes}.
|
||||
@@ -653,7 +754,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
ShapesLayer layer = canvas.getLayer(ShapesLayer.class);
|
||||
if( layer == null ) {
|
||||
layer = new ShapesLayer(getWidth(), getHeight());
|
||||
canvas.addLayer(layer);
|
||||
canvas.addLayer(2, layer);
|
||||
}
|
||||
return layer;
|
||||
}
|
||||
@@ -664,7 +765,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* @return Angepeilte Frames pro Sekunde
|
||||
*/
|
||||
public final int getFramesPerSecond() {
|
||||
return framesPerSecond;
|
||||
return framesPerSecondInternal;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -673,7 +774,13 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* @param pFramesPerSecond Neue FPS.
|
||||
*/
|
||||
public final void setFramesPerSecond( int pFramesPerSecond ) {
|
||||
framesPerSecond = pFramesPerSecond;
|
||||
if( pFramesPerSecond > 0 ) {
|
||||
framesPerSecondInternal = pFramesPerSecond;
|
||||
} else {
|
||||
framesPerSecondInternal = 1;
|
||||
// Logger ...
|
||||
}
|
||||
framesPerSecond = framesPerSecondInternal;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -718,9 +825,9 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
|
||||
/**
|
||||
* Erstellt eine Momentanaufnahme des aktuellen Inhalts der
|
||||
* {@link Zeichenleinwand} und erstellt daraus eine {@link ImageLayer Bildebene}.
|
||||
* Die Ebene wird automatisch der {@link Zeichenleinwand} vor dem
|
||||
* {@link #background} hinzugefügt.
|
||||
* {@link Zeichenleinwand} und erstellt daraus eine
|
||||
* {@link ImageLayer Bildebene}. Die Ebene wird automatisch der
|
||||
* {@link Zeichenleinwand} vor dem {@link #background} hinzugefügt.
|
||||
*
|
||||
* @return Die neue Bildebene.
|
||||
*/
|
||||
@@ -806,8 +913,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt den Mauszeiger wieder an, falls er zuvor {@link #hideCursor() versteckt}
|
||||
* wurde.
|
||||
* Zeigt den Mauszeiger wieder an, falls er zuvor
|
||||
* {@link #hideCursor() versteckt} wurde.
|
||||
* <p>
|
||||
* Nach dem Aufruf gilt {@code cursorVisible == true}.
|
||||
* <p>
|
||||
@@ -846,6 +953,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* <pre>
|
||||
* setCursor(Cursor.HAND_CURSOR);
|
||||
* </pre>
|
||||
*
|
||||
* @param pPredefinedCursor Eine der Cursor-Konstanten.
|
||||
* @see Cursor
|
||||
*/
|
||||
@@ -854,13 +962,14 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Mauszeiger auf das übergebenen Cursor-Objekt.
|
||||
* Wenn {@code pCursor} {@code null} ist, wird der Mauszeiger unsichtbar
|
||||
* gemacht (dies ist dasselbe wie der Aufruf von {@link #hideCursor()}).
|
||||
* Setzt den Mauszeiger auf das übergebenen Cursor-Objekt. Wenn
|
||||
* {@code pCursor} {@code null} ist, wird der Mauszeiger unsichtbar gemacht
|
||||
* (dies ist dasselbe wie der Aufruf von {@link #hideCursor()}).
|
||||
*
|
||||
* @param pCursor Ein Cursor-Objekt oder {@code null}.
|
||||
* <p>
|
||||
* Nach Aufruf der Methode kann über {@link #cursorVisible} abgefragt werden,
|
||||
* ob der Cursor zurzeit sichtbar ist oder nicht.
|
||||
* <p>
|
||||
* Nach Aufruf der Methode kann über {@link #cursorVisible} abgefragt
|
||||
* werden, ob der Cursor zurzeit sichtbar ist oder nicht.
|
||||
*/
|
||||
public final void setCursor( Cursor pCursor ) {
|
||||
if( pCursor == null && cursorVisible ) {
|
||||
@@ -887,7 +996,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
*/
|
||||
|
||||
/**
|
||||
* Die Settings werden einmal beim Erstellten der Zeichenmaschine aufgerufen.
|
||||
* Die Settings werden einmal beim Erstellten der Zeichenmaschine
|
||||
* aufgerufen.
|
||||
* <p>
|
||||
* {@code settings()} wird nur selten benötigt, wenn das Zeichenfenster
|
||||
*/
|
||||
@@ -912,9 +1022,9 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
* Aktualisierungen abhängig von der echten Verzögerung zwischen zwei Frames
|
||||
* zu berechnen.
|
||||
* <p>
|
||||
* {@code delta} wird in Sekunden angegeben. Um eine Form zum Beispiel
|
||||
* um {@code 50} Pixel pro Sekunde in {@code x}-Richtung zu bewegen,
|
||||
* kann so vorgegangen werden:
|
||||
* {@code delta} wird in Sekunden angegeben. Um eine Form zum Beispiel um
|
||||
* {@code 50} Pixel pro Sekunde in {@code x}-Richtung zu bewegen, kann so
|
||||
* vorgegangen werden:
|
||||
* <pre>
|
||||
* shape.move(50*delta, 0.0);
|
||||
* </pre>
|
||||
@@ -928,23 +1038,24 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
|
||||
/**
|
||||
* {@code draw()} wird einmal pro Frame aufgerufen. Bei einer
|
||||
* {@link #getFramesPerSecond() Framerate} von 60 also in etwa 60-Mal
|
||||
* pro Sekunde. In der {@code draw}-Methode wird der Inhalt der Ebenen
|
||||
* {@link #getFramesPerSecond() Framerate} von 60 also in etwa 60-Mal pro
|
||||
* Sekunde. In der {@code draw}-Methode wird der Inhalt der Ebenen
|
||||
* manipuliert und deren Inhalte gezeichnet. Am Ende des Frames werden alle
|
||||
* Ebenen auf die {@link Zeichenleinwand} übertragen.
|
||||
* <p>
|
||||
* {@code draw()} stellt die wichtigste Methode für eine Zeichenmaschine dar,
|
||||
* da hier die Zeichnung des Programms erstellt wird.
|
||||
* {@code draw()} stellt die wichtigste Methode für eine Zeichenmaschine
|
||||
* dar, da hier die Zeichnung des Programms erstellt wird.
|
||||
*/
|
||||
public void draw() {
|
||||
running = !stop_after_update;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code teardown()} wird aufgerufen, sobald die Schleife des Hauptprogramms
|
||||
* beendet wurde. Dies passiert entweder nach dem ersten Durchlauf (wenn keine
|
||||
* eigene {@link #update(double)} erstellt wurde), nach dem Aufruf von
|
||||
* {@link #stop()} oder nachdem das {@link Zeichenfenster} geschlossen wurde.
|
||||
* {@code teardown()} wird aufgerufen, sobald die Schleife des
|
||||
* Hauptprogramms beendet wurde. Dies passiert entweder nach dem ersten
|
||||
* Durchlauf (wenn keine eigene {@link #update(double)} erstellt wurde),
|
||||
* nach dem Aufruf von {@link #stop()} oder nachdem das
|
||||
* {@link Zeichenfenster} geschlossen wurde.
|
||||
* <p>
|
||||
* In {@code teardown()} kann zum Beispiel der Abschlussbildschirm eines
|
||||
* Spiels oder der Abspann einer Animation angezeigt werden, oder mit
|
||||
@@ -954,12 +1065,121 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
/*
|
||||
* Task scheduling
|
||||
*/
|
||||
|
||||
public void scheduleTask( Runnable runnable, int delay ) {
|
||||
taskQueue.add(new DelayedTask(delay, runnable));
|
||||
}
|
||||
|
||||
public void scheduleTask( Runnable runnable, int delay, boolean concurrent ) {
|
||||
DelayedTask task = new DelayedTask(delay, runnable);
|
||||
task.concurrent = concurrent;
|
||||
taskQueue.add(task);
|
||||
}
|
||||
|
||||
private void runTasks() {
|
||||
synchronized( taskQueue ) {
|
||||
DelayedTask task = taskQueue.poll();
|
||||
while( task != null ) {
|
||||
if( task.concurrent ) {
|
||||
// SwingUtilities.invokeLater(task.runnable);
|
||||
TaskRunner.run(task.runnable);
|
||||
} else {
|
||||
task.runnable.run();
|
||||
}
|
||||
|
||||
task = taskQueue.poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Mouse handling
|
||||
*/
|
||||
@Override
|
||||
public final void mouseClicked( MouseEvent e ) {
|
||||
mouseEvent = e;
|
||||
private void enqueueEvent( InputEvent evt ) {
|
||||
eventQueue.add(evt);
|
||||
|
||||
if( isPaused() ) {
|
||||
dispatchEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchEvents() {
|
||||
synchronized( eventQueue ) {
|
||||
while( !eventQueue.isEmpty() ) {
|
||||
InputEvent evt = eventQueue.poll();
|
||||
|
||||
switch( evt.getID() ) {
|
||||
case KeyEvent.KEY_TYPED:
|
||||
case KeyEvent.KEY_PRESSED:
|
||||
case KeyEvent.KEY_RELEASED:
|
||||
handleKeyEvent((KeyEvent) evt);
|
||||
break;
|
||||
|
||||
case MouseEvent.MOUSE_CLICKED:
|
||||
case MouseEvent.MOUSE_PRESSED:
|
||||
case MouseEvent.MOUSE_RELEASED:
|
||||
case MouseEvent.MOUSE_MOVED:
|
||||
case MouseEvent.MOUSE_DRAGGED:
|
||||
case MouseEvent.MOUSE_WHEEL:
|
||||
handleMouseEvent((MouseEvent) evt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleKeyEvent( KeyEvent evt ) {
|
||||
keyEvent = evt;
|
||||
key = evt.getKeyChar();
|
||||
keyCode = evt.getKeyCode();
|
||||
|
||||
switch( evt.getID() ) {
|
||||
case KeyEvent.KEY_TYPED:
|
||||
keyTyped(evt);
|
||||
break;
|
||||
case KeyEvent.KEY_PRESSED:
|
||||
keyPressed = true;
|
||||
keyPressed(evt);
|
||||
break;
|
||||
case KeyEvent.KEY_RELEASED:
|
||||
keyPressed = false;
|
||||
keyReleased(evt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMouseEvent( MouseEvent evt ) {
|
||||
mouseEvent = evt;
|
||||
|
||||
switch( evt.getID() ) {
|
||||
case MouseEvent.MOUSE_CLICKED:
|
||||
mouseClicked(evt);
|
||||
break;
|
||||
case MouseEvent.MOUSE_PRESSED:
|
||||
mousePressed = true;
|
||||
mouseButton = evt.getButton();
|
||||
mousePressed(evt);
|
||||
break;
|
||||
case MouseEvent.MOUSE_RELEASED:
|
||||
mousePressed = false;
|
||||
mouseButton = NOBUTTON;
|
||||
mousePressed(evt);
|
||||
break;
|
||||
case MouseEvent.MOUSE_DRAGGED:
|
||||
//saveMousePosition(evt);
|
||||
mouseDragged(evt);
|
||||
break;
|
||||
case MouseEvent.MOUSE_MOVED:
|
||||
//saveMousePosition(evt);
|
||||
mouseMoved(evt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseClicked( MouseEvent e ) {
|
||||
mouseClicked();
|
||||
}
|
||||
|
||||
@@ -967,11 +1187,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void mousePressed( MouseEvent e ) {
|
||||
mouseEvent = e;
|
||||
mousePressed = true;
|
||||
mouseButton = e.getButton();
|
||||
public void mousePressed( MouseEvent e ) {
|
||||
mousePressed();
|
||||
}
|
||||
|
||||
@@ -979,11 +1195,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void mouseReleased( MouseEvent e ) {
|
||||
mouseEvent = e;
|
||||
mousePressed = false;
|
||||
mouseButton = NOBUTTON;
|
||||
public void mouseReleased( MouseEvent e ) {
|
||||
mouseReleased();
|
||||
}
|
||||
|
||||
@@ -991,19 +1203,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void mouseEntered( MouseEvent e ) {
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void mouseExited( MouseEvent e ) {
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void mouseDragged( MouseEvent e ) {
|
||||
mouseEvent = e;
|
||||
public void mouseDragged( MouseEvent e ) {
|
||||
mouseDragged();
|
||||
}
|
||||
|
||||
@@ -1011,9 +1211,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void mouseMoved( MouseEvent e ) {
|
||||
mouseEvent = e;
|
||||
public void mouseMoved( MouseEvent e ) {
|
||||
mouseMoved();
|
||||
}
|
||||
|
||||
@@ -1026,8 +1224,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
pmouseX = mouseX;
|
||||
pmouseY = mouseY;
|
||||
|
||||
mouseX = event.getX();
|
||||
mouseY = event.getY();
|
||||
mouseX = cmouseX;
|
||||
mouseY = cmouseY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1045,9 +1243,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
/*
|
||||
* Keyboard handling
|
||||
*/
|
||||
@Override
|
||||
public final void keyTyped( KeyEvent e ) {
|
||||
saveKeys(e);
|
||||
public void keyTyped( KeyEvent e ) {
|
||||
keyTyped();
|
||||
}
|
||||
|
||||
@@ -1055,10 +1251,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void keyPressed( KeyEvent e ) {
|
||||
saveKeys(e);
|
||||
keyPressed = true;
|
||||
public void keyPressed( KeyEvent e ) {
|
||||
keyPressed();
|
||||
}
|
||||
|
||||
@@ -1066,10 +1259,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void keyReleased( KeyEvent e ) {
|
||||
saveKeys(e);
|
||||
keyPressed = false;
|
||||
public void keyReleased( KeyEvent e ) {
|
||||
keyReleased();
|
||||
}
|
||||
|
||||
@@ -1077,12 +1267,15 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
private final void saveKeys( KeyEvent event ) {
|
||||
keyEvent = event;
|
||||
key = event.getKeyChar();
|
||||
keyCode = event.getKeyCode();
|
||||
// Window changes
|
||||
public void fullscreenChanged() {
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
////
|
||||
// Zeichenthread
|
||||
////
|
||||
|
||||
class Zeichenthread extends Thread {
|
||||
|
||||
@Override
|
||||
@@ -1125,12 +1318,16 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
// canvas.invalidate();
|
||||
// frame.repaint();
|
||||
}
|
||||
|
||||
dispatchEvents();
|
||||
}
|
||||
|
||||
runTasks();
|
||||
|
||||
// delta time in ns
|
||||
long afterTime = System.nanoTime();
|
||||
long dt = afterTime - beforeTime;
|
||||
long sleep = ((1000000000L / framesPerSecond) - dt) - overslept;
|
||||
long sleep = ((1000000000L / framesPerSecondInternal) - dt) - overslept;
|
||||
|
||||
|
||||
if( sleep > 0 ) {
|
||||
@@ -1143,13 +1340,13 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
overslept = (System.nanoTime() - afterTime) - sleep;
|
||||
} else {
|
||||
overslept = 0L;
|
||||
|
||||
}
|
||||
|
||||
_tick += 1;
|
||||
_runtime = System.currentTimeMillis() - start;
|
||||
tick = _tick;
|
||||
runtime = _runtime;
|
||||
framesPerSecond = framesPerSecondInternal;
|
||||
|
||||
if( pause_pending ) {
|
||||
state = Options.AppState.PAUSED;
|
||||
@@ -1159,6 +1356,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
state = Options.AppState.STOPPED;
|
||||
|
||||
teardown();
|
||||
cleanup();
|
||||
state = Options.AppState.TERMINATED;
|
||||
|
||||
if( quitAfterTeardown ) {
|
||||
@@ -1184,4 +1382,94 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
|
||||
|
||||
}
|
||||
|
||||
class DelayedTask implements Delayed {
|
||||
|
||||
long startTime; // in ms
|
||||
|
||||
Runnable runnable;
|
||||
|
||||
boolean concurrent = false;
|
||||
|
||||
public DelayedTask( int delay, Runnable runnable ) {
|
||||
this.startTime = System.currentTimeMillis() + delay;
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDelay( TimeUnit unit ) {
|
||||
int diff = (int) (startTime - System.currentTimeMillis());
|
||||
return unit.convert(diff, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo( Delayed o ) {
|
||||
return (int) (startTime - ((DelayedTask) o).startTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e ) {
|
||||
enqueueEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed( MouseEvent e ) {
|
||||
enqueueEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased( MouseEvent e ) {
|
||||
enqueueEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered( MouseEvent e ) {
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited( MouseEvent e ) {
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged( MouseEvent e ) {
|
||||
cmouseX = e.getX();
|
||||
cmouseY = e.getY();
|
||||
enqueueEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved( MouseEvent e ) {
|
||||
cmouseX = e.getX();
|
||||
cmouseY = e.getY();
|
||||
enqueueEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped( KeyEvent e ) {
|
||||
enqueueEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed( KeyEvent e ) {
|
||||
enqueueEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased( KeyEvent e ) {
|
||||
enqueueEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved( MouseWheelEvent e ) {
|
||||
// enqueueEvent(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(Zeichenmaschine.class);
|
||||
|
||||
}
|
||||
|
||||
191
src/schule/ngb/zm/media/Music.java
Normal file
191
src/schule/ngb/zm/media/Music.java
Normal file
@@ -0,0 +1,191 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class Music {
|
||||
|
||||
// size of the byte buffer used to read/write the audio stream
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
|
||||
private boolean playing = false;
|
||||
|
||||
private boolean looping = false;
|
||||
|
||||
private String audioSource;
|
||||
|
||||
private AudioInputStream audioStream;
|
||||
|
||||
private SourceDataLine audioLine;
|
||||
|
||||
private float volume = 1.0f;
|
||||
|
||||
public Music( String source ) {
|
||||
this.audioSource = source;
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return playing;
|
||||
}
|
||||
|
||||
public boolean isLooping() {
|
||||
if( !playing ) {
|
||||
looping = false;
|
||||
}
|
||||
return looping;
|
||||
}
|
||||
|
||||
public void setVolume( double volume ) {
|
||||
this.volume = (float) volume;
|
||||
if( audioLine != null ) {
|
||||
applyVolume();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyVolume() {
|
||||
FloatControl gainControl =
|
||||
(FloatControl) audioLine.getControl(FloatControl.Type.MASTER_GAIN);
|
||||
|
||||
float vol = 20f * (float) Math.log10(volume);
|
||||
// vol = (float) Constants.limit(vol, gainControl.getMinimum(), gainControl.getMaximum());
|
||||
gainControl.setValue(vol);
|
||||
}
|
||||
|
||||
public void playOnce() {
|
||||
if( openLine() ) {
|
||||
TaskRunner.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stream();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void playOnceAndWait() {
|
||||
if( openLine() ) {
|
||||
stream();
|
||||
}
|
||||
}
|
||||
|
||||
public void loop() {
|
||||
looping = true;
|
||||
playOnce();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
playing = false;
|
||||
looping = false;
|
||||
dispose();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if( audioLine != null ) {
|
||||
if( audioLine.isRunning() ) {
|
||||
playing = false;
|
||||
audioLine.stop();
|
||||
}
|
||||
if( audioLine.isOpen() ) {
|
||||
audioLine.drain();
|
||||
audioLine.close();
|
||||
|
||||
}
|
||||
}
|
||||
try {
|
||||
if( audioStream != null ) {
|
||||
audioStream.close();
|
||||
}
|
||||
} catch( IOException ex ) {}
|
||||
|
||||
audioLine = null;
|
||||
audioStream = null;
|
||||
}
|
||||
|
||||
private void stream() {
|
||||
audioLine.start();
|
||||
playing = true;
|
||||
|
||||
byte[] bytesBuffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead = -1;
|
||||
|
||||
try {
|
||||
while (playing && (bytesRead = audioStream.read(bytesBuffer)) != -1) {
|
||||
audioLine.write(bytesBuffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
audioLine.drain();
|
||||
audioLine.stop();
|
||||
} catch( IOException ex ) {
|
||||
LOGGER.warning("Error while playing Music source <" + audioSource + ">");
|
||||
LOGGER.throwing("Music", "stream", ex);
|
||||
}
|
||||
|
||||
// Wait for the remaining audio to play
|
||||
/*while( audioLine.isRunning() ) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch( InterruptedException ex ) {
|
||||
// Just keep waiting ...
|
||||
}
|
||||
}*/
|
||||
|
||||
playing = false;
|
||||
streamingStopped();
|
||||
}
|
||||
|
||||
private boolean openLine() {
|
||||
if( audioLine != null ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream in = ResourceStreamProvider.getResourceStream(audioSource);
|
||||
if( in != null ) {
|
||||
final AudioInputStream inStream = AudioSystem.getAudioInputStream(in);
|
||||
AudioFormat format = inStream.getFormat();
|
||||
|
||||
final int ch = format.getChannels();
|
||||
final float rate = format.getSampleRate();
|
||||
AudioFormat outFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch*2, rate, false);
|
||||
|
||||
DataLine.Info info = new DataLine.Info(SourceDataLine.class, outFormat);
|
||||
|
||||
audioLine = (SourceDataLine) AudioSystem.getLine(info);
|
||||
audioLine.open(outFormat);
|
||||
applyVolume();
|
||||
|
||||
audioStream = AudioSystem.getAudioInputStream(outFormat, inStream);
|
||||
return true;
|
||||
} else {
|
||||
LOGGER.warning("Sound source <" + audioSource + "> could not be played: No audio source found.");
|
||||
}
|
||||
} catch( UnsupportedAudioFileException ex ) {
|
||||
LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: The specified audio file is not supported.", ex);
|
||||
} catch( LineUnavailableException ex ) {
|
||||
LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Audio line for playing back is unavailable.", ex);
|
||||
} catch( IOException ex ) {
|
||||
LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Error playing the audio file.", ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void streamingStopped() {
|
||||
playing = false;
|
||||
dispose();
|
||||
|
||||
if( looping ) {
|
||||
playOnce();
|
||||
}
|
||||
}
|
||||
|
||||
//private static final Logger LOGGER = Logger.getLogger("schule.ngb.zm.media.Music");
|
||||
private static final Logger LOGGER = Logger.getLogger(Music.class.getName());
|
||||
|
||||
}
|
||||
204
src/schule/ngb/zm/media/Sound.java
Normal file
204
src/schule/ngb/zm/media/Sound.java
Normal file
@@ -0,0 +1,204 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class Sound {
|
||||
|
||||
private boolean playing = false;
|
||||
|
||||
private boolean looping = false;
|
||||
|
||||
private String audioSource;
|
||||
|
||||
private Clip audioClip;
|
||||
|
||||
private boolean disposeAfterPlay = false;
|
||||
|
||||
private float volume = 1.0f;
|
||||
|
||||
public Sound( String source ) {
|
||||
this.audioSource = source;
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
// return audioClip != null && audioClip.isRunning();
|
||||
return playing;
|
||||
}
|
||||
|
||||
public boolean isLooping() {
|
||||
if( !playing ) {
|
||||
looping = false;
|
||||
}
|
||||
return looping;
|
||||
}
|
||||
|
||||
public void setVolume( double volume ) {
|
||||
this.volume = (float) volume;
|
||||
if( audioClip != null ) {
|
||||
applyVolume();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyVolume() {
|
||||
FloatControl gainControl =
|
||||
(FloatControl) audioClip.getControl(FloatControl.Type.MASTER_GAIN);
|
||||
|
||||
float vol = 20f * (float) Math.log10(volume);
|
||||
// vol = (float) Constants.limit(vol, gainControl.getMinimum(), gainControl.getMaximum());
|
||||
gainControl.setValue(vol);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
looping = false;
|
||||
if( audioClip.isRunning() ) {
|
||||
audioClip.stop();
|
||||
}
|
||||
playing = false;
|
||||
}
|
||||
|
||||
public void play() {
|
||||
if( this.openClip() ) {
|
||||
audioClip.start();
|
||||
playing = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void playOnce() {
|
||||
disposeAfterPlay = true;
|
||||
play();
|
||||
}
|
||||
|
||||
public void playOnceAndWait() {
|
||||
disposeAfterPlay = true;
|
||||
playAndWait();
|
||||
}
|
||||
|
||||
public void playAndWait() {
|
||||
this.play();
|
||||
|
||||
long audioLen = audioClip.getMicrosecondLength();
|
||||
while( playing ) {
|
||||
try {
|
||||
long ms = (audioLen - audioClip.getMicrosecondPosition()) / 1000L;
|
||||
Thread.sleep(ms);
|
||||
} catch( InterruptedException ex ) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
audioClip.close();
|
||||
}
|
||||
|
||||
public void loop() {
|
||||
loop(Clip.LOOP_CONTINUOUSLY);
|
||||
}
|
||||
|
||||
public void loop( int count ) {
|
||||
int loopCount = count;
|
||||
if( loopCount != Clip.LOOP_CONTINUOUSLY ) {
|
||||
if( loopCount <= 0 ) {
|
||||
return;
|
||||
}
|
||||
// Adjust Number of loops
|
||||
loopCount -= 1;
|
||||
}
|
||||
if( openClip() ) {
|
||||
looping = true;
|
||||
audioClip.loop(loopCount);
|
||||
playing = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if( audioClip != null ) {
|
||||
if( audioClip.isRunning() ) {
|
||||
audioClip.stop();
|
||||
}
|
||||
audioClip.close();
|
||||
audioClip = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean openClip() {
|
||||
if( audioClip != null ) {
|
||||
audioClip.setFramePosition(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream in = ResourceStreamProvider.getResourceStream(audioSource);
|
||||
if( in != null ) {
|
||||
AudioInputStream audioStream = AudioSystem.getAudioInputStream(in);
|
||||
AudioFormat format = audioStream.getFormat();
|
||||
DataLine.Info info = new DataLine.Info(Clip.class, format);
|
||||
|
||||
audioClip = (Clip) AudioSystem.getLine(info);
|
||||
audioClip.addLineListener(new LineListener() {
|
||||
@Override
|
||||
public void update( LineEvent event ) {
|
||||
if( event.getType() == LineEvent.Type.STOP ) {
|
||||
playbackStopped();
|
||||
}
|
||||
}
|
||||
});
|
||||
audioClip.open(audioStream);
|
||||
applyVolume();
|
||||
return true;
|
||||
} else {
|
||||
LOGGER.warning("Sound source " + audioSource + " could not be played: No audio source found.");
|
||||
}
|
||||
} catch( UnsupportedAudioFileException ex ) {
|
||||
LOGGER.warning("Sound source " + audioSource + " could not be played: The specified audio file is not supported.");
|
||||
LOGGER.throwing("Sound", "openClip", ex);
|
||||
} catch( LineUnavailableException ex ) {
|
||||
LOGGER.warning("Sound source " + audioSource + " could not be played: Audio line for playing back is unavailable.");
|
||||
LOGGER.throwing("Sound", "openClip", ex);
|
||||
} catch( IOException ex ) {
|
||||
LOGGER.warning("Sound source " + audioSource + " could not be played: Error playing the audio file.");
|
||||
LOGGER.throwing("Sound", "openClip", ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*@Override
|
||||
public void update( LineEvent event ) {
|
||||
LineEvent.Type type = event.getType();
|
||||
|
||||
if( type == LineEvent.Type.START ) {
|
||||
playing = true;
|
||||
} else if( type == LineEvent.Type.STOP ) {
|
||||
playing = false;
|
||||
if( disposeAfterPlay ) {
|
||||
this.dispose();
|
||||
disposeAfterPlay = false;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
private void playbackStopped() {
|
||||
playing = false;
|
||||
if( disposeAfterPlay ) {
|
||||
this.dispose();
|
||||
disposeAfterPlay = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public void addLineListener( LineListener listener ) {
|
||||
if( audioClip == null ) {
|
||||
openClip();
|
||||
}
|
||||
if( audioClip != null ) {
|
||||
audioClip.addLineListener(listener);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(Sound.class.getName());
|
||||
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public abstract class FilledShape extends StrokedShape {
|
||||
}
|
||||
|
||||
public void setFillColor( Color color, int alpha ) {
|
||||
fillColor = new Color(color, alpha);
|
||||
setFillColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
public void setFillColor( int gray ) {
|
||||
@@ -23,7 +23,7 @@ public abstract class FilledShape extends StrokedShape {
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
fillColor = null;
|
||||
setFillColor(null);
|
||||
}
|
||||
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
|
||||
@@ -14,9 +14,9 @@ public class Picture extends Shape {
|
||||
|
||||
private BufferedImage image;
|
||||
|
||||
private double width;
|
||||
private double imgWidth;
|
||||
|
||||
private double height;
|
||||
private double imgHeight;
|
||||
|
||||
public Picture( String source ) {
|
||||
this(0, 0, source);
|
||||
@@ -30,8 +30,8 @@ public class Picture extends Shape {
|
||||
throw new IllegalArgumentException("Could not initialize image from source " + source);
|
||||
}
|
||||
|
||||
width = image.getWidth();
|
||||
height = image.getHeight();
|
||||
imgWidth = image.getWidth();
|
||||
imgHeight = image.getHeight();
|
||||
this.anchor = Options.Direction.CENTER;
|
||||
}
|
||||
|
||||
@@ -55,25 +55,25 @@ public class Picture extends Shape {
|
||||
g.drawImage(pic.image, 0, 0, null);
|
||||
g.dispose();
|
||||
|
||||
width = image.getWidth();
|
||||
height = image.getHeight();
|
||||
imgWidth = image.getWidth();
|
||||
imgHeight = image.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return width;
|
||||
return imgWidth;
|
||||
}
|
||||
|
||||
public void setWidth( double width ) {
|
||||
scale(width / this.width);
|
||||
scale(width / this.imgWidth);
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return height;
|
||||
return imgHeight;
|
||||
}
|
||||
|
||||
public void setHeight( double height ) {
|
||||
scale(height / this.height);
|
||||
scale(height / this.imgHeight);
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
@@ -83,13 +83,13 @@ public class Picture extends Shape {
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
width *= factor;
|
||||
height *= factor;
|
||||
imgWidth *= factor;
|
||||
imgHeight *= factor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new Rectangle2D.Double(0, 0, width, height);
|
||||
return new Rectangle2D.Double(0, 0, imgWidth, imgHeight);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -161,8 +161,8 @@ public class Picture extends Shape {
|
||||
}
|
||||
|
||||
AffineTransform current = graphics.getTransform();
|
||||
graphics.transform(getTransform());
|
||||
graphics.drawImage(image, 0, 0, (int) width, (int) height, null);
|
||||
graphics.transform(transform);
|
||||
graphics.drawImage(image, 0, 0, (int) imgWidth, (int) imgHeight, null);
|
||||
graphics.setTransform(current);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ public abstract class Shape extends FilledShape {
|
||||
* Die Breite einer Form ist immer die Breite ihrer Begrenzung, <b>bevor</b>
|
||||
* Drehungen und andere Transformationen auf sei angewandt wurden.
|
||||
* <p>
|
||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit {@link #getBounds()}
|
||||
* abgerufen werden.
|
||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
||||
* {@link #getBounds()} abgerufen werden.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -65,8 +65,8 @@ public abstract class Shape extends FilledShape {
|
||||
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung, <b>bevor</b>
|
||||
* Drehungen und andere Transformationen auf sei angewandt wurden.
|
||||
* <p>
|
||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit {@link #getBounds()}
|
||||
* abgerufen werden.
|
||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
||||
* {@link #getBounds()} abgerufen werden.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -104,9 +104,10 @@ public abstract class Shape extends FilledShape {
|
||||
* Setzt den Ankerpunkt der Form basierend auf der angegebenen
|
||||
* {@link Options.Direction Richtung}.
|
||||
* <p>
|
||||
* Für das Setzen des Ankers muss das {@link #getBounds() begrenzende
|
||||
* Rechteck} berechnet werden. Unterklassen sollten die Methode
|
||||
* überschreiben, wenn der Anker auch direkt gesetzt werden kann.
|
||||
* Für das Setzen des Ankers muss das
|
||||
* {@link #getBounds() begrenzende Rechteck} berechnet werden. Unterklassen
|
||||
* sollten die Methode überschreiben, wenn der Anker auch direkt gesetzt
|
||||
* werden kann.
|
||||
*
|
||||
* @param anchor
|
||||
*/
|
||||
@@ -116,39 +117,58 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
}
|
||||
|
||||
protected static Point2D.Double getAnchorPoint( double width, double height, Options.Direction anchor ) {
|
||||
double wHalf = width * .5, hHalf = height * .5;
|
||||
|
||||
// anchor == CENTER
|
||||
Point2D.Double anchorPoint = new Point2D.Double(
|
||||
wHalf + wHalf * anchor.x,
|
||||
hHalf + hHalf * anchor.y
|
||||
);
|
||||
|
||||
return anchorPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt den Ankerpunkt der Form relativ zur oberen linken Ecke und
|
||||
* abhängig vom gesetzten {@link #setAnchor(Options.Direction) Anker}.
|
||||
* Bestimmt den Ankerpunkt der Form relativ zum gesetzten
|
||||
* {@link #setAnchor(Options.Direction) Ankerpunkt}.
|
||||
*
|
||||
* @param anchor Die Richtung des Ankerpunktes.
|
||||
* @return Der relative Ankerpunkt.
|
||||
*/
|
||||
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
|
||||
Point2D.Double anchorpoint = new Point2D.Double(0, 0);
|
||||
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
||||
|
||||
double bHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
||||
// anchor == CENTER
|
||||
anchorpoint.x = bHalf;
|
||||
anchorpoint.y = hHalf;
|
||||
Point2D.Double anchorPoint = new Point2D.Double(
|
||||
wHalf * (anchor.x - this.anchor.x),
|
||||
hHalf * (anchor.y - this.anchor.y)
|
||||
);
|
||||
|
||||
if( NORTH.in(anchor) ) {
|
||||
anchorpoint.y -= hHalf;
|
||||
}
|
||||
if( SOUTH.in(anchor) ) {
|
||||
anchorpoint.y += hHalf;
|
||||
}
|
||||
if( WEST.in(anchor) ) {
|
||||
anchorpoint.x -= bHalf;
|
||||
}
|
||||
if( EAST.in(anchor) ) {
|
||||
anchorpoint.x += bHalf;
|
||||
}
|
||||
return anchorPoint;
|
||||
}
|
||||
|
||||
return anchorpoint;
|
||||
/**
|
||||
* Ermittelt die absoluten Koordinaten eines angegebenen
|
||||
* {@link #setAnchor(Options.Direction) Ankers}.
|
||||
*
|
||||
* @param anchor Die Richtung des Ankerpunktes.
|
||||
* @return Der absolute Ankerpunkt.
|
||||
*/
|
||||
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
|
||||
// TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen.
|
||||
Point2D.Double ap = getAnchorPoint(anchor);
|
||||
ap.x += getX();
|
||||
ap.y += getY();
|
||||
return ap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kopiert die Eigenschaften der übergebenen Form in diese.
|
||||
* <p>
|
||||
* Unterklassen sollten diese Methode überschreiben, um weitere Eigenschaften
|
||||
* zu kopieren (zum Beispiel den Radius eines Kreises). Mit dem Aufruf
|
||||
* Unterklassen sollten diese Methode überschreiben, um weitere
|
||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises). Mit
|
||||
* dem Aufruf
|
||||
* <code>super.copyFrom(shape)</code> sollten die Basiseigenschaften
|
||||
* kopiert werden.
|
||||
*
|
||||
@@ -186,6 +206,96 @@ public abstract class Shape extends FilledShape {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void moveTo( Shape shape ) {
|
||||
moveTo(shape.getX(), shape.getY());
|
||||
}
|
||||
|
||||
public void moveTo( Shape shape, Options.Direction dir ) {
|
||||
moveTo(shape, dir, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen Form.
|
||||
* Mit {@code buff} kann ein zusätzlicher Abstand angegeben werden, um den
|
||||
* die Form entlang des Ankerpunktes {@code anchor} verschoben werden soll.
|
||||
* Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um
|
||||
* {@code buff} nach oben verschoben.
|
||||
*
|
||||
* @param shape
|
||||
* @param anchor
|
||||
* @param buff
|
||||
*/
|
||||
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
|
||||
Point2D ap = shape.getAbsAnchorPoint(dir);
|
||||
|
||||
this.x = ap.getX() + dir.x * buff;
|
||||
this.y = ap.getY() + dir.y * buff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Richtet die Form entlang der angegebenen Richtung am Rand der
|
||||
* Zeichenfläche aus.
|
||||
*
|
||||
* @param dir Die Richtung der Ausrichtung.
|
||||
*/
|
||||
public void alignTo( Options.Direction dir ) {
|
||||
alignTo(dir, 0.0);
|
||||
}
|
||||
|
||||
public void alignTo( Options.Direction dir, double buff ) {
|
||||
Point2D anchorShape = Shape.getAnchorPoint(width, height, dir);
|
||||
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
||||
|
||||
this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
|
||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||
}
|
||||
|
||||
public void alignTo( Shape shape, Options.Direction dir ) {
|
||||
alignTo(shape, dir, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
|
||||
* aus. Für {@code DOWN} wird beispielsweise die y-Koordinate der unteren
|
||||
* Kante dieser Form an der unteren Kante der angegebenen Form {@code shape}
|
||||
* ausgerichtet. Die x-Koordinate wird nicht verändert. {@code buff} gibt
|
||||
* einen Abstand ab, um den diese From versetzt ausgerichtet werden soll.
|
||||
*
|
||||
* @param shape
|
||||
* @param dir
|
||||
* @param buff
|
||||
*/
|
||||
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
|
||||
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
||||
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
||||
|
||||
this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
|
||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||
}
|
||||
|
||||
public void nextTo( Shape shape, Options.Direction dir ) {
|
||||
nextTo(shape, dir, STD_BUFFER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bewegt die Form neben eine andere in Richtung des angegebenen
|
||||
* Ankerpunktes. Im Gegensatz zu
|
||||
* {@link #moveTo(Shape, Options.Direction, double)} wird die Breite bzw.
|
||||
* Höhe der Formen berücksichtigt und die Formen so platziert, dass keine
|
||||
* Überlappungen vorhanden sind.
|
||||
*
|
||||
* @param shape
|
||||
* @param dir
|
||||
* @param buff
|
||||
*/
|
||||
public void nextTo( Shape shape, Options.Direction dir, double buff ) {
|
||||
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
||||
Point2D anchorThis = this.getAbsAnchorPoint(dir.inverse());
|
||||
|
||||
this.x += (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
|
||||
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||
}
|
||||
|
||||
public void scale( double factor ) {
|
||||
scale = factor;
|
||||
}
|
||||
@@ -210,7 +320,7 @@ public abstract class Shape extends FilledShape {
|
||||
this.rotation += angle % 360;
|
||||
|
||||
// Rotate x/y position
|
||||
double x1 = this.x-x, y1 = this.y-y;
|
||||
double x1 = this.x - x, y1 = this.y - y;
|
||||
|
||||
double rad = Math.toRadians(angle);
|
||||
double x2 = x1 * Math.cos(rad) - y1 * Math.sin(rad);
|
||||
@@ -225,13 +335,15 @@ public abstract class Shape extends FilledShape {
|
||||
}*/
|
||||
|
||||
public AffineTransform getTransform() {
|
||||
Point2D.Double anchorPoint = getAnchorPoint(this.anchor);
|
||||
// Point2D.Double anchorPoint = getAnchorPoint();
|
||||
Point2D.Double basePoint = getAnchorPoint(Options.Direction.NORTHWEST);
|
||||
|
||||
AffineTransform transform = new AffineTransform();
|
||||
transform.translate(x, y);
|
||||
transform.rotate(Math.toRadians(rotation));
|
||||
//transform.scale(scale, scale);
|
||||
transform.translate(-anchorPoint.x, -anchorPoint.y);
|
||||
//transform.translate(-anchorPoint.x, -anchorPoint.y);
|
||||
transform.translate(basePoint.x, basePoint.y);
|
||||
return transform;
|
||||
}
|
||||
|
||||
@@ -246,8 +358,9 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet die Form, aber wendet zuvor noch eine zusätzliche Transformations-
|
||||
* matrix an. Wird u.A. von der {@link ShapeGroup} verwendet.
|
||||
* Zeichnet die Form, aber wendet zuvor noch eine zusätzliche
|
||||
* Transformations- matrix an. Wird u.A. von der {@link ShapeGroup}
|
||||
* verwendet.
|
||||
*
|
||||
* @param graphics
|
||||
* @param transform
|
||||
|
||||
@@ -2,20 +2,40 @@ package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Eine {@code ShapwGroup} ist eine Sammlung von {@link Shape}s, die gemeinsam
|
||||
* eine Gruppe bilden. Transformationen der Gruppe werden nach den
|
||||
* Transformationen der einzelnen Formen auf die gesammte Gruppe angewandt. So
|
||||
* kann ein Rechteck in der Gruppe zunächst um 45 Grad gedreht werden und dann
|
||||
* die Gruppe um -45 Grad. Das Rechteck wird dann wieder waagerecht dargestellt,
|
||||
* während alle anderen Formen der Gruppe nun gedreht erscheinen. Da die Gruppe
|
||||
* selbst ein eigenes Drehzentrum hat, können so komplexe Strukturen als eine
|
||||
* eigene, zusammenhängende Form verwendet werden. (Im Gegensatz zu einer
|
||||
* {@link CustomShape} haben Formgruppen den Vorteil, dass die einzelnen Formen
|
||||
* individuelle Farben und Konturen bekommen können.)
|
||||
* <p>
|
||||
* Da die Größe der Gruppe durch seine zugewiesenen Formen fefstgelegt wird,
|
||||
* sollten Modifikation wie {@link #setAnchor(Options.Direction)},
|
||||
* {@link #nextTo(Shape, Options.Direction)} oder
|
||||
* {@link #alignTo(Shape, Options.Direction, double)}, die die Größe der Gruppe
|
||||
* benötigen, erst nach Hinzufügen der Gruppenelemente ausgeführt werden.
|
||||
* Nachdem sich die Zusammensetzung der Gruppe geändert hat, muss die Gruppe
|
||||
* ggf. neu ausgerichtet werden.
|
||||
*/
|
||||
public class ShapeGroup extends Shape {
|
||||
|
||||
private List<Shape> shapes;
|
||||
|
||||
protected double width = -1.0;
|
||||
private double groupWidth = -1.0;
|
||||
|
||||
protected double height = -1.0;
|
||||
private double groupHeight = -1.0;
|
||||
|
||||
public ShapeGroup() {
|
||||
super();
|
||||
@@ -33,7 +53,6 @@ public class ShapeGroup extends Shape {
|
||||
for( Shape pShape : shapes ) {
|
||||
this.shapes.add(pShape);
|
||||
}
|
||||
this.anchor = Options.Direction.CENTER;
|
||||
}
|
||||
|
||||
public Shape copy() {
|
||||
@@ -68,9 +87,9 @@ public class ShapeGroup extends Shape {
|
||||
|
||||
public <ShapeType extends Shape> List<ShapeType> getShapes( Class<ShapeType> typeClass ) {
|
||||
LinkedList<ShapeType> list = new LinkedList<>();
|
||||
for( Shape s: shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
if( typeClass.getClass().isInstance(s) ) {
|
||||
list.add((ShapeType)s);
|
||||
list.add((ShapeType) s);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
@@ -99,23 +118,23 @@ public class ShapeGroup extends Shape {
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
if( width < 0 ) {
|
||||
if( groupWidth < 0 ) {
|
||||
calculateBounds();
|
||||
}
|
||||
return width;
|
||||
return groupWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
if( height < 0 ) {
|
||||
if( groupHeight < 0 ) {
|
||||
calculateBounds();
|
||||
}
|
||||
return height;
|
||||
return groupHeight;
|
||||
}
|
||||
|
||||
private void invalidateBounds() {
|
||||
width = -1.0;
|
||||
height = -1.0;
|
||||
groupWidth = -1.0;
|
||||
groupHeight = -1.0;
|
||||
}
|
||||
|
||||
private void calculateBounds() {
|
||||
@@ -124,13 +143,13 @@ public class ShapeGroup extends Shape {
|
||||
for( Shape pShape : shapes ) {
|
||||
Bounds bounds = pShape.getBounds();
|
||||
minx = Math.min(minx, bounds.x);
|
||||
maxx = Math.max(maxx, bounds.x+bounds.width);
|
||||
maxx = Math.max(maxx, bounds.x + bounds.width);
|
||||
miny = Math.min(miny, bounds.y);
|
||||
maxy = Math.max(maxy, bounds.y+bounds.height);
|
||||
maxy = Math.max(maxy, bounds.y + bounds.height);
|
||||
}
|
||||
|
||||
width = maxx-minx;
|
||||
height = maxy-miny;
|
||||
groupWidth = maxx - minx;
|
||||
groupHeight = maxy - miny;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,7 +175,7 @@ public class ShapeGroup extends Shape {
|
||||
verzerrung.translate(-anchor.x, -anchor.y);
|
||||
*/
|
||||
|
||||
for( Shape f: shapes ) {
|
||||
for( Shape f : shapes ) {
|
||||
AffineTransform af = f.getTransform();
|
||||
af.preConcatenate(transform);
|
||||
f.draw(graphics, af);
|
||||
|
||||
@@ -3,6 +3,7 @@ package schule.ngb.zm.shapes;
|
||||
import schule.ngb.zm.Layer;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class ShapesLayer extends Layer {
|
||||
@@ -48,17 +49,35 @@ public class ShapesLayer extends Layer {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void add( Shape... shape ) {
|
||||
public void add( Shape... shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape f : shape ) {
|
||||
shapes.add(f);
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove( Shape shape ) {
|
||||
public void add( Collection<Shape> shapes ) {
|
||||
synchronized( shapes ) {
|
||||
shapes.remove(shape);
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove( Shape... shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape s: shapes ) {
|
||||
this.shapes.remove(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove( Collection<Shape> shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape s: shapes ) {
|
||||
this.shapes.remove(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color color , int alpha ) {
|
||||
this.strokeColor = new Color(color, alpha);
|
||||
setStrokeColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray ) {
|
||||
@@ -35,7 +35,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
strokeColor = null;
|
||||
setStrokeColor(null);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.FontLoader;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Canvas;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
@@ -12,16 +17,33 @@ public class Text extends Shape {
|
||||
|
||||
protected Font font;
|
||||
|
||||
protected Color fontColor = BLACK;
|
||||
|
||||
protected int width = 0, height = 0, ascent = 0;
|
||||
|
||||
public Text( double x, double y, String text ) {
|
||||
this(x, y, text, new Font(Font.SANS_SERIF, Font.PLAIN, STD_FONTSIZE));
|
||||
}
|
||||
public Text( double x, double y, String text, String fontname ) {
|
||||
super(x, y);
|
||||
Font userfont = FontLoader.loadFont(fontname);
|
||||
if( userfont != null ) {
|
||||
font = userfont;
|
||||
} else {
|
||||
font = new Font(Font.SANS_SERIF, Font.PLAIN, STD_FONTSIZE);
|
||||
}
|
||||
setText(text);
|
||||
fillColor = null;
|
||||
strokeColor = null;
|
||||
anchor = Options.Direction.CENTER;
|
||||
}
|
||||
|
||||
public Text( double x, double y, String text, Font font ) {
|
||||
super(x, y);
|
||||
this.font = font;
|
||||
setText(text);
|
||||
fillColor = null;
|
||||
strokeColor = null;
|
||||
anchor = Options.Direction.CENTER;
|
||||
}
|
||||
|
||||
@@ -38,6 +60,18 @@ public class Text extends Shape {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setFont( String fontname ) {
|
||||
Font newFont = FontLoader.loadFont(fontname);
|
||||
if( newFont != null ) {
|
||||
setFont(newFont);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFont( Font newFont ) {
|
||||
font = newFont.deriveFont(font.getSize2D());
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
@@ -47,6 +81,10 @@ public class Text extends Shape {
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public double getFontsize() {
|
||||
return font.getSize2D();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
@@ -56,6 +94,46 @@ public class Text extends Shape {
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public Color getFontColor() {
|
||||
return fontColor;
|
||||
}
|
||||
|
||||
public void setFontColor( Color color ) {
|
||||
if( color != null ) {
|
||||
fontColor = color;
|
||||
} else {
|
||||
fontColor = BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
public void setFontColor( Color color, int alpha ) {
|
||||
if( color != null ) {
|
||||
fontColor = new Color(color, alpha);
|
||||
} else {
|
||||
fontColor = BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
public void setFontColor( int gray ) {
|
||||
setFontColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setFontColor( int gray, int alpha ) {
|
||||
setFontColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setFontColor( int red, int green, int blue ) {
|
||||
setFontColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setFontColor( int red, int green, int blue, int alpha ) {
|
||||
setFontColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void resetFontColor() {
|
||||
setFontColor(BLACK);
|
||||
}
|
||||
|
||||
private void calculateBounds() {
|
||||
//GraphicsDevice gd;
|
||||
//gd.getDefaultConfiguration().createCompatibleImage(1,1);
|
||||
@@ -64,6 +142,7 @@ public class Text extends Shape {
|
||||
width = metrics.stringWidth(text);
|
||||
//height = metrics.getHeight();
|
||||
height = metrics.getDescent() + metrics.getAscent();
|
||||
ascent = metrics.getMaxAscent();
|
||||
}
|
||||
|
||||
public Shape copy() {
|
||||
@@ -100,18 +179,32 @@ public class Text extends Shape {
|
||||
|
||||
// Aktuelle Werte speichern
|
||||
Font currentFont = graphics.getFont();
|
||||
Color currentColor = graphics.getColor();
|
||||
java.awt.Color currentColor = graphics.getColor();
|
||||
AffineTransform af = graphics.getTransform();
|
||||
|
||||
// Neue Werte setzen
|
||||
graphics.setFont(font);
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.transform(transform);
|
||||
|
||||
// Draw text
|
||||
//FontMetrics fm = graphics.getFontMetrics();
|
||||
//graphics.drawString(text, (float) (x - fm.stringWidth(text)/2.0), (float) (y + fm.getDescent()));
|
||||
graphics.drawString(text, 0, 0);
|
||||
// Hintergrund
|
||||
if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
graphics.setColor(fillColor.getJavaColor());
|
||||
graphics.fillRect(0, 0, width, height);
|
||||
}
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.drawRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
// Neue Werte setzen
|
||||
if( font != null && fontColor != null && fontColor.getAlpha() > 0 ) {
|
||||
graphics.setFont(font);
|
||||
graphics.setColor(fontColor.getJavaColor());
|
||||
|
||||
// Draw text
|
||||
//FontMetrics fm = graphics.getFontMetrics();
|
||||
//graphics.drawString(text, (float) (x - fm.stringWidth(text)/2.0), (float) (y + fm.getDescent()));
|
||||
graphics.drawString(text, 0, ascent);
|
||||
}
|
||||
|
||||
// Alte Werte wiederherstellen
|
||||
graphics.setTransform(af);
|
||||
|
||||
76
src/schule/ngb/zm/tasks/TaskRunner.java
Normal file
76
src/schule/ngb/zm/tasks/TaskRunner.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Führt Aufgaben (Tasks) parallel zum Hauptprogramm aus.
|
||||
*/
|
||||
public class TaskRunner {
|
||||
|
||||
private static final int POOL_SIZE = 4;
|
||||
|
||||
private static final int SHUTDOWN_TIME = 100;
|
||||
|
||||
|
||||
private static TaskRunner runner;
|
||||
|
||||
public static TaskRunner getTaskRunner() {
|
||||
if( runner == null ) {
|
||||
runner = new TaskRunner();
|
||||
}
|
||||
return runner;
|
||||
}
|
||||
|
||||
public static void run( Runnable task ) {
|
||||
TaskRunner r = getTaskRunner();
|
||||
r.pool.execute(task);
|
||||
}
|
||||
|
||||
public static void schedule( Runnable task, int ms ) {
|
||||
TaskRunner r = getTaskRunner();
|
||||
r.pool.schedule(task, ms, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public static void invokeLater( Runnable task ) {
|
||||
SwingUtilities.invokeLater(task);
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
if( runner != null ) {
|
||||
runner.pool.shutdown();
|
||||
try {
|
||||
runner.pool.awaitTermination(SHUTDOWN_TIME, TimeUnit.MILLISECONDS);
|
||||
} catch( InterruptedException ex ) {
|
||||
|
||||
} finally {
|
||||
if( !runner.pool.isTerminated() ) {
|
||||
runner.pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScheduledExecutorService pool;
|
||||
|
||||
private TaskRunner() {
|
||||
//pool = new ScheduledThreadPoolExecutor(4);
|
||||
pool = Executors.newScheduledThreadPool(POOL_SIZE, new ThreadFactory() {
|
||||
private final ThreadFactory threadFactory = Executors.defaultThreadFactory();
|
||||
|
||||
@Override
|
||||
public Thread newThread( Runnable r ) {
|
||||
Thread t = threadFactory.newThread(r);
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(TaskRunner.class.getName());
|
||||
|
||||
}
|
||||
59
src/schule/ngb/zm/util/FontLoader.java
Normal file
59
src/schule/ngb/zm/util/FontLoader.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class FontLoader {
|
||||
|
||||
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
|
||||
|
||||
public static Font loadFont( String source ) {
|
||||
Objects.requireNonNull(source, "Font source may not be null");
|
||||
if( source.length() == 0 ) {
|
||||
throw new IllegalArgumentException("Font source may not be empty.");
|
||||
}
|
||||
|
||||
if( fontCache.containsKey(source) ) {
|
||||
LOG.trace("Retrieved font <%s> from font cache.", source);
|
||||
return fontCache.get(source);
|
||||
}
|
||||
|
||||
// Look for System fonts
|
||||
Font font = Font.decode(source);
|
||||
if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
||||
fontCache.put(source, font);
|
||||
LOG.debug("Loaded system font for <%s>.", source);
|
||||
return font;
|
||||
} else {
|
||||
font = null;
|
||||
}
|
||||
|
||||
// Load userfonts
|
||||
try( InputStream in = ResourceStreamProvider.getResourceStream(source) ) {
|
||||
font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN);
|
||||
|
||||
if( font != null ) {
|
||||
fontCache.put(source, font);
|
||||
//ge.registerFont(font);
|
||||
}
|
||||
LOG.debug("Loaded custom font from <%s>.", source);
|
||||
} catch( IOException ioex ) {
|
||||
LOG.error(ioex, "Error loading custom font file from source <%s>.", source);
|
||||
} catch( FontFormatException ffex ) {
|
||||
LOG.error(ffex, "Error creating custom font from source <%s>.", source);
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
private FontLoader() {
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(FontLoader.class);
|
||||
|
||||
}
|
||||
@@ -1,169 +1,354 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ImageLoader {
|
||||
|
||||
public static boolean cacheing = true;
|
||||
public static boolean caching = true;
|
||||
|
||||
private static HashMap<String, BufferedImage> imageCache = new HashMap<>();
|
||||
private static Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
||||
|
||||
private static SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
||||
|
||||
/**
|
||||
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
||||
* <p>
|
||||
* Die Bilddatei wird nach den Regeln von
|
||||
* {@link ResourceStreamProvider#getResourceStream(String)} gesucht und
|
||||
* geöffnet. Tritt dabei ein Fehler auf oder konnte keine passende Datei
|
||||
* gefunden werden, wird {@code null} zurückgegeben.
|
||||
* <p>
|
||||
* Ist der {@link #activateCache() Cache aktiviert} und ein Bild mit der
|
||||
* angegebenen Quelle schon vorhanden, wird das gespeicherte Bild
|
||||
* zurückgegeben.
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
* @see #loadImage(String, boolean)
|
||||
*/
|
||||
public static BufferedImage loadImage( String source ) {
|
||||
return loadImage(source, cacheing);
|
||||
return loadImage(source, caching);
|
||||
}
|
||||
|
||||
/**
|
||||
* Läadt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
||||
* Lädt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
||||
* Bild zurück oder <code>null</code>, wenn das Bild nicht geladen werden
|
||||
* konnte. Ist ein Bild mit der angegebenen Quelle im Cache, wird das
|
||||
* gechachete Bild zurückgegeben. Dies kann mit <code>cacheing = false</code>
|
||||
* gespeicherte Bild zurückgegeben. Dies kann mit {@code caching = false}
|
||||
* verhindert werden.
|
||||
* <p>
|
||||
* Wurde chacheing global deaktiviert, kann mit <code>cacheing = true</code>
|
||||
* das Bild trotzdem aus dem Cache geladen werden, wenn es vorhanden ist.
|
||||
* Wurde das Chaching global deaktiviert, kann mit <code>caching =
|
||||
* true</code> das Bild trotzdem aus dem Cache geladen werden, wenn es
|
||||
* vorhanden ist.
|
||||
*
|
||||
* @param source
|
||||
* @param cacheing
|
||||
* @param source Die Quelle des Bildes.
|
||||
* @param caching Ob das Bild (falls vorhanden) aus dem Zwischenspeicher
|
||||
* geladen werden soll.
|
||||
* @return
|
||||
*/
|
||||
public static BufferedImage loadImage( String source, boolean cacheing ) {
|
||||
if( source == null || source.length() == 0 )
|
||||
throw new IllegalArgumentException("Image source may not be null or empty.");
|
||||
public static BufferedImage loadImage( String source, boolean caching ) {
|
||||
Validator.requireNotNull(source, "Image source may not be null");
|
||||
Validator.requireNotEmpty(source, "Image source may not be empty.");
|
||||
|
||||
if( cacheing && imageCache.containsKey(source) ) {
|
||||
BufferedImage cachedImage = imageCache.get(source);
|
||||
if( cachedImage != null ) {
|
||||
return cachedImage;
|
||||
}
|
||||
if( caching && isCached(source) ) {
|
||||
return getCache(source);
|
||||
}
|
||||
|
||||
try {
|
||||
BufferedImage img;
|
||||
BufferedImage img = null;
|
||||
try( InputStream in = ResourceStreamProvider.getResourceStream(source) ) {
|
||||
//URL url = ResourceStreamProvider.getResourceURL(source);
|
||||
//BufferedImage img = ImageIO.read(url);
|
||||
|
||||
// Load image from working dir
|
||||
File file = new File(source);
|
||||
if( file.isFile() ) {
|
||||
img = ImageIO.read(file);
|
||||
} else {
|
||||
// load ressource relative to .class-file
|
||||
URL url = ImageLoader.class.getResource(source);
|
||||
img = ImageIO.read(in);
|
||||
|
||||
// relative to ClassLoader
|
||||
if( url == null ) {
|
||||
url = ImageLoader.class.getClassLoader().getResource(source);
|
||||
}
|
||||
|
||||
// load form web or jar-file
|
||||
if( url == null ) {
|
||||
url = new URL(source);
|
||||
}
|
||||
|
||||
img = ImageIO.read(url);
|
||||
if( caching && img != null ) {
|
||||
putCache(source, img);
|
||||
}
|
||||
|
||||
if( cacheing && img != null ) {
|
||||
imageCache.put(source, img);
|
||||
}
|
||||
|
||||
return img;
|
||||
} catch( IOException ioe ) {
|
||||
return null;
|
||||
} catch( IOException ioex ) {
|
||||
LOG.error(ioex, "Error loading image file from source <%s>.", source);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an image into the cache with a user specified name that may differ
|
||||
* from the image source string.
|
||||
* Lädt ein Bild aus der angegebenen Quelle unter dem angegebenen Namen in
|
||||
* den Cache.
|
||||
* <p>
|
||||
* Der {@code name} kann beliebig gewählt werden. Existiert unter dem Namen
|
||||
* schon ein Bild im Zwischenspeicher, wird dieses überschrieben.
|
||||
* <p>
|
||||
* Wenn der Cache aktiviert ist, werden zukünftige Aufrufe von
|
||||
* {@link #loadImage(String)} mit {@code name} als Quelle das gespeicherte
|
||||
* Bild zurückgeben.
|
||||
*
|
||||
* @param name
|
||||
* @param source
|
||||
* @return
|
||||
* @param name Name des Bildes im Zwischenspeicher.
|
||||
* @param source Quelle, aus dem das Bild geladen werden soll.
|
||||
* @return {@code true}, wenn das Bild erfolgreich geladen wurde,
|
||||
* {@code false} sonst.
|
||||
* @see #loadImage(String)
|
||||
*/
|
||||
public static boolean preloadImage( String name, String source ) {
|
||||
BufferedImage img = loadImage(source, true);
|
||||
if( cacheing && img != null ) {
|
||||
imageCache.put(name, img);
|
||||
if( img != null ) {
|
||||
putCache(name, img);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an image with the given name is currently cached.
|
||||
* Speiechert das angegebene Bild unter dem angegebenen Namen im Cache.
|
||||
* <p>
|
||||
* Exisitert zu {@code name} schon ein Bild im Zwischenspeicher
|
||||
* ({@code ImageLoader.isCached(name) == true}), dann wird dieses
|
||||
* überschrieben.
|
||||
* <p>
|
||||
* Wenn der Cache aktiviert ist, werden zukünftige Aufrufe von
|
||||
* {@link #loadImage(String)} mit {@code name} als Quelle das gespeicherte
|
||||
* Bild zurückgeben.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
* @param name Name des Bildes im Zwischenspeicher.
|
||||
* @param img ZU speicherndes Bild.
|
||||
*/
|
||||
public static boolean isCached( String name ) {
|
||||
if( imageCache.containsKey(name) ) {
|
||||
return imageCache.get(name) != null;
|
||||
}
|
||||
return false;
|
||||
public static void preloadImage( String name, BufferedImage img ) {
|
||||
putCache(name, img);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified key from the cache.
|
||||
* Prüft, ob zum angegebenen Namen ein Bild im Cache gespeichert ist.
|
||||
*
|
||||
* @param name
|
||||
* @param name Name des Bildes im Cache.
|
||||
* @return {@code true}, wenn es ein Bild zum Namen gibt, sonst
|
||||
* {@code false}.
|
||||
*/
|
||||
public static void invalidateCache( String name ) {
|
||||
public static boolean isCached( String name ) {
|
||||
SoftReference<BufferedImage> imgRef = imageCache.get(name);
|
||||
return imgRef != null && imgRef != NOCACHE && imgRef.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt das Bild zum angegebenen Namen aus dem Cache. Gibt es zum Namen
|
||||
* kein Bild im Zwischenspeicher, dann passiert nichts.
|
||||
*
|
||||
* @param name Name des Bildes im Cache.
|
||||
*/
|
||||
public static void invalidateCache( final String name ) {
|
||||
if( imageCache.containsKey(name) ) {
|
||||
imageCache.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents caching for the specified source.
|
||||
* Speichert ein Bild als {@link SoftReference} im Cache.
|
||||
*
|
||||
* @param source
|
||||
* @param name Name des Bildes im Zwischenspeicher.
|
||||
* @param img Das zu speichernde Bild.
|
||||
*/
|
||||
public static void noCache( String source ) {
|
||||
imageCache.put(source, null);
|
||||
private static void putCache( final String name, final BufferedImage img ) {
|
||||
imageCache.put(name, new SoftReference<>(img));
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt ein Bild aus dem Cache.
|
||||
* <p>
|
||||
* Prüft nicht, ob ein Bild vorhanden ist. Dies sollte vom Aufrufenden
|
||||
* übernommen werden, da sonst eine {@link NullPointerException} erzeugt
|
||||
* werden kann.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
private static BufferedImage getCache( final String name ) {
|
||||
return imageCache.get(name).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deaktiviert den Cache für die angegebene Quelle.
|
||||
* <p>
|
||||
* Selbst wenn der {@link #activateCache() Cache aktiviert} ist, wird das
|
||||
* Bild zur angegebenen Quelle niemals zwischengespeichert und immer neu
|
||||
* geladen.
|
||||
*
|
||||
* @param name Die Quelle des Bildes.
|
||||
*/
|
||||
public static void preventCache( final String name ) {
|
||||
imageCache.put(name, NOCACHE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leer den Cache und löschte alle bisher gespeicherten Bilder.
|
||||
* <p>
|
||||
* Auch vorher mit {@link #preventCache(String)} verhinderte Caches werden
|
||||
* gelöscht und müssen neu gesetzt werden.
|
||||
*/
|
||||
public static void clearCache() {
|
||||
imageCache.clear();
|
||||
}
|
||||
|
||||
public static void enableCaching() {
|
||||
cacheing = true;
|
||||
/**
|
||||
* Aktiviert den Cache.
|
||||
* <p>
|
||||
* Der Cache ist ein Zwischenspeicher für geladene Bilder. Wenn er aktiviert
|
||||
* ist (Standard), dann werden mit dem {@code ImageLoader} geladene Bilder
|
||||
* im Zwischenspeicher abgelegt. Bei jedem folgenden laden desselben Bildes
|
||||
* (bzw. eines Bildes mit derselben {@code source}), wird das gespeicherte
|
||||
* Bild zurückgegeben und nicht komplett neu geladen.
|
||||
* <p>
|
||||
* <b>Wichtig:</b> Bildreferenzen, die aus dem Cache geladen werden,
|
||||
* verweisen alle auf dasselbe Objekt. Änderungen schalgen sich daher in
|
||||
* allen anderen Versionen des Bildes nieder (inklusive dem Bild im
|
||||
* Zwischenspeicher). Für Änderungen sollte daher immer
|
||||
* {@link #copyImage(BufferedImage) eine Kopie} des Bildes erstellt werden:
|
||||
* <pre>
|
||||
* BufferedImage originalImage = ImageLoader.loadImage("assets/image.gif");
|
||||
* BufferedImage copiedImage = ImageLoader.copyImage(originalImage);
|
||||
* </pre>
|
||||
* <p>
|
||||
* Alternativ kann der Cache umgangen werden, indem
|
||||
* {@link #loadImage(String, boolean)} verwendet wird.
|
||||
*/
|
||||
public static void activateCache() {
|
||||
caching = true;
|
||||
}
|
||||
|
||||
public static void disableCaching() {
|
||||
cacheing = false;
|
||||
/**
|
||||
* Deaktiviert den Cache.
|
||||
*
|
||||
* @see #activateCache()
|
||||
*/
|
||||
public static void deactivateCache() {
|
||||
caching = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Erstellt eine exakte Kopie des angegebenen Bildes als neues Objekt.
|
||||
* <p>
|
||||
* Die Methode ist hilfreich, wenn ein Bild aus dem
|
||||
* {@link #activateCache() Cache} geladen wurde und dann verändert werden
|
||||
* soll, ohne den Cache (oder andere Referenzen auf das Bild) zu verändern.
|
||||
*
|
||||
* @param image Das Originalbild.
|
||||
* @return Eine exakte Kopie des Originals.
|
||||
*/
|
||||
public static BufferedImage copyImage( BufferedImage image ) {
|
||||
ColorModel cm = image.getColorModel();
|
||||
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
|
||||
WritableRaster raster = image.copyData(null);
|
||||
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein {@code BufferedImage} mit demselben Inhalt wie das
|
||||
* angegebene {@code Image}.
|
||||
*
|
||||
* @param image Das Originalbild.
|
||||
* @return Eine exakte Kopie des Originals.
|
||||
*/
|
||||
public static BufferedImage copyImage( Image image ) {
|
||||
if( image instanceof BufferedImage ) {
|
||||
return copyImage((BufferedImage) image);
|
||||
} else {
|
||||
//BufferedImage outimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage outimage = createImage(image.getWidth(null), image.getHeight(null));
|
||||
|
||||
Graphics2D g = outimage.createGraphics();
|
||||
g.setColor(Color.WHITE);
|
||||
g.fillRect(0, 0, outimage.getWidth(), outimage.getHeight());
|
||||
g.drawImage(image, 0, 0, null);
|
||||
g.dispose();
|
||||
|
||||
return outimage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert das angegebene Bild in der angegebenen Datei auf der
|
||||
* Festplatte.
|
||||
*
|
||||
* @param image Das zu speichernde Bild. Falls die Datei schon existiert,
|
||||
* wird sie überschrieben.
|
||||
* @param file Die Zieldatei.
|
||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||
* {@code null} ist.
|
||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||
*/
|
||||
public static void saveImage( Image image, File file ) throws IOException {
|
||||
saveImage(image, file, false);
|
||||
saveImage(copyImage(image), file, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert das angegebene Bild in der angegebenen Datei auf der
|
||||
* Festplatte.
|
||||
*
|
||||
* @param image Das zu speichernde Bild. Falls die Datei schon existiert,
|
||||
* wird sie überschrieben.
|
||||
* @param file Die Zieldatei.
|
||||
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
||||
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
||||
* wenn die Datei schon exisitiert.
|
||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||
* {@code null} ist.
|
||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||
*/
|
||||
public static void saveImage( Image image, File file, boolean overwriteIfExists ) throws IOException {
|
||||
//BufferedImage outimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage outimage = createImage(image.getWidth(null), image.getHeight(null));
|
||||
|
||||
Graphics2D g = outimage.createGraphics();
|
||||
g.setColor(Color.WHITE);
|
||||
g.fillRect(0, 0, outimage.getWidth(), outimage.getHeight());
|
||||
g.drawImage(image, 0, 0, null);
|
||||
g.dispose();
|
||||
|
||||
saveImage(outimage, file, overwriteIfExists);
|
||||
saveImage(copyImage(image), file, overwriteIfExists);
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert das angegebene Bild in der angegebenen Datei auf der
|
||||
* Festplatte.
|
||||
*
|
||||
* @param image Das zu speichernde Bild. Falls die Datei schon existiert,
|
||||
* wird sie überschrieben.
|
||||
* @param file Die Zieldatei.
|
||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||
* {@code null} ist.
|
||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||
*/
|
||||
public static void saveImage( BufferedImage image, File file ) throws IOException {
|
||||
saveImage(image, file, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert das angegebene Bild in der angegebenen Datei auf der
|
||||
* Festplatte.
|
||||
*
|
||||
* @param image Das zu speichernde Bild. Falls die Datei schon existiert,
|
||||
* wird sie überschrieben.
|
||||
* @param file Die Zieldatei.
|
||||
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
||||
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
||||
* wenn die Datei schon exisitiert.
|
||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||
* {@code null} ist.
|
||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||
*/
|
||||
public static void saveImage( BufferedImage image, File file, boolean overwriteIfExists ) throws IOException {
|
||||
if( image == null ) {
|
||||
throw new NullPointerException("Image may not be <null>.");
|
||||
}
|
||||
if( file == null ) {
|
||||
throw new NullPointerException("File may not be <null>.");
|
||||
}
|
||||
|
||||
if( file.isFile() ) {
|
||||
// Datei existiert schon
|
||||
if( !overwriteIfExists ) {
|
||||
throw new IOException("File already exists. Delete target file before saving image.");
|
||||
} else if( !file.canWrite() ) {
|
||||
@@ -171,6 +356,7 @@ public class ImageLoader {
|
||||
}
|
||||
}
|
||||
|
||||
// Dateiformat anhand der Dateiendung ermitteln oder PNG nutzen
|
||||
String filename = file.getName();
|
||||
String formatName = "png";
|
||||
if( filename.lastIndexOf('.') >= 0 ) {
|
||||
@@ -179,13 +365,31 @@ public class ImageLoader {
|
||||
file = new File(file.getAbsolutePath() + ".png");
|
||||
}
|
||||
|
||||
// Datei schreiben
|
||||
ImageIO.write(image, formatName, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein neues, leeres {@code BufferedImage} passend für dieses
|
||||
* Anzeigegerät.
|
||||
*
|
||||
* @param width Breite des leeren Bildes.
|
||||
* @param height Höhe des leeren Bildes.
|
||||
* @return Ein neues, leeres Bild.
|
||||
*/
|
||||
public static BufferedImage createImage( int width, int height ) {
|
||||
return createImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein neues, leeres {@code BufferedImage} passend für dieses
|
||||
* Anzeigegerät.
|
||||
*
|
||||
* @param width Breite des neuen Bildes.
|
||||
* @param height Höhe des neuen Bildes.
|
||||
* @param type {@link BufferedImage#getType() Typ} des neuen Bildes.
|
||||
* @return Ein neues, leeres Bild.
|
||||
*/
|
||||
public static BufferedImage createImage( int width, int height, int type ) {
|
||||
return GraphicsEnvironment
|
||||
.getLocalGraphicsEnvironment()
|
||||
@@ -194,4 +398,9 @@ public class ImageLoader {
|
||||
.createCompatibleImage(width, height, type);
|
||||
}
|
||||
|
||||
private ImageLoader() {
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(ImageLoader.class);
|
||||
|
||||
}
|
||||
|
||||
318
src/schule/ngb/zm/util/Log.java
Normal file
318
src/schule/ngb/zm/util/Log.java
Normal file
@@ -0,0 +1,318 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.*;
|
||||
|
||||
import static java.util.logging.Level.*;
|
||||
|
||||
/**
|
||||
* Einfache Logging-API, die auf {@link java.util.logging} aufsetzt.
|
||||
* <p>
|
||||
* Klassen, die Informations- oder Fehlernachrichten loggen wollen, erstellen
|
||||
* ein internes {@code LOG} Objekt dieser Klasse. Die Zeichenmaschine erstellt
|
||||
* ihren Logger beispielsweise so:
|
||||
* <pre>
|
||||
* private static final Log LOG = new Log(Zeichenmaschine.class);
|
||||
* </pre>
|
||||
* <p>
|
||||
* Jedes {@code Log} nutzt intern einen {@link Logger}, der über
|
||||
* {@link Logger#getLogger(String)} abgerufen wird. Die {@code Log}-Objekte
|
||||
* selbst werden nicht weiter zwischengespeichert, aber in der Regel wird pro
|
||||
* Klasse nur genau ein {@code Log}-Objekt erstellt. Mehrere {@code Log}s nutzen
|
||||
* dann aber denselben {@code Logger}.
|
||||
* <p>
|
||||
* Die API orientiert sich lose an <a href="#">Log4j</a> und vereinfacht die
|
||||
* Nutzung der Java logging API für die häufigsten Anwendungsfälle.
|
||||
*/
|
||||
public final class Log {
|
||||
|
||||
private static final String ROOT_LOGGER = "schule.ngb.zm";
|
||||
|
||||
private static boolean LOGGING_INIT = false;
|
||||
|
||||
/**
|
||||
* Aktiviert das Logging in der Zeichenmaschine global.
|
||||
* <p>
|
||||
* Die Methode sollte einmalig möglichst früh im Programm aufgerufen werden,
|
||||
* um für alle bisher und danach erstellten {@link Logger} das minimale
|
||||
* Logging-Level auf {@link Level#FINE} zu setzen. Dies entspricht allen
|
||||
* Nachrichten die mit den Methoden (außer {@code trace}) eines {@link Log}
|
||||
* erzeugt werden.
|
||||
*
|
||||
* @see #enableGlobalLevel(Level)
|
||||
*/
|
||||
public static final void enableGlobalDebugging() {
|
||||
enableGlobalLevel(ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt das Logging-Level aller bisher und danach erzeugten {@link Logger}
|
||||
* auf den angegebenen {@code Level}.
|
||||
* <p>
|
||||
* Das Level für bestehende {@code Logger} wird nur abgesenkt, so dass
|
||||
* Nachrrichten bis {@code level} auf jeden Fall ausgegeben werden. Besitzt
|
||||
* der {@code Logger} schon ein niedrigeres Level, wird dieses nicht
|
||||
* verändert. Gleiches gilt für alle {@link ConsoleHandler}, die den
|
||||
* bestehenden {@code Logger}n hinzugefügt wurden.
|
||||
* <p>
|
||||
* <strong>Hinweis:</strong> Das Setzen des Logging-Level während der
|
||||
* Programmausführung gilt als <em>bad practice</em>, also schlechter
|
||||
* Programmierstil. Im Kontext der Zeichenmaschine macht dies Sinn, um
|
||||
* Programmieranfängern eine einfache Möglichkeit zu geben, in ihren
|
||||
* Programmen auf Fehlersuche zu gehen. Für andere Einsatzszenarien sollte
|
||||
* auf die übliche Konfiguration des {@link java.util.logging} Pakets über
|
||||
* eine Konfigurationsdatei zurückgegriffen werden.
|
||||
*
|
||||
* @param level Das Level, auf das alle {@code Logger} und {@code Handler}
|
||||
* mindestens herabgesenkt werden sollen.
|
||||
*/
|
||||
public static final void xenableGlobalLevel( Level level ) {
|
||||
int lvl = Validator.requireNotNull(level).intValue();
|
||||
|
||||
Logger rootLogger = Logger.getLogger(ROOT_LOGGER);
|
||||
rootLogger.setLevel(level);
|
||||
|
||||
for( Handler handler : rootLogger.getHandlers() ) {
|
||||
if( handler instanceof ConsoleHandler ) {
|
||||
Level handlerLevel = handler.getLevel();
|
||||
if( handlerLevel == null || handler.getLevel().intValue() > lvl ) {
|
||||
handler.setLevel(level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final void enableGlobalLevel( Level level ) {
|
||||
int lvl = Validator.requireNotNull(level).intValue();
|
||||
|
||||
// Decrease level of root level ConsoleHandlers for outpu
|
||||
Logger rootLogger = Logger.getLogger("");
|
||||
for( Handler handler : rootLogger.getHandlers() ) {
|
||||
if( handler instanceof ConsoleHandler ) {
|
||||
Level handlerLevel = handler.getLevel();
|
||||
if( handlerLevel == null || handler.getLevel().intValue() > lvl ) {
|
||||
handler.setLevel(level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrease level of all existing ZM Loggers
|
||||
Iterator<String> loggerNames = LogManager.getLogManager().getLoggerNames().asIterator();
|
||||
while( loggerNames.hasNext() ) {
|
||||
String loggerName = loggerNames.next();
|
||||
|
||||
if( loggerName.startsWith(ROOT_LOGGER) ) {
|
||||
Logger logger = Logger.getLogger(loggerName);
|
||||
logger.setLevel(level);
|
||||
|
||||
for( Handler handler : logger.getHandlers() ) {
|
||||
if( handler instanceof ConsoleHandler ) {
|
||||
Level handlerLevel = handler.getLevel();
|
||||
if( handlerLevel == null || handler.getLevel().intValue() > lvl ) {
|
||||
handler.setLevel(level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final Log getLogger( Class<?> clazz ) {
|
||||
if( !LOGGING_INIT ) {
|
||||
Logger.getLogger(ROOT_LOGGER);
|
||||
LOGGING_INIT = true;
|
||||
}
|
||||
return new Log(clazz);
|
||||
}
|
||||
|
||||
private final Logger LOGGER;
|
||||
|
||||
private final Class<?> sourceClass;
|
||||
|
||||
private Log( final Class<?> clazz ) {
|
||||
sourceClass = clazz;
|
||||
if( !clazz.getName().startsWith(ROOT_LOGGER) ) {
|
||||
LOGGER = Logger.getLogger(ROOT_LOGGER + "." + clazz.getSimpleName());
|
||||
} else {
|
||||
LOGGER = Logger.getLogger(clazz.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/*public Log( final String name ) {
|
||||
LOGGER = Logger.getLogger(name);
|
||||
}*/
|
||||
|
||||
public void log( Level level, final CharSequence msg ) {
|
||||
//LOGGER.log(level, msg::toString);
|
||||
if( LOGGER.isLoggable(level) ) {
|
||||
//LOGGER.logp(level, sourceClass.getName(), inferCallerName(), msg::toString);
|
||||
doLog(level, null, msg::toString);
|
||||
}
|
||||
}
|
||||
|
||||
public void log( Level level, final CharSequence msg, Object... params ) {
|
||||
if( LOGGER.isLoggable(level) ) {
|
||||
//LOGGER.logp(level, sourceClass.getName(), inferCallerName(), () -> String.format(msg.toString(), params));
|
||||
doLog(level, null, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
}
|
||||
|
||||
public void log( Level level, final Supplier<String> msgSupplier ) {
|
||||
if( LOGGER.isLoggable(level) ) {
|
||||
//LOGGER.logp(level, sourceClass.getName(), inferCallerName(), msgSupplier);
|
||||
doLog(level, null, msgSupplier);
|
||||
}
|
||||
}
|
||||
|
||||
public void log( Level level, final Throwable throwable, final CharSequence msg, Object... params ) {
|
||||
if( LOGGER.isLoggable(level) ) {
|
||||
//LOGGER.logp(level, sourceClass.getName(), inferCallerName(), throwable, () -> String.format(msg.toString(), params));
|
||||
doLog(level, throwable, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
}
|
||||
|
||||
public void log( Level level, final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||
if( LOGGER.isLoggable(level) ) {
|
||||
//LOGGER.logp(level, sourceClass.getName(), inferCallerName(), throwable, msgSupplier);
|
||||
doLog(level, throwable, msgSupplier);
|
||||
}
|
||||
}
|
||||
|
||||
private String inferCallerName() {
|
||||
StackTraceElement[] trace = new Throwable().getStackTrace();
|
||||
for( int i = 0; i < trace.length; i++ ) {
|
||||
/// if( trace[i].getClassName().equals(sourceClass.getName()) ) {
|
||||
if( !trace[i].getClassName().equals(Log.class.getName()) ) {
|
||||
return trace[i].getMethodName();
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private void doLog( Level level, final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||
String clazz = sourceClass.getName();
|
||||
String method = "unknown";
|
||||
|
||||
StackTraceElement[] trace = new Throwable().getStackTrace();
|
||||
for( int i = 0; i < trace.length; i++ ) {
|
||||
if( !trace[i].getClassName().equals(Log.class.getName()) ) {
|
||||
clazz = trace[i].getClassName();
|
||||
method = trace[i].getMethodName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( throwable != null ) {
|
||||
LOGGER.logp(level, clazz, method, throwable, msgSupplier);
|
||||
} else {
|
||||
LOGGER.logp(level, clazz, method, msgSupplier);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLoggable( Level level ) {
|
||||
return LOGGER.isLoggable(level);
|
||||
}
|
||||
|
||||
public void info( final CharSequence msg ) {
|
||||
this.log(INFO, msg);
|
||||
}
|
||||
|
||||
public void info( final CharSequence msg, Object... params ) {
|
||||
this.log(INFO, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void info( final Supplier<String> msgSupplier ) {
|
||||
this.log(INFO, msgSupplier);
|
||||
}
|
||||
|
||||
public void info( final Throwable ex, final CharSequence msg, Object... params ) {
|
||||
this.log(INFO, ex, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void info( final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||
this.log(INFO, throwable, msgSupplier);
|
||||
}
|
||||
|
||||
public void warn( final CharSequence msg ) {
|
||||
this.log(WARNING, msg);
|
||||
}
|
||||
|
||||
public void warn( final CharSequence msg, Object... params ) {
|
||||
this.log(WARNING, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void warn( final Supplier<String> msgSupplier ) {
|
||||
this.log(WARNING, msgSupplier);
|
||||
}
|
||||
|
||||
public void warn( final Throwable ex, final CharSequence msg, Object... params ) {
|
||||
this.log(WARNING, ex, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void warn( final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||
this.log(WARNING, throwable, msgSupplier);
|
||||
}
|
||||
|
||||
public void error( final CharSequence msg ) {
|
||||
this.log(SEVERE, msg);
|
||||
}
|
||||
|
||||
public void error( final CharSequence msg, Object... params ) {
|
||||
this.log(SEVERE, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void error( final Supplier<String> msgSupplier ) {
|
||||
this.log(SEVERE, msgSupplier);
|
||||
}
|
||||
|
||||
public void error( final Throwable ex, final CharSequence msg, Object... params ) {
|
||||
this.log(SEVERE, ex, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void error( final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||
this.log(SEVERE, throwable, msgSupplier);
|
||||
}
|
||||
|
||||
public void debug( final CharSequence msg ) {
|
||||
this.log(FINE, msg);
|
||||
}
|
||||
|
||||
public void debug( final CharSequence msg, Object... params ) {
|
||||
this.log(FINE, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void debug( final Supplier<String> msgSupplier ) {
|
||||
this.log(FINE, msgSupplier);
|
||||
}
|
||||
|
||||
public void debug( final Throwable ex, final CharSequence msg, Object... params ) {
|
||||
this.log(FINE, ex, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void debug( final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||
this.log(FINE, throwable, msgSupplier);
|
||||
}
|
||||
|
||||
public void trace( final CharSequence msg ) {
|
||||
this.log(FINER, msg);
|
||||
}
|
||||
|
||||
public void trace( final CharSequence msg, Object... params ) {
|
||||
this.log(FINER, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void trace( final Supplier<String> msgSupplier ) {
|
||||
this.log(FINER, msgSupplier);
|
||||
}
|
||||
|
||||
public void trace( final Throwable ex, final CharSequence msg, Object... params ) {
|
||||
this.log(FINER, ex, () -> String.format(msg.toString(), params));
|
||||
}
|
||||
|
||||
public void trace( final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||
this.log(FINER, throwable, msgSupplier);
|
||||
}
|
||||
|
||||
}
|
||||
171
src/schule/ngb/zm/util/ResourceStreamProvider.java
Normal file
171
src/schule/ngb/zm/util/ResourceStreamProvider.java
Normal file
@@ -0,0 +1,171 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import schule.ngb.zm.Zeichenmaschine;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogManager;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Helferklasse, um {@link InputStream}s für Resourcen zu erhalten.
|
||||
*/
|
||||
public class ResourceStreamProvider {
|
||||
|
||||
/**
|
||||
* Sucht eine zur angegebenen Quelle passende Resource und öffnet einen
|
||||
* passenden {@link InputStream}. Die konkrete Art des Streams hängt davon
|
||||
* ab, welche Art an Resource gefunden wird:
|
||||
* <ol>
|
||||
* <li>Ist {@code source} eine existierende Datei
|
||||
* {@code new File(source}.isFile() == true}, dann wird ein
|
||||
* {@link FileInputStream} erstellt.</li>
|
||||
* <li>Ist {@code source} ein relativer Pfad im Projekt, wird ein passender
|
||||
* Stream mit {@link Class#getResourceAsStream(String)} geöffnet.</li>
|
||||
* <li>Ist {@code source} im Classpath enthalten, wird ein passender Stream
|
||||
* mit {@link ClassLoader#getResourceAsStream(String)} geöffnet.</li>
|
||||
* <li>Ist {@code source} eine gültige {@link URL}, dann wird ein
|
||||
* {@link URL#openStream() Netwerkstream} geöffnet.</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* Die Möglichen Resourcen werden in der gelisteten Reihenfolge durchprobiert
|
||||
* und der erste gefundene Stream zurückgegeben. Auftretende Exceptions
|
||||
* werden als {@link IOException} geworfen. Das bedeutet, falls für {@code source}
|
||||
* keine gültige Resource gefunden werden kann, wird am Ende die
|
||||
* von {@link URL#openStream()} erzeugte {@code IOException} geworfen.
|
||||
* <p>
|
||||
* Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt.
|
||||
* Eine {@link SecurityException} beim Öffnen des {@code FileInputStream}
|
||||
* (zum Beispiel, weil auf eine existierende Datei keine Leserechte bestehen)
|
||||
* verhindert daher, dass noch im Classpath gesucht wird.
|
||||
*
|
||||
* @param source Eine Quelle für die Resource (Absoluter Dateipfad,
|
||||
* Dateipfad im Classpath oder Netzwerkresource)
|
||||
* @return Ein {@code InputStream} für die Resource
|
||||
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
||||
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
||||
* ist.
|
||||
* @throws IOException Geworfen beim öffnen des Streams zu
|
||||
* einer bestehenden Resource oder falls
|
||||
* keine passende Resource gefunden wurde.
|
||||
*/
|
||||
public static InputStream getResourceStream( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||
if( source == null ) {
|
||||
throw new NullPointerException("Resource source may not be null");
|
||||
}
|
||||
if( source.length() == 0 ) {
|
||||
throw new IllegalArgumentException("Resource source may not be empty.");
|
||||
}
|
||||
|
||||
InputStream in;
|
||||
|
||||
// See if source is a readable file
|
||||
File file = new File(source);
|
||||
try {
|
||||
if( file.isFile() ) {
|
||||
in = new FileInputStream(file);
|
||||
}
|
||||
} catch( FileNotFoundException fnfex ) {
|
||||
// Somehow an exception occurred, but we still try other sources
|
||||
in = null;
|
||||
}
|
||||
// File does not exist, try other means
|
||||
// load ressource relative to .class-file
|
||||
in = Zeichenmaschine.class.getResourceAsStream(source);
|
||||
|
||||
// relative to ClassLoader
|
||||
if( in == null ) {
|
||||
in = Zeichenmaschine.class.getClassLoader().getResourceAsStream(source);
|
||||
}
|
||||
|
||||
// load form web or jar-file
|
||||
if( in == null ) {
|
||||
in = new URL(source).openStream();
|
||||
}
|
||||
|
||||
// One of the above got a valid Stream,
|
||||
// otherwise an Exception was thrown
|
||||
return in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified
|
||||
* Resource Locator</em>). Eine passende Datei-Resource wird wie folgt
|
||||
* ermittelt:
|
||||
* <ol>
|
||||
* <li>Ist {@code source} eine existierende Datei
|
||||
* ({@code new File(source}.isFile() == true})?</li>
|
||||
* <li>Ist {@code source} ein relativer Pfad im Projekt ({@code getResource(source) != null})?.</li>
|
||||
* <li>Ist {@code source} im Classpath enthalten ({@code getClassLoader().getResource(source) != null})?</li>
|
||||
* <li>Ansonten erstellt ein {@link URL}-Objekt.</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* Ein {@code URL} für die erste gefundene Resource wird zurückgegeben.
|
||||
* Auftretende Exceptions
|
||||
* werden als {@link IOException} geworfen.
|
||||
* <p>
|
||||
* Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt.
|
||||
* Eine {@link java.net.MalformedURLException} beim Konstruieren des {@code URL}
|
||||
* zu einer Datei verhindert daher, dass noch im Classpath gesucht wird.
|
||||
*
|
||||
* @param source Eine Quelle für die Resource (Absoluter Dateipfad,
|
||||
* Dateipfad im Classpath oder Netzwerkresource)
|
||||
* @return Ein {@code InputStream} für die Resource
|
||||
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
||||
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
||||
* ist.
|
||||
* @throws IOException Geworfen beim erzeugen eines URL zu
|
||||
* einer bestehenden Resource.
|
||||
*/
|
||||
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||
if( source == null ) {
|
||||
throw new NullPointerException("Resource source may not be null");
|
||||
}
|
||||
if( source.length() == 0 ) {
|
||||
throw new IllegalArgumentException("Resource source may not be empty.");
|
||||
}
|
||||
|
||||
File file = new File(source);
|
||||
if( file.isFile() ) {
|
||||
return file.toURI().toURL();
|
||||
}
|
||||
|
||||
URL url;
|
||||
|
||||
url = Zeichenmaschine.class.getResource(source);
|
||||
if( url != null ) {
|
||||
return url;
|
||||
}
|
||||
|
||||
url = Zeichenmaschine.class.getClassLoader().getResource(source);
|
||||
if( url != null ) {
|
||||
return url;
|
||||
}
|
||||
|
||||
return new URL(source);
|
||||
}
|
||||
|
||||
public static InputStream getResourceStream( File file ) throws FileNotFoundException, SecurityException {
|
||||
if( file == null ) {
|
||||
throw new NullPointerException("Provided file can't be null.");
|
||||
}
|
||||
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
public static InputStream getResourceStream( URL url ) throws IOException {
|
||||
if( url == null ) {
|
||||
throw new NullPointerException("Provided URL can't be null.");
|
||||
}
|
||||
|
||||
return url.openStream();
|
||||
}
|
||||
|
||||
private ResourceStreamProvider() {
|
||||
}
|
||||
|
||||
|
||||
private static final Log LOG = Log.getLogger(ResourceStreamProvider.class);
|
||||
|
||||
}
|
||||
152
src/schule/ngb/zm/util/Validator.java
Normal file
152
src/schule/ngb/zm/util/Validator.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Validator {
|
||||
|
||||
public static final <T> T requireNotNull( T obj ) {
|
||||
return Objects.requireNonNull(obj);
|
||||
}
|
||||
|
||||
public static final <T> T requireNotNull( T obj, CharSequence msg ) {
|
||||
return Objects.requireNonNull(obj, msg::toString);
|
||||
}
|
||||
|
||||
public static final <T> T requireNotNull( T obj, Supplier<String> msg ) {
|
||||
return Objects.requireNonNull(obj, msg);
|
||||
}
|
||||
|
||||
public static final String requireNotEmpty( String str ) {
|
||||
return requireNotEmpty(str, (Supplier<String>)null);
|
||||
}
|
||||
|
||||
public static final String requireNotEmpty( String str, CharSequence msg ) {
|
||||
return requireNotEmpty(str, msg::toString);
|
||||
}
|
||||
|
||||
public static final String requireNotEmpty( String str, Supplier<String> msg ) {
|
||||
if( str.isEmpty() )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get());
|
||||
return str;
|
||||
}
|
||||
|
||||
public static final int requirePositive( int i ) {
|
||||
return requirePositive(i, (Supplier<String>)null);
|
||||
}
|
||||
|
||||
public static final int requirePositive( int i, CharSequence msg ) {
|
||||
return requirePositive(i, msg::toString);
|
||||
}
|
||||
|
||||
public static final int requirePositive( int i, Supplier<String> msg ) {
|
||||
if( i <= 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter must be positive (<%d> provided)", i) : msg.get());
|
||||
return i;
|
||||
}
|
||||
|
||||
public static final int requireNotNegative( int i ) {
|
||||
return requireNotNegative(i, (Supplier<String>)null);
|
||||
}
|
||||
|
||||
public static final int requireNotNegative( int i, CharSequence msg ) {
|
||||
return requireNotNegative(i, msg::toString);
|
||||
}
|
||||
|
||||
public static final int requireNotNegative( int i, Supplier<String> msg ) {
|
||||
if( i < 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be negative (<%d> provided)", i) : msg.get());
|
||||
return i;
|
||||
}
|
||||
|
||||
public static final int requireInRange( int i, int min, int max ) {
|
||||
return requireInRange(i, min, max, (Supplier<String>)null);
|
||||
}
|
||||
|
||||
public static final int requireInRange( int i, int min, int max, CharSequence msg ) {
|
||||
return requireInRange(i, min, max, msg::toString);
|
||||
}
|
||||
|
||||
public static final int requireInRange( int i, int min, int max, Supplier<String> msg ) {
|
||||
if( i < min || i > max )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter out of range <%d> to <%d> (<%d> provided)", min, max, i) : msg.get());
|
||||
return i;
|
||||
}
|
||||
|
||||
public static final double requirePositive( double i ) {
|
||||
return requirePositive(i, (Supplier<String>)null);
|
||||
}
|
||||
|
||||
public static final double requirePositive( double i, CharSequence msg ) {
|
||||
return requirePositive(i, msg::toString);
|
||||
}
|
||||
|
||||
public static final double requirePositive( double i, Supplier<String> msg ) {
|
||||
if( i <= 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter must be positive (<%f> provided)", i) : msg.get());
|
||||
return i;
|
||||
}
|
||||
|
||||
public static final double requireNotNegative( double i ) {
|
||||
return requireNotNegative(i, (Supplier<String>)null);
|
||||
}
|
||||
|
||||
public static final double requireNotNegative( double i, CharSequence msg ) {
|
||||
return requireNotNegative(i, msg::toString);
|
||||
}
|
||||
|
||||
public static final double requireNotNegative( double i, Supplier<String> msg ) {
|
||||
if( i < 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be negative (<%f> provided)", i) : msg.get());
|
||||
return i;
|
||||
}
|
||||
|
||||
public static final double requireInRange( double i, double min, double max ) {
|
||||
return requireInRange(i, min, max, (Supplier<String>)null);
|
||||
}
|
||||
|
||||
public static final double requireInRange( double i, double min, double max, CharSequence msg ) {
|
||||
return requireInRange(i, min, max, msg::toString);
|
||||
}
|
||||
|
||||
public static final double requireInRange( double i, double min, double max, Supplier<String> msg ) {
|
||||
if( i < min || i > max )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter out of range <%f> to <%f> (<%f> provided)", min, max, i) : msg.get());
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
public static final <N extends Number> N requirePositive( N num, Supplier<String> msg ) {
|
||||
if( num.doubleValue() <= 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter must be positive (<%s> provided)", num.toString()) : msg.get());
|
||||
return num;
|
||||
}
|
||||
|
||||
public static final <N extends Number> N requireNotNegative( N num, Supplier<String> msg ) {
|
||||
if( num.doubleValue() < 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be negative (<%s> provided)", num.toString()) : msg.get());
|
||||
return num;
|
||||
}
|
||||
|
||||
public static final <N extends Number> N requireInRange( N num, N min, N max, Supplier<String> msg ) {
|
||||
if( num.doubleValue() < min.doubleValue() || num.doubleValue() > max.doubleValue() )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter out of range <%s> to <%s> (<%s> provided)", min.toString(), max.toString(), num.toString()) : msg.get());
|
||||
return num;
|
||||
}
|
||||
*/
|
||||
|
||||
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
||||
return requireSize(arr, 0, ()->"Parameter array may not be empty");
|
||||
}
|
||||
|
||||
public static final <T> T[] requireSize( T[] arr, int size, Supplier<String> msg ) {
|
||||
if( arr.length != size )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array must have <%d> elements (<%d> provided)", size) : msg.get());
|
||||
return arr;
|
||||
}
|
||||
|
||||
private Validator() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user