mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-10-12 09:56:33 +02:00
Merge branch 'main' into events
# Conflicts: # src/main/java/schule/ngb/zm/media/AudioListener.java # src/main/java/schule/ngb/zm/media/Mixer.java # src/main/java/schule/ngb/zm/media/Music.java # src/schule/ngb/zm/media/Sound.java
This commit is contained in:
568
src/main/java/schule/ngb/zm/Color.java
Normal file
568
src/main/java/schule/ngb/zm/Color.java
Normal file
@@ -0,0 +1,568 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
/**
|
||||
* Repräsentiert eine Farbe in der Zeichenmaschine.
|
||||
* <p>
|
||||
* Farben bestehen entweder aus einem Grauwert (zwischen 0 und
|
||||
* 255) oder einem Rot-, Grün- und Blauanteil (jeweils zwischen
|
||||
* 0 und 255).
|
||||
* <p>
|
||||
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0
|
||||
* (unsichtbar) und 255 (deckend).
|
||||
*/
|
||||
public class Color {
|
||||
|
||||
|
||||
|
||||
//@formatter:off
|
||||
/**
|
||||
* Die Farbe Schwarz (Grauwert 0).
|
||||
*/
|
||||
public static final Color BLACK = new Color(0);
|
||||
|
||||
/**
|
||||
* Die Farbe Weiß (Grauwert 255).
|
||||
*/
|
||||
public static final Color WHITE = new Color(255);
|
||||
|
||||
/**
|
||||
* Die Farbe Grau (Grauwert 128).
|
||||
*/
|
||||
public static final Color GRAY = new Color(128);
|
||||
|
||||
/**
|
||||
* Die Farbe Dunkelgrau (Grauwert 64).
|
||||
*/
|
||||
public static final Color DARKGRAY = new Color(64);
|
||||
|
||||
/**
|
||||
* Die Farbe Hellgrau (Grauwert 192).
|
||||
*/
|
||||
public static final Color LIGHTGRAY = new Color(192);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Rot.
|
||||
*/
|
||||
public static final Color RED = new Color(240, 80, 37);
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Grün.
|
||||
*/
|
||||
public static final Color GREEN = new Color(98, 199, 119);
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Blau.
|
||||
*/
|
||||
public static final Color BLUE = new Color(49, 197, 244);
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Gelb.
|
||||
*/
|
||||
public static final Color YELLOW = new Color(248, 239, 34);
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Orange.
|
||||
*/
|
||||
public static final Color ORANGE = new Color(248, 158, 80);
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Türkis.
|
||||
*/
|
||||
public static final Color CYAN = new Color(java.awt.Color.CYAN);
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Magenta.
|
||||
*/
|
||||
public static final Color MAGENTA = new Color(java.awt.Color.MAGENTA);
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Rosa.
|
||||
*/
|
||||
public static final Color PINK = new Color(240, 99, 164);
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Lila.
|
||||
*/
|
||||
public static final Color PURPLE = new Color(101, 0, 191);
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Braun.
|
||||
*/
|
||||
public static final Color BROWN = new Color(148, 82, 0);
|
||||
|
||||
/**
|
||||
* Die Farbe Helmholtz-Grün.
|
||||
*/
|
||||
public static final Color HGGREEN = new Color(0, 165, 81);
|
||||
/**
|
||||
* Die Farbe Helmholtz-Rot.
|
||||
*/
|
||||
public static final Color HGRED = new Color(151, 54, 60);
|
||||
//@formatter:on
|
||||
|
||||
/**
|
||||
* RGBA Wert der Farbe als Integer kodiert.
|
||||
*/
|
||||
private final int rgba;
|
||||
|
||||
/**
|
||||
* Erstellt eine leere (schwarze) Farbe.
|
||||
*/
|
||||
public Color() {
|
||||
rgba = 0xFF000000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine graue Farbe entsprechend des Grauwertes <var>gray</var>.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
*/
|
||||
public Color( int gray ) {
|
||||
this(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine graue Farbe entsprechend des Grauwertes <var>gray</var> und
|
||||
* des Transparentwertes <var>alpha</var>.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
*/
|
||||
public Color( int gray, int alpha ) {
|
||||
this(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
||||
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
||||
* liegen zwischen 0 und 255.
|
||||
*
|
||||
* @param red Rotwert zwischen 0 und 255.
|
||||
* @param green Grünwert zwischen 0 und 255.
|
||||
* @param blue Blauwert zwischen 0 und 255.
|
||||
*/
|
||||
public Color( int red, int green, int blue ) {
|
||||
this(red, green, blue, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
||||
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
||||
* liegen zwischen 0 und 255.
|
||||
* <var>alpha</var> gibt den den Transparentwert an (auch zwischen
|
||||
* 0 und 255), wobei
|
||||
* 0 komplett durchsichtig ist und 255 komplett
|
||||
* deckend.
|
||||
*
|
||||
* @param red Rotwert zwischen 0 und 255.
|
||||
* @param green Grünwert zwischen 0 und 255.
|
||||
* @param blue Blauwert zwischen 0 und 255.
|
||||
* @param alpha Transparentwert zwischen 0 und 255.
|
||||
*/
|
||||
public Color( int red, int green, int blue, int alpha ) {
|
||||
rgba = (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe als Kopie von <var>color</var>.
|
||||
*
|
||||
* @param color
|
||||
*/
|
||||
public Color( Color color ) {
|
||||
this(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe als Kopie von <var>color</var> und ersetzt den
|
||||
* Transparentwert durch <var>alpha</var>.
|
||||
*
|
||||
* @param color
|
||||
* @param alpha
|
||||
*/
|
||||
public Color( Color color, int alpha ) {
|
||||
this(color.getRed(), color.getGreen(), color.getBlue(), alpha);
|
||||
}
|
||||
|
||||
public Color( java.awt.Color color ) {
|
||||
this(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
}
|
||||
|
||||
public Color( java.awt.Color color, int alpha ) {
|
||||
this(color.getRed(), color.getGreen(), color.getBlue(), alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interner Konstruktor für die Initialisierung einer Farbe mit einem
|
||||
* RGBA-Wert.
|
||||
*
|
||||
* Da der Konstruktor {@link #Color(int)} schon besetzt ist, muss hier der
|
||||
* Parameter {@code isRGBA} auf {@code true} gesetzt werden, damit {@code rgba}
|
||||
* korrekt interpretiert wird.
|
||||
* @param rgba RGBA-wert der Farbe.
|
||||
* @param isRGBA Sollte immer {@code true} sein.
|
||||
*/
|
||||
Color( int rgba, boolean isRGBA ) {
|
||||
if( !isRGBA ) {
|
||||
this.rgba = (255 << 24) | (rgba << 16) | (rgba << 8) | rgba;
|
||||
} else {
|
||||
this.rgba = rgba;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Farbe aus einem kodierten RGBA Integer-Wert.
|
||||
*
|
||||
* @param rgba
|
||||
* @return
|
||||
*/
|
||||
public static Color getRGBColor( int rgba ) {
|
||||
Color c = new Color(rgba, true);
|
||||
return c;
|
||||
}
|
||||
|
||||
public static Color getHSBColor(double h, double s, double b) {
|
||||
return new Color(java.awt.Color.getHSBColor((float)h, (float)s, (float)b));
|
||||
}
|
||||
|
||||
public static Color getHSLColor(double h, double s, double l) {
|
||||
int rgb = Color.HSLtoRGB(new float[]{(float)h, (float)s, (float)l});
|
||||
return Color.getRGBColor(rgb);
|
||||
}
|
||||
|
||||
public static Color parseString( String pColor ) {
|
||||
pColor = pColor.toLowerCase().strip();
|
||||
if( pColor.contains("red") || pColor.contains("rot") ) {
|
||||
return Color.RED.copy();
|
||||
} else if( pColor.contains("blue") || pColor.contains("blau") ) {
|
||||
return Color.BLUE.copy();
|
||||
} else if( pColor.contains("green") || pColor.contains("grün") || pColor.contains("gruen") ) {
|
||||
return Color.GREEN.copy();
|
||||
} else if( pColor.contains("yellow") || pColor.contains("gelb") ) {
|
||||
return Color.YELLOW.copy();
|
||||
} else {
|
||||
return new Color();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann
|
||||
* sechs- oder achtstellig sein (wenn ein Transparentwert vorhanden ist).
|
||||
* Dem Code kann ein {@code #} Zeichen vorangestellt sein.
|
||||
*
|
||||
* @param hexcode
|
||||
* @return
|
||||
*/
|
||||
public static Color parseHexcode( String hexcode ) {
|
||||
if( hexcode.startsWith("#") ) {
|
||||
hexcode = hexcode.substring(1);
|
||||
}
|
||||
if( hexcode.length() != 3 && hexcode.length() != 6 && hexcode.length() != 8 ) {
|
||||
throw new IllegalArgumentException("color hexcodes have to be 3, 6 or 8 digits long, got " + hexcode.length());
|
||||
}
|
||||
|
||||
// normalize hexcode to 8 digits
|
||||
int alpha = 255;
|
||||
if( hexcode.length() == 3 ) {
|
||||
hexcode = "" + hexcode.charAt(0) + hexcode.charAt(0) + hexcode.charAt(1) + hexcode.charAt(1) + hexcode.charAt(2) + hexcode.charAt(2);
|
||||
} else if( hexcode.length() == 8 ) {
|
||||
alpha = Integer.valueOf(hexcode.substring(6, 8), 16);
|
||||
hexcode = hexcode.substring(0, 6);
|
||||
} else {
|
||||
hexcode = hexcode;
|
||||
}
|
||||
|
||||
return Color.getRGBColor((alpha << 24) | Integer.valueOf(hexcode, 16));
|
||||
}
|
||||
|
||||
public static Color interpolate( java.awt.Color color1, java.awt.Color color2, double t ) {
|
||||
if( t < 0.0 || color2 == null ) {
|
||||
return new Color(color1);
|
||||
}
|
||||
if( t > 1.0 || color1 == null ) {
|
||||
return new Color(color2);
|
||||
}
|
||||
double pFactorInv = 1 - t;
|
||||
return new Color((int) (pFactorInv * color1.getRed() + t * color2.getRed()), (int) (pFactorInv * color1.getGreen() + t * color2.getGreen()), (int) (pFactorInv * color1.getBlue() + t * color2.getBlue()), (int) (pFactorInv * color1.getAlpha() + t * color2.getAlpha()));
|
||||
}
|
||||
|
||||
public static Color interpolate( Color color1, Color color2, double t ) {
|
||||
if( color1 == null && color2 == null ) {
|
||||
throw new IllegalArgumentException("Color.interpolate() needs at least one color to be not null.");
|
||||
}
|
||||
if( t < 0.0 || color2 == null ) {
|
||||
return color1.copy();
|
||||
}
|
||||
if( t > 1.0 || color1 == null ) {
|
||||
return color2.copy();
|
||||
}
|
||||
double pFactorInv = 1 - t;
|
||||
return new Color((int) (pFactorInv * color1.getRed() + t * color2.getRed()), (int) (pFactorInv * color1.getGreen() + t * color2.getGreen()), (int) (pFactorInv * color1.getBlue() + t * color2.getBlue()), (int) (pFactorInv * color1.getAlpha() + t * color2.getAlpha()));
|
||||
}
|
||||
|
||||
public static float[] RGBtoHSL( int rgb, float[] hsl ) {
|
||||
float r = ((rgb >> 16) & 255) / 255.0f;
|
||||
float g = ((rgb >> 8) & 255) / 255.0f;
|
||||
float b = (rgb & 255) / 255.0f;
|
||||
float max = Math.max(Math.max(r, g), b);
|
||||
float min = Math.min(Math.min(r, g), b);
|
||||
float c = max - min;
|
||||
|
||||
if( hsl == null ) {
|
||||
hsl = new float[3];
|
||||
}
|
||||
|
||||
float h_ = 0.f;
|
||||
if( c == 0 ) {
|
||||
h_ = 0;
|
||||
} else if( max == r ) {
|
||||
h_ = (float) (g - b) / c;
|
||||
if( h_ < 0 ) h_ += 6.f;
|
||||
} else if( max == g ) {
|
||||
h_ = (float) (b - r) / c + 2.f;
|
||||
} else if( max == b ) {
|
||||
h_ = (float) (r - g) / c + 4.f;
|
||||
}
|
||||
float h = 60.f * h_;
|
||||
|
||||
float l = (max + min) * 0.5f;
|
||||
|
||||
float s;
|
||||
if( c == 0 ) {
|
||||
s = 0.f;
|
||||
} else {
|
||||
s = c / (1 - Math.abs(2.f * l - 1.f));
|
||||
}
|
||||
|
||||
hsl[0] = h;
|
||||
hsl[1] = s;
|
||||
hsl[2] = l;
|
||||
return hsl;
|
||||
}
|
||||
|
||||
public static int HSLtoRGB( float[] hsl ) {
|
||||
return HSLtoRGB(hsl, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert eine Farbe mit Komponenten im HSL-Farbraum in den
|
||||
* RGB-Farbraum.
|
||||
*
|
||||
* Die Farbkomponenten werden als Float-Array übergeben. Im Index 0 steht
|
||||
* der h-Wert im Bereich 0 bis 360, Index 1 und 2 enthalten den s- und
|
||||
* l-Wert im Bereich von 0 bis 1.
|
||||
* @param hsl Die Farbkomponenten im HSL-Farbraum.
|
||||
* @param alpha Ein Transparenzwert im Bereich 0 bis 255.
|
||||
* @return Der RGBA-Wert der Farbe.
|
||||
*/
|
||||
public static int HSLtoRGB( float[] hsl, int alpha ) {
|
||||
float h = hsl[0];
|
||||
float s = hsl[1];
|
||||
float l = hsl[2];
|
||||
|
||||
float c = (1 - Math.abs(2.f * l - 1.f)) * s;
|
||||
float h_ = h / 60.f;
|
||||
float h_mod2 = h_;
|
||||
if( h_mod2 >= 4.f ) h_mod2 -= 4.f;
|
||||
else if( h_mod2 >= 2.f ) h_mod2 -= 2.f;
|
||||
|
||||
float x = c * (1 - Math.abs(h_mod2 - 1));
|
||||
float r_, g_, b_;
|
||||
if( h_ < 1 ) {
|
||||
r_ = c;
|
||||
g_ = x;
|
||||
b_ = 0;
|
||||
} else if( h_ < 2 ) {
|
||||
r_ = x;
|
||||
g_ = c;
|
||||
b_ = 0;
|
||||
} else if( h_ < 3 ) {
|
||||
r_ = 0;
|
||||
g_ = c;
|
||||
b_ = x;
|
||||
} else if( h_ < 4 ) {
|
||||
r_ = 0;
|
||||
g_ = x;
|
||||
b_ = c;
|
||||
} else if( h_ < 5 ) {
|
||||
r_ = x;
|
||||
g_ = 0;
|
||||
b_ = c;
|
||||
} else {
|
||||
r_ = c;
|
||||
g_ = 0;
|
||||
b_ = x;
|
||||
}
|
||||
|
||||
float m = l - (0.5f * c);
|
||||
int r = (int) ((r_ + m) * (255.f) + 0.5f);
|
||||
int g = (int) ((g_ + m) * (255.f) + 0.5f);
|
||||
int b = (int) ((b_ + m) * (255.f) + 0.5f);
|
||||
return (alpha & 255) << 24 | r << 16 | g << 8 | b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Kopie dieser Farbe.
|
||||
* @return Ein neues Farbobjekt.
|
||||
*/
|
||||
public Color copy() {
|
||||
return new Color(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den RGBA-Wert dieser Farbe zurück.
|
||||
*
|
||||
* Eine Farbe wird als 32-Bit Integer gespeichert. Bits 24-31 enthalten
|
||||
* den Transparenzwert, 16-23 den Rotwert, 8-15 den Grünwert und 0-7 den
|
||||
* Blauwert der Farbe.
|
||||
* @return Der RGBA-Wert der Farbe.
|
||||
* @see #getRed()
|
||||
* @see #getGreen()
|
||||
* @see #getBlue()
|
||||
* @see #getAlpha()
|
||||
*/
|
||||
public int getRGBA() {
|
||||
return rgba;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Rotwert dieser Farbe zurück.
|
||||
* @return Der Rotwert der Farbe zwischen 0 und 255.
|
||||
*/
|
||||
public int getRed() {
|
||||
return (rgba >> 16) & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Grünwert dieser Farbe zurück.
|
||||
* @return Der Grünwert der Farbe zwischen 0 und 255.
|
||||
*/
|
||||
public int getGreen() {
|
||||
return (rgba >> 8) & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Blauwert dieser Farbe zurück.
|
||||
* @return Der Blauwert der Farbe zwischen 0 und 255.
|
||||
*/
|
||||
public int getBlue() {
|
||||
return rgba & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Transparenzwert dieser Farbe zurück.
|
||||
* @return Der Transparenzwert der Farbe zwischen 0 und 255.
|
||||
*/
|
||||
public int getAlpha() {
|
||||
return (rgba >> 24) & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt ein {@link java.awt.Color}-Objekt aus dieser Farbe.
|
||||
*
|
||||
* Das erzeugte Farbobjekt hat dieselben Rot-, Grün-, Blau-
|
||||
* und Transparenzwerte wie diese Farbe.
|
||||
* @return Ein Java-Farbobjekt.
|
||||
*/
|
||||
public java.awt.Color getJavaColor() {
|
||||
return new java.awt.Color(rgba, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
||||
*
|
||||
* Die Methode gibt genau dann {@code true} zurück, wenn das andere
|
||||
* Objekt nicht {@code null} ist, vom Typ {@code Color} ist und es
|
||||
* dieselben Rot-, Grün-, Blau- und Transparenzwerte hat.
|
||||
* @param obj Das zu vergleichende Objekt.
|
||||
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
||||
*/
|
||||
public boolean equals( Object obj ) {
|
||||
return obj instanceof Color && ((Color)obj).getRGBA() == this.rgba;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Text-String, der diese Farbe beschreibt.
|
||||
* @return Eine Textversion der Farbe.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getCanonicalName() + '[' + "r=" + getRed() + ',' + "g=" + getGreen() + ',' + "b=" + getBlue() + ',' + "a=" + getAlpha() + ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet einen Hashcode für dieses Farbobjekt.
|
||||
* @return Ein Hashcode für diese Rabe.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return rgba;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine um 30% hellere Version dieser Farbe.
|
||||
* @return Ein Farbobjekt mit einer helleren Farbe.
|
||||
*/
|
||||
public Color brighter() {
|
||||
return brighter(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine um {@code percent} hellere Version dieser Farbe.
|
||||
* @param percent Eine Prozentzahl zwischen 0 und 100.
|
||||
* @return Ein Farbobjekt mit einer helleren Farbe.
|
||||
*/
|
||||
public Color brighter( int percent ) {
|
||||
float[] hsl = RGBtoHSL(rgba, null);
|
||||
hsl[2] = Math.min(Math.max(hsl[2] * (1.0f + percent / 100.0f), 0.0f), 1.0f);
|
||||
return Color.getRGBColor(HSLtoRGB(hsl, getAlpha()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine um 30% dunklere Version dieser Farbe.
|
||||
* @return Ein Farbobjekt mit einer dunkleren Farbe.
|
||||
*/
|
||||
public Color darker() {
|
||||
return darker(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine um {@code percent} dunklere Version dieser Farbe.
|
||||
* @param percent Eine Prozentzahl zwischen 0 und 100.
|
||||
* @return Ein Farbobjekt mit einer dunkleren Farbe.
|
||||
*/
|
||||
public Color darker( int percent ) {
|
||||
float[] hsl = RGBtoHSL(rgba, null);
|
||||
hsl[2] = Math.min(Math.max(hsl[2] * (1.0f - percent / 100.0f), 0.0f), 1.0f);
|
||||
return Color.getRGBColor(HSLtoRGB(hsl, getAlpha()));
|
||||
}
|
||||
|
||||
public Color greyscale() {
|
||||
return new Color((int)(getRed()*.299 + getGreen()*.587 + getBlue()*0.114));
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine zu dieser invertierte Farbe.
|
||||
* @return Ein Farbobjekt mit der invertierten Farbe.
|
||||
*/
|
||||
public Color inverted() {
|
||||
//return new Color((0xFFFFFFFF - (0x00FFFFFF & rgba)) | (0xFF000000 & rgba));
|
||||
return new Color(rgba ^ 0x00FFFFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt die Komplementärfarbe zu dieser.
|
||||
* @return Ein Farbobjekt mit der Komplementärfarbe.
|
||||
*/
|
||||
public Color complement() {
|
||||
float[] hsl = RGBtoHSL(rgba, null);
|
||||
hsl[0] = 360 - hsl[0];
|
||||
return new Color(HSLtoRGB(hsl, getAlpha()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt entweder {@link #WHITE weiß} oder {@link #BLACK schwarz} aus, je
|
||||
* nachdem, welche der Farben besser als Textfarbe mit dieser Farbe als
|
||||
* Hintergrund funktioniert (besser lesbar ist).
|
||||
* @return Schwarz oder weiß.
|
||||
*/
|
||||
public Color textcolor() {
|
||||
// Basiert auf https://stackoverflow.com/questions/946544/good-text-foreground-color-for-a-given-background-color
|
||||
if( (getRed()*.299 + getGreen()*.587 + getBlue()*0.114) < 186 ) {
|
||||
return WHITE;
|
||||
} else {
|
||||
return BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
55
src/main/java/schule/ngb/zm/ColorLayer.java
Normal file
55
src/main/java/schule/ngb/zm/ColorLayer.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
public class ColorLayer extends Layer {
|
||||
|
||||
private Color background;
|
||||
|
||||
public ColorLayer( Color color ) {
|
||||
this.background = color;
|
||||
clear();
|
||||
}
|
||||
|
||||
public ColorLayer( int width, int height, Color color ) {
|
||||
super(width, height);
|
||||
this.background = color;
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSize( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
clear();
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return background;
|
||||
}
|
||||
|
||||
public void setColor( Color color ) {
|
||||
background = color;
|
||||
clear();
|
||||
}
|
||||
|
||||
public void setColor( int gray ) {
|
||||
setColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setColor( int gray, int alpha ) {
|
||||
setColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setColor( int red, int green, int blue ) {
|
||||
setColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setColor( int red, int green, int blue, int alpha ) {
|
||||
setColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
drawing.setColor(background.getJavaColor());
|
||||
drawing.fillRect(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
}
|
||||
1383
src/main/java/schule/ngb/zm/Constants.java
Normal file
1383
src/main/java/schule/ngb/zm/Constants.java
Normal file
File diff suppressed because it is too large
Load Diff
31
src/main/java/schule/ngb/zm/Drawable.java
Normal file
31
src/main/java/schule/ngb/zm/Drawable.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* Zeichenbare Objekte können auf eine Zeichenfläche gezeichnet werden.
|
||||
* In der Regel werden sie einmal pro Frame gezeichnet.
|
||||
*/
|
||||
public interface Drawable {
|
||||
|
||||
/**
|
||||
* Gibt an, ob das Objekt derzeit sichtbar ist (also gezeichnet werden
|
||||
* muss).
|
||||
*
|
||||
* @return {@code true}, wenn das Objekt sichtbar ist.
|
||||
*/
|
||||
boolean isVisible();
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, um das Objekt auf die Zeichenfläche <var>graphics</var>
|
||||
* zu draw.
|
||||
* <p>
|
||||
* Das Objekt muss dafür Sorge tragen, dass der Zustand der Zeichenfläche
|
||||
* (Transformationsmatrix, Farbe, ...) erhalten bleibt. Das Objekt sollte
|
||||
* also etwaige Änderungen am Ende des Aufrufs wieder rückgängig machen.
|
||||
*
|
||||
* @param graphics Die Zeichenfläche.
|
||||
*/
|
||||
void draw( Graphics2D graphics );
|
||||
|
||||
}
|
||||
56
src/main/java/schule/ngb/zm/DrawableLayer.java
Normal file
56
src/main/java/schule/ngb/zm/DrawableLayer.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class DrawableLayer extends Layer {
|
||||
|
||||
protected LinkedList<Drawable> drawables = new LinkedList<>();
|
||||
|
||||
protected boolean clearBeforeDraw = true;
|
||||
|
||||
public DrawableLayer() {
|
||||
}
|
||||
|
||||
public DrawableLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
public void add( Drawable... drawables ) {
|
||||
synchronized( drawables ) {
|
||||
for( Drawable d : drawables ) {
|
||||
this.drawables.add(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public java.util.List<Drawable> getDrawables() {
|
||||
return drawables;
|
||||
}
|
||||
|
||||
public boolean isClearBeforeDraw() {
|
||||
return clearBeforeDraw;
|
||||
}
|
||||
|
||||
public void setClearBeforeDraw( boolean pClearBeforeDraw ) {
|
||||
this.clearBeforeDraw = pClearBeforeDraw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
if( clearBeforeDraw ) {
|
||||
clear();
|
||||
}
|
||||
|
||||
synchronized( drawables ) {
|
||||
for( Drawable d : drawables ) {
|
||||
if( d.isVisible() ) {
|
||||
d.draw(drawing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(pGraphics);
|
||||
}
|
||||
|
||||
}
|
||||
559
src/main/java/schule/ngb/zm/DrawingLayer.java
Normal file
559
src/main/java/schule/ngb/zm/DrawingLayer.java
Normal file
@@ -0,0 +1,559 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
import java.util.Stack;
|
||||
|
||||
public class DrawingLayer extends Layer {
|
||||
|
||||
protected Color fillColor = STD_FILLCOLOR;
|
||||
|
||||
protected Color strokeColor = STD_STROKECOLOR;
|
||||
|
||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
private Options.Direction default_anchor = CENTER;
|
||||
|
||||
protected Line2D.Double line = new Line2D.Double();
|
||||
protected Ellipse2D.Double ellipse = new Ellipse2D.Double();
|
||||
protected Rectangle2D.Double rect = new Rectangle2D.Double();
|
||||
protected Arc2D.Double arc = new Arc2D.Double();
|
||||
|
||||
protected Path2D.Double path = new Path2D.Double();
|
||||
|
||||
private boolean pathStarted = false;
|
||||
|
||||
private Stack<AffineTransform> transformStack = new Stack<>();
|
||||
|
||||
private FontMetrics fontMetrics = null;
|
||||
|
||||
public DrawingLayer() {
|
||||
super();
|
||||
transformStack.push(new AffineTransform());
|
||||
fontMetrics = drawing.getFontMetrics();
|
||||
}
|
||||
|
||||
public DrawingLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
transformStack.push(new AffineTransform());
|
||||
fontMetrics = drawing.getFontMetrics();
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
public void setFillColor( int gray ) {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( Color color ) {
|
||||
fillColor = color;
|
||||
drawing.setColor(color.getJavaColor());
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
fillColor = null;
|
||||
}
|
||||
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
setFillColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
setFillColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray ) {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color color ) {
|
||||
strokeColor = color;
|
||||
drawing.setColor(color.getJavaColor());
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
strokeColor = null;
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
setStrokeColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
setStrokeColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
setStrokeColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void setStrokeWeight( double pWeight ) {
|
||||
strokeWeight = pWeight;
|
||||
drawing.setStroke(createStroke());
|
||||
}
|
||||
|
||||
protected Stroke createStroke() {
|
||||
switch( strokeType ) {
|
||||
case DOTTED:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
||||
case DASHED:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{5.0f}, 0.0f);
|
||||
default:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND);
|
||||
}
|
||||
}
|
||||
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return strokeType;
|
||||
}
|
||||
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
switch( type ) {
|
||||
case DASHED:
|
||||
this.strokeType = DASHED;
|
||||
break;
|
||||
case DOTTED:
|
||||
this.strokeType = DOTTED;
|
||||
break;
|
||||
default:
|
||||
this.strokeType = SOLID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void resetStroke() {
|
||||
setStrokeColor(STD_STROKECOLOR);
|
||||
setStrokeWeight(STD_STROKEWEIGHT);
|
||||
setStrokeType(SOLID);
|
||||
}
|
||||
|
||||
public void setAnchor( Options.Direction anchor ) {
|
||||
default_anchor = anchor;
|
||||
}
|
||||
|
||||
public void clear( int gray ) {
|
||||
clear(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void clear( int gray, int alpha ) {
|
||||
clear(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void clear( int red, int green, int blue ) {
|
||||
clear(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void clear( int red, int green, int blue, int alpha ) {
|
||||
clear(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void clear( Color pColor ) {
|
||||
/*graphics.setBackground(pColor);
|
||||
graphics.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());*/
|
||||
java.awt.Color currentColor = drawing.getColor();
|
||||
pushMatrix();
|
||||
resetMatrix();
|
||||
drawing.setColor(pColor.getJavaColor());
|
||||
drawing.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
|
||||
drawing.setColor(currentColor);
|
||||
popMatrix();
|
||||
}
|
||||
|
||||
public void line( double x1, double y1, double x2, double y2 ) {
|
||||
//Shape line = new Line2D.Double(x1, y1, x2, y2);
|
||||
line.setLine(x1, y1, x2, y2);
|
||||
drawShape(line);
|
||||
}
|
||||
|
||||
public void pixel( double x, double y ) {
|
||||
square(x, y, 1);
|
||||
}
|
||||
|
||||
public void square( double x, double y, double w ) {
|
||||
rect(x, y, w, w);
|
||||
}
|
||||
|
||||
public void square( double x, double y, double w, Options.Direction anchor ) {
|
||||
rect(x, y, w, w, anchor);
|
||||
}
|
||||
|
||||
public void rect( double x, double y, double w, double h ) {
|
||||
rect(x, y, w, h, default_anchor);
|
||||
}
|
||||
|
||||
public void rect( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, anchor);
|
||||
// Shape rect = new Rectangle2D.Double(anchorPoint.getX(), anchorPoint.getY(), w, h);
|
||||
rect.setRect(anchorPoint.getX(), anchorPoint.getY(), w, h);
|
||||
fillShape(rect);
|
||||
drawShape(rect);
|
||||
}
|
||||
|
||||
public void point( double x, double y ) {
|
||||
circle(x - 1, y - 1, 2);
|
||||
}
|
||||
|
||||
public void circle( double x, double y, double r ) {
|
||||
ellipse(x, y, r+r, r+r, default_anchor);
|
||||
}
|
||||
|
||||
public void circle( double x, double y, double r, Options.Direction anchor ) {
|
||||
ellipse(x, y, r+r, r+r, anchor);
|
||||
}
|
||||
|
||||
public void ellipse( double x, double y, double w, double h ) {
|
||||
ellipse(x, y, w, h, default_anchor);
|
||||
}
|
||||
|
||||
public void ellipse( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, anchor);
|
||||
// Shape ellipse = new Ellipse2D.Double(anchorPoint.x, anchorPoint.y, w, h);
|
||||
ellipse.setFrame(anchorPoint.x, anchorPoint.y, w, h);
|
||||
fillShape(ellipse);
|
||||
drawShape(ellipse);
|
||||
}
|
||||
|
||||
public void arc( double x, double y, double r, double angle1, double angle2 ) {
|
||||
arc(x, y, r+r, r+r, angle1, angle2);
|
||||
}
|
||||
|
||||
public void arc( double x, double y, double w, double h, double angle1, double angle2 ) {
|
||||
while( angle2 < angle1 ) angle2 += 360.0;
|
||||
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, CENTER);
|
||||
/*Shape arc = new Arc2D.Double(
|
||||
anchorPoint.x,
|
||||
anchorPoint.y,
|
||||
d, d,
|
||||
angle1, angle2 - angle1,
|
||||
Arc2D.OPEN
|
||||
);*/
|
||||
arc.setArc(
|
||||
anchorPoint.x, anchorPoint.y,
|
||||
w, h,
|
||||
//Math.toRadians(angle1), Math.toRadians(angle2 - angle1),
|
||||
angle1, angle2-angle1,
|
||||
Arc2D.OPEN
|
||||
);
|
||||
|
||||
drawShape(arc);
|
||||
}
|
||||
|
||||
public void pie( double x, double y, double r, double angle1, double angle2 ) {
|
||||
while( angle2 < angle1 ) angle2 += 360.0;
|
||||
|
||||
double d = r+r;
|
||||
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, d, d, CENTER);
|
||||
/*Shape arc = new Arc2D.Double(
|
||||
anchorPoint.x,
|
||||
anchorPoint.y,
|
||||
d, d,
|
||||
angle1, angle2 - angle1,
|
||||
Arc2D.PIE
|
||||
);*/
|
||||
arc.setArc(
|
||||
anchorPoint.x, anchorPoint.y,
|
||||
d, d,
|
||||
angle1, angle2 - angle1,
|
||||
Arc2D.PIE
|
||||
);
|
||||
|
||||
fillShape(arc);
|
||||
drawShape(arc);
|
||||
}
|
||||
|
||||
public void curve( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
||||
QuadCurve2D curve = new QuadCurve2D.Double(x1, y1, x2, y2, x3, y3);
|
||||
drawShape(curve);
|
||||
}
|
||||
|
||||
public void curve( double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4 ) {
|
||||
CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
drawShape(curve);
|
||||
}
|
||||
|
||||
public void triangle( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
||||
Path2D path = new Path2D.Double();
|
||||
path.moveTo(x1, y1);
|
||||
path.lineTo(x2, y2);
|
||||
path.lineTo(x3, y3);
|
||||
path.lineTo(x1, y1);
|
||||
|
||||
fillShape(path);
|
||||
drawShape(path);
|
||||
}
|
||||
|
||||
public void rhombus( double x, double y, double width, double height ) {
|
||||
rhombus(x, y, width, height, default_anchor);
|
||||
}
|
||||
|
||||
public void rhombus( double x, double y, double width, double height, Options.Direction anchor ) {
|
||||
double whalf = width / 2.0, hhalf = height / 2.0;
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, width, height, anchor);
|
||||
polygon(anchorPoint.x + whalf, anchorPoint.y, anchorPoint.x + width, anchorPoint.y + hhalf, anchorPoint.x + whalf, anchorPoint.y + height, anchorPoint.x, anchorPoint.y + hhalf);
|
||||
}
|
||||
|
||||
public void polygon( double... coordinates ) {
|
||||
if( coordinates.length < 4 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path2D path = new Path2D.Double();
|
||||
path.moveTo(coordinates[0], coordinates[1]);
|
||||
for( int i = 2; i < coordinates.length; i += 2 ) {
|
||||
if( i + 1 < coordinates.length ) {
|
||||
path.lineTo(coordinates[i], coordinates[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
int len = coordinates.length;
|
||||
if( coordinates[len - 2] != coordinates[0] || coordinates[len - 1] != coordinates[1] ) {
|
||||
path.lineTo(coordinates[0], coordinates[1]);
|
||||
}
|
||||
|
||||
fillShape(path);
|
||||
drawShape(path);
|
||||
}
|
||||
|
||||
public void polygon( Point2D... points ) {
|
||||
if( points.length < 2 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path2D path = new Path2D.Double();
|
||||
path.moveTo(points[0].getX(), points[0].getY());
|
||||
for( int i = 1; i < points.length; i += 1 ) {
|
||||
path.lineTo(points[i].getX(), points[i].getY());
|
||||
}
|
||||
|
||||
int len = points.length;
|
||||
if( points[len - 1].equals(points[0]) ) {
|
||||
path.moveTo(points[0].getX(), points[0].getY());
|
||||
}
|
||||
|
||||
fillShape(path);
|
||||
drawShape(path);
|
||||
}
|
||||
|
||||
// TODO: General shape drawing
|
||||
/*
|
||||
public void beginShape() {
|
||||
path.reset();
|
||||
pathStarted = false;
|
||||
}
|
||||
|
||||
public void lineTo( double x, double y ) {
|
||||
if( !pathStarted ) {
|
||||
path.moveTo(x, y);
|
||||
pathStarted = true;
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public void endShape() {
|
||||
path.closePath();
|
||||
path.trimToSize();
|
||||
pathStarted = false;
|
||||
|
||||
fillShape(path);
|
||||
drawShape(path);
|
||||
}
|
||||
*/
|
||||
|
||||
public void image( String imageSource, double x, double y ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, default_anchor);
|
||||
}
|
||||
|
||||
public void image( String imageSource, double x, double y, Options.Direction anchor ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor);
|
||||
}
|
||||
|
||||
public void image( String imageSource, double x, double y, double scale ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, scale, default_anchor);
|
||||
}
|
||||
|
||||
public void image( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, scale, anchor);
|
||||
}
|
||||
|
||||
public void image( Image image, double x, double y ) {
|
||||
image(image, x, y, 1.0, default_anchor);
|
||||
}
|
||||
|
||||
public void image( Image image, double x, double y, double scale ) {
|
||||
image(image, x, y, scale, default_anchor);
|
||||
}
|
||||
|
||||
public void image( Image image, double x, double y, double scale, Options.Direction anchor ) {
|
||||
if( image != null ) {
|
||||
double neww = image.getWidth(null) * scale;
|
||||
double newh = image.getHeight(null) * scale;
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, neww, newh, anchor);
|
||||
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) neww, (int) newh, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void text( String text, double x, double y ) {
|
||||
text(text, x, y, default_anchor);
|
||||
}
|
||||
|
||||
public void text( String text, double x, double y, Options.Direction anchor ) {
|
||||
FontMetrics fm = drawing.getFontMetrics();
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y + fm.getAscent(), fm.stringWidth(text), fm.getHeight(), anchor);
|
||||
|
||||
drawing.drawString(text, (float) anchorPoint.x, (float) anchorPoint.y);
|
||||
}
|
||||
|
||||
public void setFontSize( int size ) {
|
||||
setFont(drawing.getFont().deriveFont((float)size));
|
||||
}
|
||||
|
||||
public void setFont( String fontName ) {
|
||||
Font font = new Font(fontName, drawing.getFont().getStyle(), drawing.getFont().getSize());
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
public void setFont( String fontName, int size ) {
|
||||
Font font = new Font(fontName, drawing.getFont().getStyle(), size);
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
public void setFont( String fontName, int size, int style ) {
|
||||
Font font = new Font(fontName, style, size);
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
public void setFont( Font font ) {
|
||||
drawing.setFont(font);
|
||||
fontMetrics = drawing.getFontMetrics();
|
||||
}
|
||||
|
||||
|
||||
private Point2D.Double transformToCanvas( double x, double y ) {
|
||||
return transformToCanvas(new Point2D.Double(x,y));
|
||||
}
|
||||
|
||||
private Point2D.Double transformToCanvas( Point2D.Double pPoint ) {
|
||||
AffineTransform matrix = getMatrix();
|
||||
matrix.transform(pPoint, pPoint);
|
||||
return pPoint;
|
||||
}
|
||||
|
||||
private Point2D.Double transformToUser( double x, double y ) {
|
||||
return transformToUser(new Point2D.Double(x,y));
|
||||
}
|
||||
|
||||
private Point2D.Double transformToUser( Point2D.Double pPoint ) {
|
||||
AffineTransform matrix = getMatrix();
|
||||
|
||||
try {
|
||||
matrix.inverseTransform(pPoint, pPoint);
|
||||
} catch( NoninvertibleTransformException e ) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return pPoint;
|
||||
}
|
||||
|
||||
private Point2D.Double getAnchorPoint( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||
double whalf = w * .5, hhalf = h * .5;
|
||||
|
||||
// anchor == CENTER
|
||||
Point2D.Double anchorPoint = new Point2D.Double(x - whalf, y - hhalf);
|
||||
|
||||
if( NORTH.in(anchor) ) {
|
||||
anchorPoint.y += hhalf;
|
||||
}
|
||||
if( SOUTH.in(anchor) ) {
|
||||
anchorPoint.y -= hhalf;
|
||||
}
|
||||
if( WEST.in(anchor) ) {
|
||||
anchorPoint.x += whalf;
|
||||
}
|
||||
if( EAST.in(anchor) ) {
|
||||
anchorPoint.x -= whalf;
|
||||
}
|
||||
|
||||
return anchorPoint;
|
||||
}
|
||||
|
||||
private void fillShape( Shape pShape ) {
|
||||
if( fillColor != null && fillColor.getAlpha() > 0.0 ) {
|
||||
drawing.setColor(fillColor.getJavaColor());
|
||||
drawing.fill(pShape);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawShape( Shape pShape ) {
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0.0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.setStroke(createStroke());
|
||||
drawing.draw(pShape);
|
||||
}
|
||||
}
|
||||
|
||||
public void translate( double dx, double dy ) {
|
||||
drawing.translate(dx, dy);
|
||||
}
|
||||
|
||||
public void scale( double factor ) {
|
||||
drawing.scale(factor, factor);
|
||||
}
|
||||
|
||||
public void rotate( double pAngle ) {
|
||||
drawing.rotate(Math.toRadians(pAngle));
|
||||
}
|
||||
|
||||
public void shear( double dx, double dy ) {
|
||||
drawing.shear(dx, dy);
|
||||
}
|
||||
|
||||
public AffineTransform getMatrix() {
|
||||
return drawing.getTransform();
|
||||
}
|
||||
|
||||
public void pushMatrix() {
|
||||
transformStack.push(drawing.getTransform());
|
||||
}
|
||||
|
||||
public void popMatrix() {
|
||||
if( transformStack.isEmpty() ) {
|
||||
resetMatrix();
|
||||
} else {
|
||||
drawing.setTransform(transformStack.pop());
|
||||
}
|
||||
}
|
||||
|
||||
public void resetMatrix() {
|
||||
drawing.setTransform(new AffineTransform());
|
||||
}
|
||||
|
||||
}
|
||||
11
src/main/java/schule/ngb/zm/GraphicsLayer.java
Normal file
11
src/main/java/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;
|
||||
}
|
||||
|
||||
}
|
||||
69
src/main/java/schule/ngb/zm/ImageLayer.java
Normal file
69
src/main/java/schule/ngb/zm/ImageLayer.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
|
||||
public class ImageLayer extends Layer {
|
||||
|
||||
protected Image image;
|
||||
|
||||
protected double x = 0;
|
||||
|
||||
protected double y = 0;
|
||||
|
||||
protected boolean redraw = true;
|
||||
|
||||
public ImageLayer(String source) {
|
||||
image = ImageLoader.loadImage(source);
|
||||
}
|
||||
|
||||
public ImageLayer(Image image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public ImageLayer(int width, int height, Image image) {
|
||||
super(width, height);
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
this.image = image;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(double pX) {
|
||||
this.x = pX;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(double pY) {
|
||||
this.y = pY;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D graphics) {
|
||||
if (redraw && visible) {
|
||||
drawing.drawImage(image, (int) x, (int) y, null);
|
||||
redraw = false;
|
||||
}
|
||||
super.draw(graphics);
|
||||
}
|
||||
|
||||
}
|
||||
140
src/main/java/schule/ngb/zm/Layer.java
Normal file
140
src/main/java/schule/ngb/zm/Layer.java
Normal file
@@ -0,0 +1,140 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
|
||||
protected BufferedImage buffer;
|
||||
|
||||
protected Graphics2D drawing;
|
||||
|
||||
protected boolean visible = true;
|
||||
|
||||
protected boolean active = true;
|
||||
|
||||
|
||||
public Layer() {
|
||||
this(STD_WIDTH, STD_HEIGHT);
|
||||
}
|
||||
|
||||
public Layer( int width, int height ) {
|
||||
createCanvas(width, height);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return buffer.getWidth();
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return buffer.getHeight();
|
||||
}
|
||||
|
||||
public void setSize( int width, int height ) {
|
||||
if( buffer != null ) {
|
||||
if( buffer.getWidth() != width || buffer.getHeight() != height ) {
|
||||
recreateCanvas(width, height);
|
||||
}
|
||||
} else {
|
||||
createCanvas(width, height);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
drawing.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Puffer für die Ebene und konfiguriert diesen.
|
||||
*
|
||||
* @param width Width des neuen Puffers.
|
||||
* @param height Höhe des neuen Puffers.
|
||||
*/
|
||||
private void createCanvas( int width, int height ) {
|
||||
buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
drawing = buffer.createGraphics();
|
||||
|
||||
// add antialiasing
|
||||
RenderingHints hints = new RenderingHints(
|
||||
RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON
|
||||
);
|
||||
hints.put(
|
||||
RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_ON
|
||||
);
|
||||
hints.put(
|
||||
RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_QUALITY
|
||||
);
|
||||
drawing.addRenderingHints(hints);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param height Höhe des neuen Puffers.
|
||||
*/
|
||||
private void recreateCanvas( int width, int height ) {
|
||||
BufferedImage oldCanvas = buffer;
|
||||
createCanvas(width, height);
|
||||
drawing.drawImage(oldCanvas, 0, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leert die Ebene und löscht alles bisher gezeichnete. Alle Pixel der
|
||||
* Ebene werden transparent, damit unterliegende Ebenen durchscheinen können.
|
||||
*/
|
||||
public void clear() {
|
||||
// https://stackoverflow.com/questions/31149206/set-pixels-of-bufferedimage-as-transparent
|
||||
Color current = drawing.getColor();
|
||||
drawing.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
|
||||
drawing.setColor(new Color(255, 255, 255, 255));
|
||||
drawing.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
|
||||
drawing.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
|
||||
drawing.setColor(current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet den Puffer auf die Grafik-Instanz.
|
||||
*
|
||||
* @param pGraphics
|
||||
*/
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
if( visible ) {
|
||||
pGraphics.drawImage(buffer, 0, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
public void view() {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
public void toggle() {
|
||||
visible = !visible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( double delta ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
}
|
||||
99
src/main/java/schule/ngb/zm/Options.java
Normal file
99
src/main/java/schule/ngb/zm/Options.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.geom.Arc2D;
|
||||
|
||||
/**
|
||||
* Diese Klasse sammelt Enumerationen, die verschiedene Eigenschaften der zu
|
||||
* zeichnenden Formen darstellen.
|
||||
*/
|
||||
public final class Options {
|
||||
|
||||
private Options() {
|
||||
}
|
||||
|
||||
public enum StrokeType {
|
||||
SOLID, DASHED, DOTTED
|
||||
}
|
||||
|
||||
public enum ArrowHead {
|
||||
LINES, FILLED
|
||||
}
|
||||
|
||||
public enum PathType {
|
||||
OPEN(Arc2D.OPEN), CLOSED(Arc2D.CHORD), PIE(Arc2D.PIE);
|
||||
|
||||
public final int awt_type;
|
||||
|
||||
PathType( int type ) {
|
||||
awt_type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AppState {
|
||||
INITIALIZING,
|
||||
INITIALIZED,
|
||||
RUNNING,
|
||||
UPDATING,
|
||||
DRAWING,
|
||||
PAUSED,
|
||||
STOPPED,
|
||||
TERMINATED
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
CENTER(0, 0),
|
||||
NORTH(0, -1),
|
||||
EAST(1, 0),
|
||||
SOUTH(0, 1),
|
||||
WEST(-1, 0),
|
||||
|
||||
NORTHEAST(1, -1),
|
||||
SOUTHEAST(1, 1),
|
||||
NORTHWEST(-1, -1),
|
||||
SOUTHWEST(-1, 1),
|
||||
|
||||
MIDDLE(CENTER),
|
||||
UP(NORTH),
|
||||
DOWN(SOUTH),
|
||||
LEFT(WEST),
|
||||
RIGHT(EAST);
|
||||
|
||||
public final byte x, y;
|
||||
|
||||
Direction( int x, int y ) {
|
||||
this.x = (byte)x;
|
||||
this.y = (byte)y;
|
||||
}
|
||||
|
||||
Direction( Direction original ) {
|
||||
this.x = original.x;
|
||||
this.y = original.y;
|
||||
}
|
||||
|
||||
public boolean in( Direction dir ) {
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
166
src/main/java/schule/ngb/zm/Shape2DLayer.java
Normal file
166
src/main/java/schule/ngb/zm/Shape2DLayer.java
Normal file
@@ -0,0 +1,166 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public final class Shape2DLayer extends Layer {
|
||||
|
||||
protected Color strokeColor = STD_STROKECOLOR;
|
||||
|
||||
protected Color fillColor = STD_FILLCOLOR;
|
||||
|
||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
private LinkedList<java.awt.Shape> shapes;
|
||||
|
||||
private boolean instantDraw = false;
|
||||
|
||||
public Shape2DLayer() {
|
||||
super();
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
}
|
||||
|
||||
public Shape2DLayer( boolean instantDraw ) {
|
||||
super();
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
this.instantDraw = instantDraw;
|
||||
}
|
||||
|
||||
public Shape2DLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
}
|
||||
|
||||
public Shape2DLayer( int width, int height, boolean instantDraw ) {
|
||||
super(width, height);
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
this.instantDraw = instantDraw;
|
||||
}
|
||||
|
||||
public Color getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
public void setFillColor( int gray ) {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( Color pColor ) {
|
||||
fillColor = pColor;
|
||||
drawing.setColor(pColor.getJavaColor());
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
fillColor = null;
|
||||
}
|
||||
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
setFillColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
setFillColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray ) {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color pColor ) {
|
||||
strokeColor = pColor;
|
||||
drawing.setColor(pColor.getJavaColor());
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
strokeColor = null;
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
setStrokeColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
setStrokeColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
setStrokeColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void setStrokeWeight( double pWeight ) {
|
||||
strokeWeight = pWeight;
|
||||
drawing.setStroke(createStroke());
|
||||
}
|
||||
|
||||
protected Stroke createStroke() {
|
||||
switch( strokeType ) {
|
||||
case DOTTED:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
||||
case DASHED:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{5.0f}, 0.0f);
|
||||
default:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND);
|
||||
}
|
||||
}
|
||||
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return strokeType;
|
||||
}
|
||||
|
||||
public void setStrokeType( Options.StrokeType strokeType ) {
|
||||
this.strokeType = strokeType;
|
||||
}
|
||||
|
||||
public java.util.List<java.awt.Shape> getShapes() {
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public void add( java.awt.Shape s ) {
|
||||
shapes.add(s);
|
||||
|
||||
if( instantDraw ) {
|
||||
drawing.setColor(fillColor.getJavaColor());
|
||||
drawing.fill(s);
|
||||
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.draw(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
if( !instantDraw ) {
|
||||
for( Shape shape : shapes ) {
|
||||
drawing.setColor(fillColor.getJavaColor());
|
||||
drawing.fill(shape);
|
||||
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.draw(shape);
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(pGraphics);
|
||||
}
|
||||
|
||||
}
|
||||
104
src/main/java/schule/ngb/zm/Spielemaschine.java
Normal file
104
src/main/java/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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
23
src/main/java/schule/ngb/zm/Updatable.java
Normal file
23
src/main/java/schule/ngb/zm/Updatable.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
/**
|
||||
* Aktualisierbare Objekte können in regelmäßigen Intervallen (meist einmal
|
||||
* pro Frame) ihren Zustand update. Diese Änderung kann abhängig vom
|
||||
* Zeitintervall (in Sekunden) zum letzten Aufruf passieren.
|
||||
*/
|
||||
public interface Updatable {
|
||||
|
||||
/**
|
||||
* Gibt an, ob das Objekt gerade auf Aktualisierungen reagiert.
|
||||
* @return {@code true}, wenn das Objekt aktiv ist.
|
||||
*/
|
||||
public boolean isActive();
|
||||
|
||||
/**
|
||||
* Änderung des Zustandes des Objekts abhängig vom Zeitintervall
|
||||
* <var>delta</var> in Sekunden.
|
||||
* @param delta Zeitintervall seit dem letzten Aufruf (in Sekunden).
|
||||
*/
|
||||
public void update( double delta );
|
||||
|
||||
}
|
||||
669
src/main/java/schule/ngb/zm/Vector.java
Normal file
669
src/main/java/schule/ngb/zm/Vector.java
Normal file
@@ -0,0 +1,669 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
/**
|
||||
* Ein zweidimensionaler Vektor.
|
||||
* <p>
|
||||
* Ein Vektor besteht aus zwei Komponenten {@code x} und {@code y}. Die
|
||||
* {@code Vector} Klasse unterstützt alle wesentlichen Vektoroperationen wie
|
||||
* {@link #scale(double) Skalierung}, {@link #add(Vector) Addition} oder
|
||||
* {@link #length() Längen-} und {@link #distance(Vector) Abstandsberechnung}.
|
||||
* Jede Operation, für die ein Parameter (ein anderer Vektor oder ein Skalar)
|
||||
* benötigt werden, gibt es in zwei Varianten: als Objektmethode, die das
|
||||
* Vektorobjekt selbst verändert und als Klassenmethode, die einen neuen Vektor
|
||||
* erzeugt. Die Objektmethoden unterstützen
|
||||
* <a href="https://en.wikipedia.org/wiki/Method_chaining">method chaining</a>
|
||||
* und geben jeweils das Vektorobjekt selbst zurück.
|
||||
* <p>
|
||||
* Außerdem werden eine Reihe von Hilfsmethoden implementiert, um
|
||||
* {@link #random() Zufallsvektoren} zu erzeugen, oder die
|
||||
* {@link #mouse() Mausposition} als Vektorobjekt zu holen.
|
||||
* <p>
|
||||
* Der Vektor der Zeichenmaschine erweitert die Klasse {@link Point2D} und lässt
|
||||
* sich dadurch einfach mit den Klassen des {@link java.awt} Pakets benutzen.
|
||||
*/
|
||||
public class Vector extends Point2D.Double {
|
||||
|
||||
/**
|
||||
* Der Nullvektor {@code Vector(0.0, 0.0)}.
|
||||
*/
|
||||
public static final Vector ZERO = new Vector(0.0, 0.0);
|
||||
|
||||
/**
|
||||
* Vektor der negativen Ordinatenachse {@code Vector(0.0, -1.0)} [oben]
|
||||
*/
|
||||
public static final Vector UP = new Vector(0, -1.0);
|
||||
|
||||
/**
|
||||
* Vektor der Ordinatenachse {@code Vector(0.0, 1.0)} [unten]
|
||||
*/
|
||||
public static final Vector DOWN = new Vector(0, 1.0);
|
||||
|
||||
/**
|
||||
* Vektor der Abszissenachse {@code Vector(1.0, 0.0)} [rechts]
|
||||
*/
|
||||
public static final Vector RIGHT = new Vector(1.0, 0);
|
||||
|
||||
/**
|
||||
* Vektor der negativen Abszissenachse {@code Vector(-1.0, 0.0)} [links]
|
||||
*/
|
||||
public static final Vector LEFT = new Vector(-1.0, 0.0);
|
||||
|
||||
/**
|
||||
* Vektor mit der aktuellen Mausposition {@code Vector(mouseX, mouseY)}
|
||||
*
|
||||
* @return Einen Vektor mit den Koordinaten des Mauszeigers.
|
||||
*/
|
||||
public static final Vector mouse() {
|
||||
return new Vector(Constants.mouseX, Constants.mouseY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Erzeugt den Nullvektor.
|
||||
*/
|
||||
public Vector() {
|
||||
x = 0.0;
|
||||
y = 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Vektor mit den angegebenen Komponenten.
|
||||
*
|
||||
* @param x Die x-Komponente.
|
||||
* @param y Die y-Komponente.
|
||||
*/
|
||||
public Vector( double x, double y ) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Vektor mit denselben Werten wie der angegebene Punkt.
|
||||
*
|
||||
* @param point Ein Punkt, dessen Koordinaten kopiert werden.
|
||||
*/
|
||||
public Vector( Point2D point ) {
|
||||
x = point.getX();
|
||||
y = point.getY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Vektor mit zufälligen Werten.
|
||||
* <p>
|
||||
* Die Werte liegen zwischen 0 und 100.
|
||||
*
|
||||
* @return Ein zufälliger Vektor.
|
||||
*/
|
||||
public static Vector random() {
|
||||
return new Vector(Constants.random(100.0), Constants.random(100.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Vektor mit zufälligen Werten innerhalb der angegebenen
|
||||
* Grenzen.
|
||||
*
|
||||
* @param min Untere Grenze der Zufallswerte.
|
||||
* @param max Obere Grenze der Zufallswerte.
|
||||
* @return Ein zufälliger Vektor.
|
||||
*/
|
||||
public static Vector random( double min, double max ) {
|
||||
return new Vector(Constants.random(min, max), Constants.random(min, max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Vektor mit zufälligen Werten innerhalb der angegebenen
|
||||
* Grenzen.
|
||||
*
|
||||
* @param minX Untere Grenze der x-Komponente.
|
||||
* @param maxX Obere Grenze der x-Komponente.
|
||||
* @param minY Untere Grenze der y-Komponente.
|
||||
* @param maxY Obere Grenze der y-Komponente.
|
||||
* @return Ein zufälliger Vektor.
|
||||
*/
|
||||
public static Vector random( double minX, double maxX, double minY, double maxY ) {
|
||||
return new Vector(Constants.random(minX, maxX), Constants.random(minY, maxY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen neuen Vektor mit derselben Richtun wie der angegebene
|
||||
* Vektor und der Länge 1.
|
||||
*
|
||||
* @param vector Der original Vektor.
|
||||
* @return Ein neuer Vektor mit der angegebene Länge.
|
||||
*/
|
||||
public static Vector normalize( Vector vector ) {
|
||||
Vector vec = vector.copy();
|
||||
vec.normalize();
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtrahiert den zweiten vom ersten Vektor.
|
||||
*
|
||||
* @param vector1 Erster Vektor.
|
||||
* @param vector2 Zweiter Vektor.
|
||||
* @return Differenzvektor der beiden Vektoren.
|
||||
*/
|
||||
public static Vector sub( Vector vector1, Vector vector2 ) {
|
||||
Vector vec = vector1.copy();
|
||||
vec.sub(vector2);
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen neuen Vektor der gleich dem angegebenen Vektor um {code
|
||||
* scalar} skaliert ist.
|
||||
*
|
||||
* @param vector Ein Vektor.
|
||||
* @param scalar Ein Skalar.
|
||||
* @return Ein skalierter Vektor.
|
||||
*/
|
||||
public static Vector scale( Vector vector, double scalar ) {
|
||||
Vector vec = vector.copy();
|
||||
vec.scale(scalar);
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen neuen Vektor der gleich dem angegebenen Vektor durch {code
|
||||
* scalar} dividiert ist.
|
||||
*
|
||||
* @param vector Ein Vektor.
|
||||
* @param scalar Ein Skalar.
|
||||
* @return Ein skalierter Vektor.
|
||||
*/
|
||||
public static Vector div( Vector vector, double scalar ) {
|
||||
Vector vec = vector.copy();
|
||||
vec.div(scalar);
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen neuen Vektor mit denselben Komponenten wie dieses
|
||||
* Vektorobjekt.
|
||||
*
|
||||
* @return Eine Kopie dieses Vektors.
|
||||
*/
|
||||
public Vector copy() {
|
||||
return new Vector(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Komponenten dieses Vektors neu.
|
||||
*
|
||||
* @param x Der neue x-Wert.
|
||||
* @param y Der neue y-Wert.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
public Vector set( double x, double y ) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Komponenten dieses Vectors auf die Werte des angegebenen
|
||||
* Vektors.
|
||||
*
|
||||
* @param vector Ein Vektor.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
public Vector set( Vector vector ) {
|
||||
x = vector.x;
|
||||
y = vector.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Komponenten dieses Vectors auf die Werte des angegebenen
|
||||
* Punktes.
|
||||
*
|
||||
* @param pPunkt Ein Punkt.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
public Vector set( Point2D pPunkt ) {
|
||||
x = pPunkt.getX();
|
||||
x = pPunkt.getY();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die x-Komponente des Vektors.
|
||||
*
|
||||
* @param x Der neue x-Wert.
|
||||
*/
|
||||
public void setX( double x ) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die y-Komponente des Vektors.
|
||||
*
|
||||
* @param y Der neue y-Wert.
|
||||
*/
|
||||
public void setY( double y ) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die Länge des Vektors.
|
||||
* <p>
|
||||
* Zur Berechnung der Länge muss eine Quadratwurzel gezogen werden. Dies ist
|
||||
* langsam und kann unter Umständen vermieden werden, wenn mit
|
||||
* {@link #lengthSq()} gearbeitet wird.
|
||||
*
|
||||
* @return Die Länge des Vektors.
|
||||
*/
|
||||
public double length() {
|
||||
return Math.sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die quadrierte Länge des Vektors.
|
||||
*
|
||||
* @return Das Quadrat der Länge.
|
||||
*/
|
||||
public double lengthSq() {
|
||||
return x * x + y * y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legt die Länge des Vektors fest.
|
||||
* @param length Die neue Länge des Vektors.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
public Vector setLength( double length ) {
|
||||
normalize();
|
||||
return scale(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen neuen Vektor mit derselben Richtung wie der angegebene
|
||||
* Vektor und der Länge {@code length}.
|
||||
*
|
||||
* @param vector Der original Vektor.
|
||||
* @param length Die neue Länge des Vektors.
|
||||
* @return Ein neuer Vektor mit der angegebene Länge.
|
||||
*/
|
||||
public static Vector setLength( Vector vector, double length ) {
|
||||
Vector vec = vector.copy();
|
||||
vec.setLength(length);
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
public Vector normalize() {
|
||||
double len = length();
|
||||
if( len != 0 && len != 1 ) {
|
||||
x /= len;
|
||||
y /= len;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addiert den Vektor {@code vector} zu diesem.
|
||||
* @param vector Ein anderer Vektor.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
public Vector add( Vector vector ) {
|
||||
x += vector.x;
|
||||
y += vector.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addiert die angegebenen Werte zur x- und y-Komponente des Vektors.
|
||||
* @param x Summand x-Komponente.
|
||||
* @param y Summand y-Komponente.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
public Vector add( double x, double y ) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addiert die beiden Vektoren zu einem neuen Vektor.
|
||||
*
|
||||
* @param vector1 Erster Vektor.
|
||||
* @param vector2 Zweiter Vektor.
|
||||
* @return Summenvektor der beiden Vektoren.
|
||||
*/
|
||||
public static Vector add( Vector vector1, Vector vector2 ) {
|
||||
Vector vec = vector1.copy();
|
||||
vec.add(vector2);
|
||||
return vec;
|
||||
}
|
||||
|
||||
public Vector sub( Vector vector ) {
|
||||
x -= vector.x;
|
||||
y -= vector.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector sub( double x, double y ) {
|
||||
this.x -= x;
|
||||
this.y -= y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector scale( double scalar ) {
|
||||
x *= scalar;
|
||||
y *= scalar;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector div( double scalar ) {
|
||||
if( scalar == 0.0 ) {
|
||||
throw new IllegalArgumentException("Can't divide by zero.");
|
||||
}
|
||||
x /= scalar;
|
||||
y /= scalar;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den Abstand zum angegebenen Punkt.
|
||||
* <p>
|
||||
* Zur Berechnung der Länge des Differenzvektors muss eine Quadratwurzel
|
||||
* gezogen werden. Um Geschwindigkeitsbuße zu vermeiden und auf den genauen
|
||||
* Abstand verzichtet werden kann, kann unter Umständen
|
||||
* {@link #distanceSq(Vector)} verwendet werden.
|
||||
*
|
||||
* @param vector Ein anderer Vektor.
|
||||
* @return Der Abstand zum Vektor.
|
||||
*/
|
||||
public double distance( Vector vector ) {
|
||||
return super.distance(vector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den Abstand zwischen den durch die angegebenen Vektoren
|
||||
* beschriebenen Punkten.
|
||||
* <p>
|
||||
* Zur Berechnung der Länge des Differenzvektors muss eine Quadratwurzel
|
||||
* gezogen werden. Um Geschwindigkeitsbuße zu vermeiden und auf den genauen
|
||||
* Abstand verzichtet werden kann, kann unter Umständen
|
||||
* {@link #distanceSq(Vector, Vector)} verwendet werden.
|
||||
*
|
||||
* @param vector1 Der erste Ortsvektor.
|
||||
* @param vector2 Der zweite Ortsvektor.
|
||||
* @return Der Abstand zwischen den Vektoren.
|
||||
*/
|
||||
public static double distance( Vector vector1, Vector vector2 ) {
|
||||
return vector1.distance(vector2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den quadrierten Abstand zum angegebenen Vektor.
|
||||
* <p>
|
||||
* Das Quadrat des Abstandes ist in der Regel schneller zu berechnen und ist
|
||||
* in vielen Fällen ausreichend. Beispielsweise lassen sich Vergleiche mit
|
||||
* dem quadrierten Abstand durchführen, wenn auch die gewünschte Entfernung
|
||||
* quadriert wird.
|
||||
*
|
||||
* @param vector
|
||||
* @return
|
||||
*/
|
||||
public double distanceSq( Vector vector ) {
|
||||
return super.distanceSq(vector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den Abstand zum Quadrat zwischen den durch die angegebenen
|
||||
* Vektoren beschriebenen Punkten.
|
||||
* <p>
|
||||
* Das Quadrat des Abstandes ist in der Regel schneller zu berechnen und ist
|
||||
* in vielen Fällen ausreichend. Beispielsweise lassen sich Vergleiche mit
|
||||
* dem quadrierten Abstand durchführen, wenn auch die gewünschte Entfernung
|
||||
* quadriert wird.
|
||||
*
|
||||
* @param vector1 Der erste Ortsvektor.
|
||||
* @param vector2 Der zweite Ortsvektor.
|
||||
* @return Der Abstand zwischen den Vektoren.
|
||||
*/
|
||||
public static double distanceSq( Vector vector1, Vector vector2 ) {
|
||||
return vector1.distance(vector2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt das Skalarprodukt des Vektors mit dem angegebenen Vektor.
|
||||
*
|
||||
* @param vector Ein zweiter Vektor.
|
||||
* @return Das Skalarprodukt der Vektoren.
|
||||
*/
|
||||
public double dot( Vector vector ) {
|
||||
return x * vector.x + y * vector.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt das Skalarprodukt der angegebenen Vektoren.
|
||||
*
|
||||
* @param vector1 Der erste Vektor.
|
||||
* @param vector2 Der zweite Vektor.
|
||||
* @return Das Skalarprodukt der Vektoren.
|
||||
*/
|
||||
public static double dot( Vector vector1, Vector vector2 ) {
|
||||
return vector1.dot(vector2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet das <a
|
||||
* href="https://de.wikipedia.org/wiki/Kreuzprodukt">Kreuzprodukt</a> des
|
||||
* Vektors mit dem angegebenen Vektor.
|
||||
*
|
||||
* @param vector Ein Vektor.
|
||||
* @return Das Kreuzprodukt der Vektoren.
|
||||
*/
|
||||
public double cross( Vector vector ) {
|
||||
// See: http://allenchou.net/2013/07/cross-product-of-2d-vectors/
|
||||
return x * vector.y - vector.x * y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet das <a
|
||||
* href="https://de.wikipedia.org/wiki/Kreuzprodukt">Kreuzprodukt</a> der
|
||||
* angegebenen Vektoren.
|
||||
*
|
||||
* @param vector1 Der erste Vektor.
|
||||
* @param vector2 Der zweite Vektor.
|
||||
* @return Das Kreuzprodukt der Vektoren.
|
||||
*/
|
||||
public static double cross( Vector vector1, Vector vector2 ) {
|
||||
return vector1.cross(vector2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Beschränkt die Länge dieses Vektors auf den Bereich 0 bis {@code max}.
|
||||
*
|
||||
* @param max Maximale Länge des Vektors.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #setLength(double)
|
||||
*/
|
||||
public Vector limit( double max ) {
|
||||
if( lengthSq() > max * max ) {
|
||||
setLength(max);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beschränkt die Länge dieses Vektors auf den Bereich {@code min} bis
|
||||
* {@code max}.
|
||||
*
|
||||
* @param min Minimale Länge des Vektors.
|
||||
* @param max Maximale Länge des Vektors.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #setLength(double)
|
||||
*/
|
||||
public Vector limit( double min, double max ) {
|
||||
if( min > max ) {
|
||||
double t = min;
|
||||
min = max;
|
||||
max = t;
|
||||
}
|
||||
|
||||
if( lengthSq() < min * min ) {
|
||||
setLength(min);
|
||||
} else if( lengthSq() > max * max ) {
|
||||
setLength(max);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt den Winkel, den der Vektor mit der Ordinatenachse einschließt in
|
||||
* Grad.
|
||||
*
|
||||
* @return Winkel des Vektors in Grad.
|
||||
*/
|
||||
public double angle() {
|
||||
return Math.toDegrees(Math.atan2(y, x));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt den Winkel, den der Vektor mit der Ordinatenachse einschließt in
|
||||
* Bogenmaß.
|
||||
*
|
||||
* @return Winkel des Vektors in Bogenmaß.
|
||||
*/
|
||||
public double heading() {
|
||||
return Math.atan2(y, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dreht den Vektor um {@code degree} Grad im Uhrzeigersinn.
|
||||
*
|
||||
* @param degree Gradzahl für die Rotation.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #rotateRad(double)
|
||||
*/
|
||||
public Vector rotate( double degree ) {
|
||||
return rotateRad(Math.toRadians(degree));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dreht den Vektor um {@code rad} im Uhrzeigersinn.
|
||||
*
|
||||
* @param rad Winkel der Rotation in Bogenmaß.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #rotate(double)
|
||||
*/
|
||||
public Vector rotateRad( double rad ) {
|
||||
double temp = x;
|
||||
x = x * Math.cos(rad) - y * Math.sin(rad);
|
||||
y = temp * Math.sin(rad) + y * Math.cos(rad);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dreht den Vektor um {@code degree} Grad im Uhrzeigersinn.
|
||||
*
|
||||
* @param vector Ein Vektor.
|
||||
* @param degree Gradzahl für die Rotation.
|
||||
* @return Ein gedrehter Vektor.
|
||||
*/
|
||||
public static Vector rotate( Vector vector, double degree ) {
|
||||
Vector vec = vector.copy();
|
||||
vec.rotate(degree);
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verschiebt diesen Vektor um den Faktor {@code t} in Richtung des Vektors
|
||||
* {@code vector}. Im Vergleich zu {@link #interpolate(Vector, double)} ist
|
||||
* {@code t} auf den Bereich nur zwischen 0 und 1 beschränkt. Für {@code t}
|
||||
* gleich 0 passiert nichts, für {@code t} wird dieser Vektor zu
|
||||
* {@code vector}.
|
||||
*
|
||||
* @param vector Der erste Ortsvektor.
|
||||
* @param t Ein Wert zwischen 0 und 1.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
public Vector morph( Vector vector, double t ) {
|
||||
double tt = Math.min(Math.max(t, 0.0), 1.0);
|
||||
x = x + (vector.x - x) * tt;
|
||||
y = y + (vector.y - y) * tt;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Vektor, der auf der Strecke zwischen den angegebenen
|
||||
* Vektoren liegt. Der Ort des Vektors auf der Strecke wird relativ von
|
||||
* {@code vector1} abhängig von {@code t} bestimmt. Im Vergleich zu
|
||||
* {@link #interpolate(Vector, Vector, double)} ist {@code t} auf den
|
||||
* Bereich nur zwischen 0 und 1 beschränkt. Für {@code t} gleich 0 ist der
|
||||
* erzeugte Vektor gleich {@code vector1}, für {@code t} gleich 1 ist das
|
||||
* Ergebnis gleich {@code vector2}. Für {@code t} gleich 0.5 liegt der
|
||||
* Vektor genau mittig zwischen den beiden Ortsvektoren.
|
||||
*
|
||||
* @param vector1 Der erste Ortsvektor.
|
||||
* @param vector2 Der zweite Ortsvektor.
|
||||
* @param t Ein Wert zwischen 0 und 1.
|
||||
* @return Ein Vektor zwischen den beiden Ortsvektoren.
|
||||
*/
|
||||
public static Vector morph( Vector vector1, Vector vector2, double t ) {
|
||||
Vector vec = vector1.copy();
|
||||
vec.morph(vector2, t);
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verschiebt diesen Vektor um den Faktor {@code t} in Richtung des Vektors
|
||||
* {@code vector}. Für {@code t} gleich 0 ist der erzeugte Vektor gleich
|
||||
* {@code vector1}, für {@code t} gleich 1 ist das Ergebnis gleich
|
||||
* {@code vector2}. Für {@code t} gleich 0.5 liegt der Vektor genau mittig
|
||||
* zwischen den beiden Ortsvektoren.
|
||||
* <p>
|
||||
* Negative Werte für {@code t} verschieben den Vektor von {@code vector}
|
||||
* weg und Werte größer 1 schieben ihn über den anderen Vektor hinaus. Soll
|
||||
* dies verhindert werden kann {@link #morph(Vector, double)} verwendet
|
||||
* werden.
|
||||
*
|
||||
* @param vector Der erste Ortsvektor.
|
||||
* @param t Ein Wert zwischen 0 und 1.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
public Vector interpolate( Vector vector, double t ) {
|
||||
x = x + (vector.x - x) * t;
|
||||
y = y + (vector.y - y) * t;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Vektor, der auf der Strecke zwischen den angegebenen
|
||||
* Vektoren liegt. Der Ort des Vektors auf der Strecke wird relativ von
|
||||
* {@code vector1} abhängig von {@code t} bestimmt. Für {@code t} gleich 0
|
||||
* ist der erzeugte Vektor gleich {@code vector1}, für {@code t} gleich 1
|
||||
* ist das Ergebnis gleich {@code vector2}. Für {@code t} gleich 0.5 liegt
|
||||
* der Vektor genau mittig zwischen den beiden Ortsvektoren.
|
||||
* <p>
|
||||
* Negative Werte für {@code t} verschieben den Vektor von {@code vector}
|
||||
* weg und Werte größer 1 schieben ihn über den anderen Vektor hinaus. Soll
|
||||
* dies verhindert werden kann {@link #morph(Vector, Vector, double)}
|
||||
* verwendet werden.
|
||||
*
|
||||
* @param vector1 Der erste Ortsvektor.
|
||||
* @param vector2 Der zweite Ortsvektor.
|
||||
* @param t Ein Wert zwischen 0 und 1.
|
||||
* @return Ein Vektor zwischen den beiden Ortsvektoren.
|
||||
*/
|
||||
public static Vector interpolate( Vector vector1, Vector vector2, double t ) {
|
||||
Vector vec = vector1.copy();
|
||||
vec.interpolate(vector2, t);
|
||||
return vec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "schule.ngb.zm.Vector[x = " + x + ", y = " + y + "]";
|
||||
}
|
||||
|
||||
}
|
||||
6
src/main/java/schule/ngb/zm/Zeichenfenster.java
Normal file
6
src/main/java/schule/ngb/zm/Zeichenfenster.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
// TODO: Auslagern des Frames in diese Klasse (Trennung Swing-GUI/Canvas, Zeichenmaschine und Drawing-Thread)
|
||||
public class Zeichenfenster {
|
||||
|
||||
}
|
||||
268
src/main/java/schule/ngb/zm/Zeichenleinwand.java
Normal file
268
src/main/java/schule/ngb/zm/Zeichenleinwand.java
Normal file
@@ -0,0 +1,268 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.shapes.ShapesLayer;
|
||||
|
||||
import java.awt.Canvas;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.image.BufferStrategy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
|
||||
* mehreren Ebenen, auf denen auf verschiedene Arten gezeichnet werden kann. Die
|
||||
* Ebenen lassen sich beliebig übereinander legen, ausblenden oder wieder
|
||||
* löschen.
|
||||
* <p>
|
||||
* Jede Ebene besitzt eine Zeichenfläche, auf der ihre Zeichnung liegt. Diese
|
||||
* Zeichenflächen werden pro Frame einmal von "unten" nach "oben" auf diese
|
||||
* Leinwand gezeichnet.
|
||||
*/
|
||||
public class Zeichenleinwand extends Canvas {
|
||||
|
||||
/**
|
||||
* Liste der hinzugefügten Ebenen.
|
||||
*/
|
||||
private LinkedList<Layer> layers;
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Zeichenleinwand mit einer festen Größe.
|
||||
*
|
||||
* @param width Breite der Zeichenleinwand.
|
||||
* @param height Höhe der Zeichenleinwand.
|
||||
*/
|
||||
public Zeichenleinwand( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
this.setPreferredSize(this.getSize());
|
||||
this.setMinimumSize(this.getSize());
|
||||
this.setBackground(Constants.STD_BACKGROUND.getJavaColor());
|
||||
|
||||
// Liste der Ebenen initialisieren und die Standardebenen einfügen
|
||||
layers = new LinkedList<>();
|
||||
synchronized( layers ) {
|
||||
layers.add(new ColorLayer(width, height, Constants.STD_BACKGROUND));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ändert die Größe der Zeichenleinwand auf die angegebene Größe in Pixeln.
|
||||
* <p>
|
||||
* 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 height Neue Höhe der Leinwand in Pixeln.
|
||||
*/
|
||||
@Override
|
||||
public void setSize( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
this.setPreferredSize(this.getSize());
|
||||
this.setMinimumSize(this.getSize());
|
||||
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
layer.setSize(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt der Zeichenleinwand eine Ebene hinzu, die oberhalb aller bisherigen
|
||||
* Ebenen eingefügt wird.
|
||||
*
|
||||
* @param layer Die neue Ebene.
|
||||
*/
|
||||
public void addLayer( Layer layer ) {
|
||||
if( layer != null ) {
|
||||
synchronized( layers ) {
|
||||
layer.setSize(getWidth(), getHeight());
|
||||
layers.add(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 0.
|
||||
* @param layer Die neue Ebene.
|
||||
*/
|
||||
public void addLayer( int i, Layer layer ) {
|
||||
if( layer != null ) {
|
||||
synchronized( layers ) {
|
||||
layer.setSize(getWidth(), getHeight());
|
||||
if( i > layers.size() ) {
|
||||
layers.add(layer);
|
||||
} else {
|
||||
layers.add(i, layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Anzahl der {@link Layer Ebenen} in dieser Leinwand zurück.
|
||||
*
|
||||
* @return Die Anzahl der Ebenen.
|
||||
*/
|
||||
public int getLayerCount() {
|
||||
return layers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Liste der bisher hinzugefügten Ebenen zurück.
|
||||
*
|
||||
* @return Liste der Ebenen.
|
||||
*/
|
||||
public java.util.List<Layer> getLayers() {
|
||||
return layers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt die Ebene am Index <var>i</var> (beginnend bei 0).
|
||||
*
|
||||
* @param i Index der Ebene (beginnend bei 0).
|
||||
* @return Die Ebene am Index <var>i</var> oder {@code null}.
|
||||
* @throws IndexOutOfBoundsException Falls der Index nicht existiert.
|
||||
*/
|
||||
public Layer getLayer( int i ) {
|
||||
if( layers.size() > i ) {
|
||||
return layers.get(i);
|
||||
} else {
|
||||
throw new IndexOutOfBoundsException("No layer at index " + i + " (max: " + (layers.size() - 1) + ").");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht die erste Ebene des angegebenen Typs aus der Liste der Ebenen.
|
||||
* Existiert keine solche Ebene, wird {@code null} zurückgegeben.
|
||||
*
|
||||
* @param clazz Typ der Ebene.
|
||||
* @param <L>
|
||||
* @return Erste Ebene vom angegeben Typ.
|
||||
*/
|
||||
public <L extends Layer> L getLayer( Class<L> clazz ) {
|
||||
for( Layer layer : layers ) {
|
||||
if( layer.getClass().equals(clazz) ) {
|
||||
return clazz.cast(layer);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht alle Ebenen von einem bestimmten Typ aus der Liste der Ebenen und
|
||||
* gibt diese als Liste zurück. Die Reihenfolge in der Liste entspricht der
|
||||
* Reihenfolge der Ebenen in der Leinwand (von unten nach oben).
|
||||
*
|
||||
* @param pClazz
|
||||
* @param <L>
|
||||
* @return
|
||||
*/
|
||||
public <L extends Layer> java.util.List<L> getLayers( Class<L> pClazz ) {
|
||||
ArrayList<L> result = new ArrayList<>(layers.size());
|
||||
for( Layer layer : layers ) {
|
||||
if( layer.getClass().equals(pClazz) ) {
|
||||
result.add(pClazz.cast(layer));
|
||||
}
|
||||
}
|
||||
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.
|
||||
*/
|
||||
public void allocateBuffer() {
|
||||
this.createBufferStrategy(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht alle Ebenen der Zeichenebene und gibt deren Ressourcen frei.
|
||||
*/
|
||||
public void dispose() {
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
layer.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint( Graphics g ) {
|
||||
render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet den Inhalt aller {@link Layer Ebenen} in den Grafik-Kontext.
|
||||
*
|
||||
* @param graphics
|
||||
*/
|
||||
public void draw( Graphics graphics ) {
|
||||
Graphics2D g2d = (Graphics2D) graphics.create();
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
layer.draw(g2d);
|
||||
}
|
||||
}
|
||||
g2d.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt den aktuellen Inhalt der Zeichenleinwand an.
|
||||
*/
|
||||
public void render() {
|
||||
if( getBufferStrategy() == null ) {
|
||||
allocateBuffer();
|
||||
}
|
||||
|
||||
if( isDisplayable() ) {
|
||||
BufferStrategy strategy = this.getBufferStrategy();
|
||||
if( strategy != null ) {
|
||||
do {
|
||||
do {
|
||||
Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
|
||||
g2d.clearRect(0, 0, getWidth(), getHeight());
|
||||
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
layer.draw(g2d);
|
||||
}
|
||||
}
|
||||
|
||||
g2d.dispose();
|
||||
} while( strategy.contentsRestored() );
|
||||
|
||||
// Display the buffer
|
||||
if( !strategy.contentsLost() ) {
|
||||
strategy.show();
|
||||
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
}
|
||||
|
||||
// Repeat the rendering if the drawing buffer was lost
|
||||
} while( strategy.contentsLost() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1481
src/main/java/schule/ngb/zm/Zeichenmaschine.java
Normal file
1481
src/main/java/schule/ngb/zm/Zeichenmaschine.java
Normal file
File diff suppressed because it is too large
Load Diff
371
src/main/java/schule/ngb/zm/charts/BarChart.java
Normal file
371
src/main/java/schule/ngb/zm/charts/BarChart.java
Normal file
@@ -0,0 +1,371 @@
|
||||
package schule.ngb.zm.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class BarChart extends Rectangle {
|
||||
|
||||
public static String DEFAULT_LABEL = "%.2f";
|
||||
|
||||
protected ArrayList<ChartValue> values;
|
||||
|
||||
protected HashMap<ChartValue, Color> colorMap;
|
||||
|
||||
protected double max = -1;
|
||||
|
||||
protected boolean vertical = false, inverted = false, labels = true, labelsInline = false;
|
||||
|
||||
|
||||
public BarChart( double x, double y, double width, double height ) {
|
||||
this(x, y, width, height, -1);
|
||||
}
|
||||
|
||||
public BarChart( double x, double y, double width, double height, double max ) {
|
||||
this(x, y, width, height, max, null);
|
||||
}
|
||||
|
||||
public BarChart( double x, double y, double width, double height, double max, double[] pValues ) {
|
||||
super(x, y, width, height);
|
||||
this.max = max;
|
||||
|
||||
if( pValues != null ) {
|
||||
this.values = new ArrayList<>(pValues.length);
|
||||
for( double value : pValues ) {
|
||||
this.addValue(value);
|
||||
}
|
||||
} else {
|
||||
this.values = new ArrayList<>(10);
|
||||
}
|
||||
|
||||
colorMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public boolean isVertical() {
|
||||
return vertical;
|
||||
}
|
||||
|
||||
public void setVertical( boolean pPillars ) {
|
||||
this.vertical = pPillars;
|
||||
}
|
||||
|
||||
public boolean isInverted() {
|
||||
return inverted;
|
||||
}
|
||||
|
||||
public void setInverted( boolean pInverted ) {
|
||||
this.inverted = pInverted;
|
||||
}
|
||||
|
||||
public boolean isLabelsVisible() {
|
||||
return labels;
|
||||
}
|
||||
|
||||
public void setLabelsVisible( boolean pLabels ) {
|
||||
this.labels = pLabels;
|
||||
}
|
||||
|
||||
public boolean isLabelsInline() {
|
||||
return labelsInline;
|
||||
}
|
||||
|
||||
public void setLabelsInline( boolean pLabelsInline ) {
|
||||
this.labelsInline = pLabelsInline;
|
||||
}
|
||||
|
||||
public double getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax( double pMax ) {
|
||||
this.max = pMax;
|
||||
}
|
||||
|
||||
public ChartValue[] getChartValues() {
|
||||
ChartValue[] ret = new ChartValue[values.size()];
|
||||
return values.toArray(ret);
|
||||
}
|
||||
|
||||
public double[] getValues() {
|
||||
double[] ret = new double[values.size()];
|
||||
int i = 0;
|
||||
for( ChartValue cv : values ) {
|
||||
ret[i++] = cv.getValue();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void addValue( double pValue ) {
|
||||
addValue(pValue, randomNiceColor());
|
||||
}
|
||||
|
||||
public void addValue( double pValue, Color pColor ) {
|
||||
addValue(pValue, pColor, String.format(DEFAULT_LABEL, pValue));
|
||||
}
|
||||
|
||||
public void addValue( double pValue, Color pColor, String pLabel ) {
|
||||
addValue(new BasicChartValue(pValue, pLabel, pColor));
|
||||
}
|
||||
|
||||
public void addValue( ChartValue pValue ) {
|
||||
values.add(pValue);
|
||||
}
|
||||
|
||||
public void addValues( double[] pValues ) {
|
||||
for( double value : pValues ) {
|
||||
addValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void addValues( ChartValue[] pValues ) {
|
||||
for( ChartValue value : pValues ) {
|
||||
addValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void addValues( Collection<ChartValue> pValues ) {
|
||||
for( ChartValue value : pValues ) {
|
||||
addValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeValue( int i ) {
|
||||
if( i < values.size() ) {
|
||||
values.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeValue( ChartValue pValue ) {
|
||||
values.remove(pValue);
|
||||
}
|
||||
|
||||
public void removeValues( ChartValue[] pValues ) {
|
||||
for( ChartValue value : pValues ) {
|
||||
removeValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeValues( Collection<ChartValue> pValues ) {
|
||||
for( ChartValue value : pValues ) {
|
||||
removeValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public double getValue( int i ) {
|
||||
return values.get(i).getValue();
|
||||
}
|
||||
|
||||
public void setValue( int i, double pValue ) {
|
||||
values.get(i).setValue(pValue);
|
||||
}
|
||||
|
||||
public boolean containsValue( ChartValue pValue ) {
|
||||
return values.contains(pValue);
|
||||
}
|
||||
|
||||
public String[] getLabels() {
|
||||
String[] ret = new String[values.size()];
|
||||
int i = 0;
|
||||
for( ChartValue cv : values ) {
|
||||
ret[i++] = cv.getLabel();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public String getLabel( int i ) {
|
||||
return values.get(i).getLabel();
|
||||
}
|
||||
|
||||
public void setLabel( int i, String pLabel ) {
|
||||
values.get(i).setLabel(pLabel);
|
||||
}
|
||||
|
||||
public void setLabels( String... pLabels ) {
|
||||
for( int i = 0; i < values.size(); i++ ) {
|
||||
if( i < pLabels.length ) {
|
||||
values.get(i).setLabel(pLabels[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Color[] getColors() {
|
||||
Color[] ret = new Color[values.size()];
|
||||
int i = 0;
|
||||
for( ChartValue bcv : values ) {
|
||||
ret[i] = bcv.getColor();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void setColors( Color... pColors ) {
|
||||
for( int i = 0; i < values.size(); i++ ) {
|
||||
if( i < pColors.length ) {
|
||||
values.get(i).setColor(pColors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Color getColor( int i ) {
|
||||
return values.get(i).getColor();
|
||||
}
|
||||
|
||||
public void setColor( int i, Color pColor ) {
|
||||
values.get(i).setColor(pColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
AffineTransform originalTransform = graphics.getTransform();
|
||||
graphics.setTransform(transform);
|
||||
if( vertical ) {
|
||||
drawPillars(graphics);
|
||||
} else {
|
||||
drawBars(graphics);
|
||||
}
|
||||
graphics.setTransform(originalTransform);
|
||||
}
|
||||
|
||||
private void drawBars( Graphics2D graphics ) {
|
||||
double max = this.max;
|
||||
if( max == -1 ) {
|
||||
for( ChartValue cv : values ) {
|
||||
if( cv.getValue() > max ) {
|
||||
max = cv.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double gap = height * .01;
|
||||
double barY = gap * .5;
|
||||
double barHeight = (height - gap * values.size()) / values.size();
|
||||
|
||||
for( int i = 0; i < values.size(); i++ ) {
|
||||
ChartValue cv = values.get(i);
|
||||
double barWidth = (int) round(cv.getValue() * width / max);
|
||||
|
||||
if( cv.getColor() != null ) {
|
||||
graphics.setColor(cv.getColor().getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(getFillColor().getJavaColor());
|
||||
}
|
||||
if( inverted ) {
|
||||
graphics.fillRect((int) (width - barWidth), (int) barY, (int) barWidth, (int) barHeight);
|
||||
} else {
|
||||
graphics.fillRect(0, (int) barY, (int) barWidth, (int) barHeight);
|
||||
}
|
||||
|
||||
if( labels ) {
|
||||
int lWidth = graphics.getFontMetrics().stringWidth(cv.getLabel());
|
||||
int lHeight = graphics.getFontMetrics().getAscent();
|
||||
|
||||
int lX = -10 - lWidth;
|
||||
if( inverted ) {
|
||||
lX = (int) width + 10;
|
||||
}
|
||||
if( labelsInline ) {
|
||||
//lX = (int) (width - barWidth + 10);
|
||||
lX = 10;
|
||||
if( inverted ) {
|
||||
lX = (int) (width - lWidth - 10);
|
||||
}
|
||||
|
||||
if( cv.getColor() != null ) {
|
||||
graphics.setColor(cv.getColor().textcolor().getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(getFillColor().textcolor().getJavaColor());
|
||||
}
|
||||
}
|
||||
int lY = (int) (barY + barHeight * .5 + lHeight * .5);
|
||||
|
||||
graphics.drawString(cv.getLabel(), lX, lY);
|
||||
}
|
||||
|
||||
barY += barHeight + gap;
|
||||
}
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
if( inverted ) {
|
||||
graphics.drawLine((int) width, 0, (int) width, (int) height);
|
||||
} else {
|
||||
graphics.drawLine((int) 0, (int) 0, (int) 0, (int) height);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawPillars( Graphics2D graphics ) {
|
||||
double max = this.max;
|
||||
if( max == -1 ) {
|
||||
for( ChartValue bcv : values ) {
|
||||
if( bcv.getValue() > max ) {
|
||||
max = bcv.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double gap = width * .01;
|
||||
double barX = gap * .5;
|
||||
double barWidth = (width - gap * values.size()) / values.size();
|
||||
|
||||
for( int i = 0; i < values.size(); i++ ) {
|
||||
ChartValue cv = values.get(i);
|
||||
double barHeight = (int) round(cv.getValue() * height / max);
|
||||
|
||||
if( cv.getColor() != null ) {
|
||||
graphics.setColor(cv.getColor().getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(getFillColor().getJavaColor());
|
||||
}
|
||||
if( inverted ) {
|
||||
graphics.fillRect((int) barX, 0, (int) barWidth, (int) barHeight);
|
||||
} else {
|
||||
graphics.fillRect((int) barX, (int) (height - barHeight), (int) barWidth, (int) barHeight);
|
||||
}
|
||||
|
||||
if( labels ) {
|
||||
int lWidth = graphics.getFontMetrics().stringWidth(cv.getLabel());
|
||||
int lHeight = graphics.getFontMetrics().getAscent();
|
||||
|
||||
int lY = (int) (width + 10 + lWidth);
|
||||
if( inverted ) {
|
||||
lY = -10;
|
||||
}
|
||||
if( labelsInline ) {
|
||||
lY = (int) (width - 10);
|
||||
if( inverted ) {
|
||||
lY = 10 + lWidth;
|
||||
}
|
||||
|
||||
if( cv.getColor() != null ) {
|
||||
graphics.setColor(cv.getColor().textcolor().getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(getFillColor().textcolor().getJavaColor());
|
||||
}
|
||||
}
|
||||
int lX = (int) (barX + barWidth * .5 + lHeight * .5);
|
||||
|
||||
AffineTransform tt = graphics.getTransform();
|
||||
graphics.translate(lX, lY);
|
||||
graphics.rotate(-HALF_PI);
|
||||
|
||||
graphics.drawString(cv.getLabel(), 0, 0);
|
||||
|
||||
graphics.setTransform(tt);
|
||||
}
|
||||
|
||||
barX += barWidth + gap;
|
||||
}
|
||||
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
if( inverted ) {
|
||||
graphics.drawLine(0, 0, (int) width, 0);
|
||||
} else {
|
||||
graphics.drawLine(0, (int) height, (int) width, (int) height);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
195
src/main/java/schule/ngb/zm/charts/BasicChartValue.java
Normal file
195
src/main/java/schule/ngb/zm/charts/BasicChartValue.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package schule.ngb.zm.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
/**
|
||||
* Basisimplemenation eines {@link ChartValue}.
|
||||
* <p>
|
||||
* Die Klasse implementiert das Interface auf die einfachste mögliche Weise als
|
||||
* reine Datenhaltungsklasse. Sie wird von den Diagrammen verwendet, um reine
|
||||
* Double Werte als {@code ChartValue} zu speichern.
|
||||
* <p>
|
||||
* Um einfach einen Datenpunkt zu erstellen, wird eine umfassende Auswahl an
|
||||
* Konstruktoren angeboten.
|
||||
*/
|
||||
public class BasicChartValue implements ChartValue {
|
||||
|
||||
/**
|
||||
* Der x-Wert diese Datenpunktes.
|
||||
*/
|
||||
private double xValue = 0;
|
||||
|
||||
/**
|
||||
* Der Datenwert des Datenpunktes.
|
||||
*/
|
||||
private double value;
|
||||
|
||||
/**
|
||||
* Die Beschriftung des Datenpunktes.
|
||||
*/
|
||||
private String label = null;
|
||||
|
||||
/**
|
||||
* Die Farbe des Datenpunktes.
|
||||
*/
|
||||
private Color color = null;
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Datenpunkt mit dem angegebenen Datenwert.
|
||||
*
|
||||
* @param value Der Datenwert.
|
||||
*/
|
||||
public BasicChartValue( double value ) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Datenpunkt mit dem angegebenen x- und Datenwert.
|
||||
*
|
||||
* @param xValue Der x-Wert.
|
||||
* @param value Der Datenwert.
|
||||
*/
|
||||
public BasicChartValue( double xValue, double value ) {
|
||||
this.xValue = xValue;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Datenpunkt mit dem angegebenen Datenwert und der
|
||||
* angegebenen Beschriftung.
|
||||
*
|
||||
* @param value Der Datenwert.
|
||||
* @param label Die Beschriftung.
|
||||
*/
|
||||
public BasicChartValue( double value, String label ) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Datenpunkt mit dem angegebenen x- und Datenwert und
|
||||
* der angegebenen Beschriftung.
|
||||
*
|
||||
* @param xValue Der x-Wert.
|
||||
* @param value Der Datenwert.
|
||||
* @param label Die Beschriftung.
|
||||
*/
|
||||
public BasicChartValue( double xValue, double value, String label ) {
|
||||
this.xValue = xValue;
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Datenpunkt mit dem angegebenen Datenwert und der
|
||||
* angegebenen Farbe.
|
||||
*
|
||||
* @param value Der Datenwert.
|
||||
* @param color Die Farbe.
|
||||
*/
|
||||
public BasicChartValue( double value, Color color ) {
|
||||
this.value = value;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Datenpunkt mit dem angegebenen x- und Datenwert und
|
||||
* der angegebenen Farbe.
|
||||
*
|
||||
* @param xValue Der x-Wert.
|
||||
* @param value Der Datenwert.
|
||||
* @param color Die Farbe.
|
||||
*/
|
||||
public BasicChartValue( double xValue, double value, Color color ) {
|
||||
this.xValue = xValue;
|
||||
this.value = value;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Datenpunkt mit dem angegebenen Datenwert, der
|
||||
* angegebenen Beschriftung und Farbe.
|
||||
*
|
||||
* @param value Der Datenwert.
|
||||
* @param label Die Beschriftung.
|
||||
* @param color Die Farbe.
|
||||
*/
|
||||
public BasicChartValue( double value, String label, Color color ) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Datenpunkt mit dem angegebenen x- und Datenwert, der
|
||||
* angegebenen Beschriftung und Farbe.
|
||||
*
|
||||
* @param xValue Der x-Wert.
|
||||
* @param value Der Datenwert.
|
||||
* @param label Die Beschriftung.
|
||||
* @param color Die Farbe.
|
||||
*/
|
||||
public BasicChartValue( double xValue, double value, String label, Color color ) {
|
||||
this.xValue = xValue;
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getX() {
|
||||
return xValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setValue( double pValue ) {
|
||||
this.value = pValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setLabel( String pLabel ) {
|
||||
this.label = pLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setColor( Color pColor ) {
|
||||
this.color = pColor;
|
||||
}
|
||||
|
||||
}
|
||||
82
src/main/java/schule/ngb/zm/charts/ChartAxes.java
Normal file
82
src/main/java/schule/ngb/zm/charts/ChartAxes.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package schule.ngb.zm.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
public class ChartAxes extends Rectangle {
|
||||
|
||||
private double xMin = 0.0, xMax = 10.0, xStep = 1.0;
|
||||
|
||||
private double yMin = 0.0, yMax = 10.0, yStep = 1.0;
|
||||
|
||||
private int xTicks = 1, yTicks = 1;
|
||||
|
||||
private Color gridColor = Color.LIGHTGRAY;
|
||||
|
||||
private boolean showArrows = true, showGrid = false;
|
||||
|
||||
public ChartAxes( double x, double y, double width, double height ) {
|
||||
super(x, y, width, height);
|
||||
setAnchor(Options.Direction.CENTER);
|
||||
}
|
||||
|
||||
public void setAxesColor( Color pColor ) {
|
||||
setStrokeColor(pColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
|
||||
double xUnit = width / (xMax - xMin);
|
||||
double yUnit = height / (yMax - yMin);
|
||||
|
||||
AffineTransform originalTransform = graphics.getTransform();
|
||||
graphics.setTransform(transform);
|
||||
|
||||
if( showGrid ) {
|
||||
graphics.setColor(gridColor.getJavaColor());
|
||||
if( xTicks > 0 ) {
|
||||
for( int i = 0; i < (xMax - xMin); i += xTicks ) {
|
||||
graphics.drawLine((int) (i * xUnit), (int) (height), (int) (i * xUnit), (int) (height - (yMax - yMin) * yUnit));
|
||||
}
|
||||
}
|
||||
if( yTicks > 0 ) {
|
||||
for( int i = 0; i < (yMax - yMin); i += yTicks ) {
|
||||
graphics.drawLine(0, (int) (height - i * yUnit), (int) ((xMax - xMin) * xUnit), (int) (height - i * yUnit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.drawLine(0, (int)(height), (int)((xMax-xMin) * xUnit), (int)(height));
|
||||
graphics.drawLine(0, (int)(height), 0, (int)(height - (yMax-yMin) * yUnit));
|
||||
if( showArrows ) {
|
||||
int tipSize = 3;
|
||||
graphics.drawLine((int)((xMax-xMin) * xUnit), (int)(height), (int)((xMax-xMin) * xUnit-tipSize), (int)(height-tipSize));
|
||||
graphics.drawLine((int)((xMax-xMin) * xUnit), (int)(height), (int)((xMax-xMin) * xUnit-tipSize), (int)(height+tipSize));
|
||||
graphics.drawLine(0, (int)(height - (yMax-yMin) * yUnit), -tipSize, (int)(height - (yMax-yMin) * yUnit +tipSize));
|
||||
graphics.drawLine(0, (int)(height - (yMax-yMin) * yUnit), tipSize, (int)(height - (yMax-yMin) * yUnit +tipSize));
|
||||
}
|
||||
|
||||
int tickSize = 3;
|
||||
if( xTicks > 0 ) {
|
||||
for( int i = 0; i < (xMax - xMin); i += xTicks ) {
|
||||
graphics.drawLine((int) (i * xUnit), (int) (height+tickSize), (int) (i * xUnit), (int) (height-tickSize));
|
||||
}
|
||||
}
|
||||
if( yTicks > 0 ) {
|
||||
for( int i = 0; i < (yMax - yMin); i += yTicks ) {
|
||||
graphics.drawLine(-tickSize, (int) (height - i * yUnit), tickSize, (int) (height - i * yUnit));
|
||||
}
|
||||
}
|
||||
|
||||
graphics.setTransform(originalTransform);
|
||||
}
|
||||
|
||||
}
|
||||
76
src/main/java/schule/ngb/zm/charts/ChartValue.java
Normal file
76
src/main/java/schule/ngb/zm/charts/ChartValue.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package schule.ngb.zm.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
/**
|
||||
* Schnittstelle für Datenpunkte, die in einem Diagramm dargestellt werden
|
||||
* sollen. Durch Implementation dieser Schnittstelle kann eine Klasse in den
|
||||
* meisten Diagrammarten dargestllt werden. Einige Diagrammarten benötigen aber
|
||||
* spezialisiertere Schnittstellen, die von dieser abgeleitet werden.
|
||||
*/
|
||||
public interface ChartValue {
|
||||
|
||||
/**
|
||||
* Gibt den x-Wert des Datenpunktes zurück. Nicht alle Diagrammarten
|
||||
* benötigen einen x-Wert und ignorieren diesen dann gegebenenfalls. Soll
|
||||
* die Klasse nur in Diagrammen ohne x-Wert (zum Beispiel
|
||||
* {@link PieChart Kreis-} oder {@link BarChart Balkendiagramm}) dargestellt
|
||||
* werden, wird empfohlen, dass immer {@code 0} zurückgegeben wird.
|
||||
*
|
||||
* @return Der x-Wert des Datenpunktes.
|
||||
*/
|
||||
double getX();
|
||||
|
||||
/**
|
||||
* Gibt den Datenwert des Datenpunktes zurück.
|
||||
* <p>
|
||||
* Der Datenwert wird je nach Diagrammtyp anders interpretiert. In
|
||||
* {@link LineChart}s wird er im Zusammenhang mit dem {@link #getX() x-Wert}
|
||||
* als y-Wert eines Datenpunktes genommen. In
|
||||
* {@link PieChart Kreisdiagrammen} wird er als Anteil des Datums im Kreis
|
||||
* verstanden.
|
||||
*
|
||||
* @return Der Wert des Datenpunktes.
|
||||
*/
|
||||
double getValue();
|
||||
|
||||
/**
|
||||
* Ändert den Datenwert dieses Datenpunktes.
|
||||
*
|
||||
* @param pValue
|
||||
*/
|
||||
void setValue( double pValue );
|
||||
|
||||
/**
|
||||
* Gibt eine Beschriftung für den Datenpunkt zurück. Wird {@code null}
|
||||
* zurückgegeben, dann erstellt das Diagramm automatisch eine Beschriftung,
|
||||
* falls nötig (in der Regel der Datenwert).
|
||||
*
|
||||
* @return Eine Beschriftung.
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Ändert die Beschriftung dieses Datenpunktes.
|
||||
*
|
||||
* @param pLabel Die neue Beschriftung.
|
||||
*/
|
||||
void setLabel( String pLabel );
|
||||
|
||||
/**
|
||||
* Gibt eine Farbe für den Datenpunkt zurück. Wird {@code null}
|
||||
* zurückgegeben, dann wählt das Diagramm automatisch eine Farbe für die
|
||||
* Darstellung.
|
||||
*
|
||||
* @return Eine Farbe.
|
||||
*/
|
||||
Color getColor();
|
||||
|
||||
/**
|
||||
* Ändert die Farbe dieses Datenpunktes.
|
||||
*
|
||||
* @param pColor Die neue Farbe.
|
||||
*/
|
||||
void setColor( Color pColor );
|
||||
|
||||
}
|
||||
130
src/main/java/schule/ngb/zm/charts/LineChart.java
Normal file
130
src/main/java/schule/ngb/zm/charts/LineChart.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package schule.ngb.zm.charts;
|
||||
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.util.Collection;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class LineChart extends Rectangle {
|
||||
|
||||
protected SortedMap<Double, ChartValue> values;
|
||||
|
||||
protected boolean drawLines = true;
|
||||
|
||||
public LineChart( double x, double y, double width, double height ) {
|
||||
this(x, y, width, height, null, null);
|
||||
}
|
||||
|
||||
public LineChart( double x, double y, double width, double height, double[] pCoordinates, double[] pValues ) {
|
||||
super(x, y, width, height);
|
||||
|
||||
this.values = new TreeMap<>();
|
||||
if( pCoordinates != null || pValues != null ) {
|
||||
if( pCoordinates == null ) {
|
||||
pCoordinates = new double[0];
|
||||
}
|
||||
if( pValues == null ) {
|
||||
pCoordinates = new double[0];
|
||||
}
|
||||
for( int i = 0; i < max(pCoordinates.length, pValues.length); i++ ) {
|
||||
if( i < pValues.length && i < pCoordinates.length ) {
|
||||
addValue(pCoordinates[i], pValues[i]);
|
||||
} else if( i < pCoordinates.length ) {
|
||||
addValue(pCoordinates[i], 0.0);
|
||||
} else {
|
||||
addValue(0.0, pValues[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addValue( double pCoordinate, double pValue ) {
|
||||
values.put(pCoordinate, new BasicChartValue(pCoordinate, pValue));
|
||||
}
|
||||
|
||||
public void removeValue( double x ) {
|
||||
values.remove(x);
|
||||
}
|
||||
|
||||
public double getValue( double x ) {
|
||||
ChartValue value = values.get(x);
|
||||
if( value == null )
|
||||
throw new NoSuchElementException(String.format("No chart value for x-value <%f> found.", x));
|
||||
return value.getValue();
|
||||
}
|
||||
|
||||
public void setValue( double x, double pValue ) {
|
||||
ChartValue value = values.get(x);
|
||||
if( value != null ) {
|
||||
value.setValue(pValue);
|
||||
} else {
|
||||
this.addValue(x, pValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
double xMax = values.lastKey();
|
||||
double yMax = Double.MIN_VALUE;
|
||||
|
||||
Collection<ChartValue> val = values.values();
|
||||
for( ChartValue lcv : val ) {
|
||||
if( lcv.getValue() > yMax ) {
|
||||
yMax = lcv.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
double xUnit = width/xMax;
|
||||
double yUnit = height/yMax;
|
||||
|
||||
AffineTransform originalTransform = graphics.getTransform();
|
||||
graphics.setTransform(transform);
|
||||
|
||||
drawAxes(graphics, xUnit, yUnit);
|
||||
|
||||
ChartValue lastLcv = null;
|
||||
for( ChartValue lcv : val ) {
|
||||
if( drawLines && lastLcv != null ) {
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.drawLine((int)(lastLcv.getX()*xUnit), (int)(height - lastLcv.getValue()*yUnit), (int)(lcv.getX()*xUnit), (int)(height - lcv.getValue()*yUnit));
|
||||
drawDot(graphics, lastLcv, xUnit, yUnit);
|
||||
}
|
||||
|
||||
drawDot(graphics, lcv, xUnit, yUnit);
|
||||
lastLcv = lcv;
|
||||
}
|
||||
|
||||
graphics.setTransform(originalTransform);
|
||||
}
|
||||
|
||||
private void drawDot( Graphics2D graphics, ChartValue lcv, double xUnit, double yUnit ) {
|
||||
int dotSize = (int) round(strokeWeight * 2);
|
||||
graphics.setColor(getFillColor().getJavaColor());
|
||||
graphics.fillRect((int)(lcv.getX()*xUnit - dotSize), (int)(height - lcv.getValue()*yUnit - dotSize), dotSize+dotSize, dotSize+dotSize);
|
||||
}
|
||||
|
||||
private void drawAxes( Graphics2D graphics, double xUnit, double yUnit ) {
|
||||
graphics.setColor(BLACK.getJavaColor());
|
||||
graphics.setStroke(new BasicStroke());
|
||||
|
||||
graphics.drawLine(0, (int)height, 0, 0);
|
||||
graphics.drawLine(0, (int)height, (int)width, (int)height);
|
||||
|
||||
for( double i = xUnit; i <= width; i += xUnit ) {
|
||||
int xx = (int)i;
|
||||
graphics.drawLine(xx, (int)(height-2), xx, (int)(height+2));
|
||||
}
|
||||
for( double i = height-yUnit; i >= 0; i -= yUnit ) {
|
||||
int yy = (int)i;
|
||||
graphics.drawLine(-2, yy, 2, yy);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
336
src/main/java/schule/ngb/zm/charts/PieChart.java
Normal file
336
src/main/java/schule/ngb/zm/charts/PieChart.java
Normal file
@@ -0,0 +1,336 @@
|
||||
package schule.ngb.zm.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.shapes.Circle;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class PieChart extends Circle {
|
||||
|
||||
public static String DEFAULT_LABEL = "%.2f";
|
||||
|
||||
protected ArrayList<ChartValue> values;
|
||||
|
||||
protected HashMap<ChartValue, Color> colorMap;
|
||||
|
||||
protected double sum = 0;
|
||||
|
||||
protected boolean clockwise = true, labels = true, labelsInline = false;
|
||||
|
||||
public PieChart( double x, double y, double radius ) {
|
||||
this(x, y, radius, null);
|
||||
}
|
||||
|
||||
public PieChart( double x, double y, double radius, double[] pValues ) {
|
||||
super(x, y, radius);
|
||||
|
||||
if( pValues != null ) {
|
||||
this.values = new ArrayList<>(pValues.length);
|
||||
|
||||
for( double value : pValues ) {
|
||||
this.addValue(value);
|
||||
sum += value;
|
||||
}
|
||||
} else {
|
||||
this.values = new ArrayList<>(10);
|
||||
}
|
||||
|
||||
colorMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public double getSum() {
|
||||
return sum;
|
||||
}
|
||||
|
||||
public boolean isClockwise() {
|
||||
return clockwise;
|
||||
}
|
||||
|
||||
public void setClockwise( boolean pClockwise ) {
|
||||
this.clockwise = pClockwise;
|
||||
}
|
||||
|
||||
public boolean isLabelsVisible() {
|
||||
return labels;
|
||||
}
|
||||
|
||||
public void setLabelsVisible( boolean pLabels ) {
|
||||
this.labels = pLabels;
|
||||
}
|
||||
|
||||
public boolean isLabelsInline() {
|
||||
return labelsInline;
|
||||
}
|
||||
|
||||
public void setLabelsInline( boolean pLabelsInline ) {
|
||||
this.labelsInline = pLabelsInline;
|
||||
}
|
||||
|
||||
public ChartValue[] getChartValues() {
|
||||
ChartValue[] ret = new ChartValue[values.size()];
|
||||
return values.toArray(ret);
|
||||
}
|
||||
|
||||
public double[] getValues() {
|
||||
double[] ret = new double[values.size()];
|
||||
int i = 0;
|
||||
for( ChartValue cv : values ) {
|
||||
ret[i++] = cv.getValue();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void addValue( double pValue ) {
|
||||
addValue(pValue, randomNiceColor());
|
||||
}
|
||||
|
||||
public void addValue( double pValue, Color pColor ) {
|
||||
addValue(pValue, String.format(DEFAULT_LABEL, pValue), pColor);
|
||||
}
|
||||
|
||||
public void addValue( double pValue, String pLabel, Color pColor ) {
|
||||
addValue(new BasicChartValue(pValue, pLabel, pColor));
|
||||
}
|
||||
|
||||
public void addValue( ChartValue pValue ) {
|
||||
values.add(pValue);
|
||||
sum += pValue.getValue();
|
||||
}
|
||||
|
||||
public void addValues( double[] pValues ) {
|
||||
for( double value : pValues ) {
|
||||
addValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void addValues( ChartValue[] pValues ) {
|
||||
for( ChartValue value : pValues ) {
|
||||
addValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void addValues( Collection<ChartValue> pValues ) {
|
||||
for( ChartValue value : pValues ) {
|
||||
addValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeValue( int i ) {
|
||||
if( i < values.size() ) {
|
||||
ChartValue pcv = values.remove(i);
|
||||
if( pcv != null ) {
|
||||
sum -= pcv.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeValue( ChartValue pValue ) {
|
||||
if( values.remove(pValue) ) {
|
||||
sum -= pValue.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeValues( ChartValue[] pValues ) {
|
||||
for( ChartValue value : pValues ) {
|
||||
removeValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeValues( Collection<ChartValue> pValues ) {
|
||||
for( ChartValue value : pValues ) {
|
||||
removeValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public double getValue( int i ) {
|
||||
return values.get(i).getValue();
|
||||
}
|
||||
|
||||
public void setValue( int i, double pValue ) {
|
||||
values.get(i).setValue(pValue);
|
||||
}
|
||||
|
||||
public boolean containsValue( ChartValue pValue ) {
|
||||
return values.contains(pValue);
|
||||
}
|
||||
|
||||
public String[] getLabels() {
|
||||
String[] ret = new String[values.size()];
|
||||
int i = 0;
|
||||
for( ChartValue cv : values ) {
|
||||
ret[i++] = cv.getLabel();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public String getLabel( int i ) {
|
||||
return values.get(i).getLabel();
|
||||
}
|
||||
|
||||
public void setLabel( int i, String pLabel ) {
|
||||
values.get(i).setLabel(pLabel);
|
||||
}
|
||||
|
||||
public void setLabels( String... pLabels ) {
|
||||
for( int i = 0; i < values.size(); i++ ) {
|
||||
if( i < pLabels.length ) {
|
||||
values.get(i).setLabel(pLabels[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Color[] getColors() {
|
||||
Color[] ret = new Color[values.size()];
|
||||
int i = 0;
|
||||
for( ChartValue bcv : values ) {
|
||||
ret[i] = bcv.getColor();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void setColors( Color... pColors ) {
|
||||
for( int i = 0; i < values.size(); i++ ) {
|
||||
if( i < pColors.length ) {
|
||||
values.get(i).setColor(pColors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Color getColor( int i ) {
|
||||
return values.get(i).getColor();
|
||||
}
|
||||
|
||||
public void setColor( int i, Color pColor ) {
|
||||
values.get(i).setColor(pColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
AffineTransform originalTransform = graphics.getTransform();
|
||||
graphics.setTransform(transform);
|
||||
|
||||
int startAngle = 0;
|
||||
for( int i = 0; i < values.size(); i++ ) {
|
||||
ChartValue pcv;
|
||||
if( clockwise ) {
|
||||
pcv = values.get(values.size() - i - 1);
|
||||
} else {
|
||||
pcv = values.get(i);
|
||||
}
|
||||
int angle = 360 - startAngle;
|
||||
if( i < values.size() - 1 ) {
|
||||
angle = (int) round(pcv.getValue() * 360.0 / sum);
|
||||
}
|
||||
|
||||
if( pcv.getColor() == null ) {
|
||||
if( !colorMap.containsKey(pcv) ) {
|
||||
colorMap.put(pcv, randomNiceColor());
|
||||
}
|
||||
graphics.setColor(colorMap.get(pcv).getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(pcv.getColor().getJavaColor());
|
||||
}
|
||||
graphics.fillArc(0, 0, (int) (radius * 2), (int) (radius * 2), startAngle, angle);
|
||||
|
||||
if( labels ) {
|
||||
String label = pcv.getLabel();
|
||||
if( label == null ) {
|
||||
label = String.format("%.2f", pcv.getValue());
|
||||
}
|
||||
|
||||
int lWidth = graphics.getFontMetrics().stringWidth(label);
|
||||
int lHeight = graphics.getFontMetrics().getAscent();
|
||||
|
||||
double heading = radians(startAngle + angle * .5);
|
||||
|
||||
boolean onLeft = (heading > HALF_PI && heading < PI + HALF_PI);
|
||||
|
||||
|
||||
AffineTransform tt = graphics.getTransform();
|
||||
graphics.translate(radius, radius);
|
||||
|
||||
|
||||
if( labelsInline ) {
|
||||
if( pcv.getColor() == null ) {
|
||||
graphics.setColor(colorMap.get(pcv).textcolor().getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(pcv.getColor().textcolor().getJavaColor());
|
||||
}
|
||||
|
||||
if( onLeft ) {
|
||||
graphics.rotate(-heading - PI);
|
||||
graphics.translate(-radius + 10, lHeight * .5);
|
||||
} else {
|
||||
graphics.rotate(-heading);
|
||||
graphics.translate(radius - 10 - lWidth, lHeight * .5);
|
||||
}
|
||||
} else {
|
||||
double lX = (radius + 10) * Math.cos(-heading);
|
||||
double lY = (radius + 10) * Math.sin(-heading);
|
||||
|
||||
if( onLeft ) {
|
||||
graphics.translate(lX - lWidth, lY + lHeight * .5);
|
||||
} else {
|
||||
graphics.translate(lX, lY + lHeight * .5);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if( labelsInline ) {
|
||||
lX *= (radius - 10);
|
||||
lY *= (radius - 10);
|
||||
if( lX > 0 ) {
|
||||
lX -= lWidth;
|
||||
}
|
||||
if( lY < 0 ) {
|
||||
lY += lHeight;
|
||||
}
|
||||
|
||||
if( pcv.getColor() == null ) {
|
||||
graphics.setColor(colorMap.get(pcv).textcolor().getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(pcv.getColor().textcolor().getJavaColor());
|
||||
}
|
||||
|
||||
graphics.translate(radius, radius);
|
||||
graphics.fillOval(0, 0, 5, 6);
|
||||
graphics.rotate(-Math.atan2(lY, lX));
|
||||
} else {
|
||||
double heading = Math.atan2(lY, lX);
|
||||
if( lX < 0 ) {
|
||||
graphics.translate(-lWidth, 0);
|
||||
}
|
||||
if( lY > 0 ) {
|
||||
graphics.translate(0, lHeight);
|
||||
}
|
||||
|
||||
graphics.rotate(Math.atan2(lY, lX));
|
||||
if( lX < 0 ) {
|
||||
graphics.translate(-radius - 10, 0);
|
||||
} else {
|
||||
graphics.translate(radius + 10, 0);
|
||||
}
|
||||
graphics.fillOval(0, 0, 5, 6);
|
||||
}
|
||||
*/
|
||||
|
||||
graphics.drawString(label, 0, 0);
|
||||
graphics.setTransform(tt);
|
||||
}
|
||||
|
||||
startAngle += angle;
|
||||
}
|
||||
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.drawOval(0, 0, (int) (radius * 2), (int) (radius * 2));
|
||||
|
||||
graphics.setTransform(originalTransform);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
136
src/main/java/schule/ngb/zm/charts/RingChart.java
Normal file
136
src/main/java/schule/ngb/zm/charts/RingChart.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package schule.ngb.zm.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.shapes.Circle;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Stroke;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
public class RingChart extends Circle {
|
||||
|
||||
public static String DEFAULT_LABEL = "%.2f";
|
||||
|
||||
public interface RingChartValue extends ChartValue {
|
||||
double getMax();
|
||||
}
|
||||
|
||||
private class BasicRingChartValue extends BasicChartValue implements RingChartValue {
|
||||
|
||||
public BasicRingChartValue( double maxValue, double value, String label, Color color ) {
|
||||
super(maxValue, value, label, color);
|
||||
}
|
||||
|
||||
public double getMax() {
|
||||
return getX();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected RingChartValue[] values;
|
||||
|
||||
public RingChart( double x, double y, double radius ) {
|
||||
this(x, y, radius, 3);
|
||||
}
|
||||
|
||||
public RingChart( double x, double y, double radius, int initalSize ) {
|
||||
super(x, y, radius);
|
||||
this.values = new RingChartValue[initalSize];
|
||||
}
|
||||
|
||||
|
||||
public void setValue( int pRing, RingChartValue pValue ) {
|
||||
if( pRing < values.length ) {
|
||||
values[pRing] = pValue;
|
||||
} else {
|
||||
throw new ArrayIndexOutOfBoundsException("Cannot set ring " + pRing + " of " + values.length);
|
||||
}
|
||||
}
|
||||
|
||||
public void setValue( int pRing, double pValue, double pMax ) {
|
||||
setValue(pRing, new BasicRingChartValue(pMax, pValue, String.format(DEFAULT_LABEL, pValue), randomNiceColor()));
|
||||
}
|
||||
|
||||
public void setValue( int pRing, double pValue, double pMax, String pLabel ) {
|
||||
setValue(pRing, new BasicRingChartValue(pMax, pValue, pLabel, randomNiceColor()));
|
||||
}
|
||||
|
||||
public void setValue( int pRing, double pValue, double pMax, String pLabel, Color pColor ) {
|
||||
setValue(pRing, new BasicRingChartValue(pMax, pValue, pLabel, pColor));
|
||||
}
|
||||
|
||||
public void setColors( Color... pColors ) {
|
||||
for( int i = 0; i < values.length; i++ ) {
|
||||
if( i < pColors.length ) {
|
||||
values[i].setColor(pColors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setColor( int i, Color pColor ) {
|
||||
values[i].setColor(pColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
AffineTransform originalTransform = graphics.getTransform();
|
||||
graphics.setTransform(transform);
|
||||
|
||||
double len = values.length + 1;
|
||||
double gap = radius * 0.02;
|
||||
|
||||
//strokeWeight = radius/len;
|
||||
//strokeWeight = ((radius - 2 * values.length) - gap) / len;
|
||||
int outline = 1;
|
||||
strokeWeight = (radius - gap * values.length) / len;
|
||||
|
||||
Stroke outlineStroke = new BasicStroke((float) strokeWeight, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
|
||||
Stroke ringStroke = new BasicStroke((float) (strokeWeight - 2 * outline), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
|
||||
|
||||
|
||||
int startAngle = 90;
|
||||
for( int i = 0; i < values.length; i++ ) {
|
||||
RingChartValue rcv = values[values.length - 1 - i];
|
||||
if( rcv != null ) {
|
||||
/*int ringX = (int) (-((i + 1) * strokeWeight));
|
||||
int ringY = (int) (-((i + 1) * strokeWeight) - (i * gap));
|
||||
int ringR = (int) ((i + 1) * strokeWeight * 2); */
|
||||
int ringX = (int) (i * (strokeWeight + gap));
|
||||
int ringY = (int) (i * (strokeWeight + gap));
|
||||
int ringR = (int) ((values.length - i) * (strokeWeight + gap) * 2);
|
||||
int angle = (int) round(rcv.getValue() * 360.0 / rcv.getMax());
|
||||
|
||||
if( rcv.getValue() > rcv.getMax() ) {
|
||||
graphics.setStroke(outlineStroke);
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.drawArc(ringX, ringY, ringR, ringR, startAngle - angle + 360, -60);
|
||||
|
||||
graphics.setStroke(ringStroke);
|
||||
graphics.setColor(rcv.getColor().getJavaColor());
|
||||
graphics.drawArc(ringX, ringY, ringR, ringR, startAngle - angle + 360, -60);
|
||||
|
||||
graphics.setStroke(outlineStroke);
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.drawArc(ringX, ringY, ringR, ringR, startAngle - angle + 330, -330);
|
||||
|
||||
graphics.setStroke(ringStroke);
|
||||
graphics.setColor(rcv.getColor().getJavaColor());
|
||||
graphics.drawArc(ringX, ringY, ringR, ringR, startAngle - angle + 340, -340);
|
||||
} else {
|
||||
graphics.setStroke(outlineStroke);
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.drawArc(ringX, ringY, ringR, ringR, startAngle, -angle);
|
||||
|
||||
graphics.setStroke(ringStroke);
|
||||
graphics.setColor(rcv.getColor().getJavaColor());
|
||||
graphics.drawArc(ringX, ringY + outline, ringR, ringR, startAngle, -angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
graphics.setTransform(originalTransform);
|
||||
}
|
||||
|
||||
}
|
||||
83
src/main/java/schule/ngb/zm/media/Audio.java
Normal file
83
src/main/java/schule/ngb/zm/media/Audio.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
/**
|
||||
* Interface für Audio-Medien.
|
||||
*/
|
||||
public interface Audio {
|
||||
|
||||
String getSource();
|
||||
|
||||
/**
|
||||
* Prüft, ob das Medium gerade abgespielt wird.
|
||||
*
|
||||
* @return {@code true}, wenn das Medium abgespielt wird, {@code false}
|
||||
* sonst.
|
||||
*/
|
||||
boolean isPlaying();
|
||||
|
||||
/**
|
||||
* Prüft, ob das Medium gerade in einer Schleife abgespielt wird. Wenn
|
||||
* {@code isLooping() == true}, dann muss auch immer
|
||||
* {@code isPlaying() == true} gelten.
|
||||
*
|
||||
* @return {@code true}, wenn das Medium in einer Schleife abgespielt wird,
|
||||
* {@code false} sonst.
|
||||
*/
|
||||
boolean isLooping();
|
||||
|
||||
/**
|
||||
* Legt die Lautstärke des Mediums beim Abspielen fest.
|
||||
* <p>
|
||||
* Die Lautstärke wird auf einer linearen Skale festgelegt, wobei 0 kein Ton
|
||||
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
||||
* Mediums.
|
||||
*
|
||||
* @param volume Die neue Lautstärke zwischen 0 und 1.
|
||||
* @see <a
|
||||
* href="https://stackoverflow.com/a/40698149">https://stackoverflow.com/a/40698149</a>
|
||||
*/
|
||||
void setVolume( double volume );
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Lautstärkeeinstellung dieses Mediums zurück.
|
||||
* <p>
|
||||
* Die Lautstärke wird auf einer linearen Skale angegeben, wobei 0 kein Ton
|
||||
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
||||
* Mediums.
|
||||
*
|
||||
* @return Die Lautstärke als linear skalierter Wert.
|
||||
*/
|
||||
double getVolume();
|
||||
|
||||
/**
|
||||
* Startet die Wiedergabe des Mediums und beendet die Methode. Das
|
||||
* Audio-Medium wird einmal abgespielt und stoppt dann.
|
||||
*/
|
||||
void play();
|
||||
|
||||
/**
|
||||
* Startet die Wiedergabe des Mediums und blockiert das Programm, bis die
|
||||
* Wiedergabe beendet ist.
|
||||
*/
|
||||
void playAndWait();
|
||||
|
||||
/**
|
||||
* Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode
|
||||
* startet die Wiedergabe und beendet dann direkt die Methode. Um die
|
||||
* Wiedergabe zu stoppen muss {@link #stop()} aufgerufen werden.
|
||||
*/
|
||||
void loop();
|
||||
|
||||
/**
|
||||
* Stoppt die Wiedergabe. Wird das Medium gerade nicht abgespielt
|
||||
* {@code isPlaying() == false}, dann passiert nichts.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Stoppt die Wiedergabe und gibt alle Resourcen, die für das Medium
|
||||
* verwendet werden, frei.
|
||||
*/
|
||||
void dispose();
|
||||
|
||||
}
|
||||
11
src/main/java/schule/ngb/zm/media/AudioListener.java
Normal file
11
src/main/java/schule/ngb/zm/media/AudioListener.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.events.Listener;
|
||||
|
||||
public interface AudioListener extends Listener<Audio> {
|
||||
|
||||
void start( Audio source );
|
||||
|
||||
void stop( Audio source );
|
||||
|
||||
}
|
||||
181
src/main/java/schule/ngb/zm/media/Mixer.java
Normal file
181
src/main/java/schule/ngb/zm/media/Mixer.java
Normal file
@@ -0,0 +1,181 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ein Mixer ist eine Sammlung mehrerer {@link Audio Audio-Medien}, die
|
||||
* gemeinsam kontrolliert werden können.
|
||||
* <p>
|
||||
* Im einfachsten Fall kann die Audio-Gruppe gemeinsam gestartet und gestoppt
|
||||
* werden. Ein Mixer kann die Lautstärke der Medien in Relation zueinander
|
||||
* setzen. Dazu wird jedem Medium ein Faktor mitgegeben. Ein Medium mit dem
|
||||
* Faktor 0.5 ist dann halb so laut wie eines, mit dem Faktor 1.0.
|
||||
* <p>
|
||||
* Darüber hinaus kann ein Mixer Effekte wie einen
|
||||
* {@link #fade(double, int) fadeIn} auf die Medien anwenden.
|
||||
*/
|
||||
public class Mixer implements Audio {
|
||||
|
||||
private List<AudioWrapper> audios;
|
||||
|
||||
private float volume = 0.8f;
|
||||
|
||||
class AudioWrapper {
|
||||
|
||||
Audio audio;
|
||||
|
||||
float volumeFactor;
|
||||
|
||||
public AudioWrapper( Audio audio, float volumeFactor ) {
|
||||
this.audio = audio;
|
||||
this.volumeFactor = volumeFactor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Mixer() {
|
||||
this.audios = new ArrayList<>(4);
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public void add( Audio pAudio ) {
|
||||
add(pAudio, 1f);
|
||||
}
|
||||
|
||||
public void add( Audio pAudio, double pVolumeFactor ) {
|
||||
audios.add(new AudioWrapper(pAudio, (float) pVolumeFactor));
|
||||
pAudio.setVolume(pVolumeFactor * volume);
|
||||
}
|
||||
|
||||
public void remove( Audio pAudio ) {
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
audios.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
return audios.stream().anyMatch(aw -> aw.audio.isPlaying());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isLooping() {
|
||||
return audios.stream().anyMatch(aw -> aw.audio.isLooping());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setVolume( double pVolume ) {
|
||||
volume = (float) pVolume;
|
||||
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * pVolume));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void play() {
|
||||
audios.stream().forEach(aw -> aw.audio.play());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void playAndWait() {
|
||||
audios.stream().forEach(aw -> aw.audio.play());
|
||||
while( audios.stream().anyMatch(aw -> aw.audio.isPlaying()) ) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch( InterruptedException e ) {
|
||||
// Just keep waiting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
audios.stream().forEach(aw -> aw.audio.loop());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
audios.stream().forEach(aw -> aw.audio.stop());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
if( isPlaying() ) {
|
||||
stop();
|
||||
}
|
||||
audios.stream().forEach(aw -> aw.audio.dispose());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ändert die Lautstärke aller hinzugefügten Audiomedien in der angegebenen
|
||||
* Zeit schrittweise, bis die angegebene Lautstärke erreicht ist.
|
||||
* <p>
|
||||
* Zu beachten ist, dass die Lautstärke des Mixers angepasst wird. Das
|
||||
* bedeutet, dass die Lautstärke der hinzugefügten Medien mit ihrem
|
||||
* Lautstärkefaktor multipliziert werden. Die Medien haben am Ende also
|
||||
* nicht unbedingt die Lautstärke {@code to}.
|
||||
*
|
||||
* @param to Der Zielwert für die Lautstärke.
|
||||
* @param time Die Zeit, nach der die Änderung abgeschlossen sein soll.
|
||||
*/
|
||||
public void fade( final double to, final int time ) {
|
||||
TaskRunner.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final long start = System.currentTimeMillis();
|
||||
double t = 0.0;
|
||||
double from = volume;
|
||||
do {
|
||||
setVolume(Constants.interpolate(from, to, t));
|
||||
t = (double) (System.currentTimeMillis() - start) / (double) time;
|
||||
|
||||
try {
|
||||
Thread.sleep(1000 / Constants.framesPerSecond);
|
||||
} catch( InterruptedException e ) {
|
||||
// Keep waiting
|
||||
}
|
||||
} while( t < 1.0 );
|
||||
setVolume(to);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
291
src/main/java/schule/ngb/zm/media/Music.java
Normal file
291
src/main/java/schule/ngb/zm/media/Music.java
Normal file
@@ -0,0 +1,291 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.events.EventDispatcher;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Ein Musikstück, dass im Projekt abgespielt werden soll.
|
||||
* <p>
|
||||
* Im gegensatz zu einem {@link Sound} sind Musikstücke längere Audiodateien,
|
||||
* die zum Beispiel als Hintergrundmusik ablaufen sollen. Die Musik wird daher
|
||||
* nicht komplett in den Speicher geladen, sondern direkt aus der Audioquelle
|
||||
* gestreamt und wiedergegeben.
|
||||
*/
|
||||
public class Music implements Audio {
|
||||
|
||||
// size of the byte buffer used to read/write the audio stream
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
|
||||
/**
|
||||
* Ob der Sound gerade abgespielt wird.
|
||||
*/
|
||||
private boolean playing = false;
|
||||
|
||||
/**
|
||||
* Ob der Sound gerade in einer Schleife abgespielt wird.
|
||||
*/
|
||||
private boolean looping = false;
|
||||
|
||||
/**
|
||||
* Die Quelle des Musikstücks.
|
||||
*/
|
||||
private String audioSource;
|
||||
|
||||
/**
|
||||
* Der AudioStream, um die AUdiosdaten zulsen, falls dieser schon geöffnet
|
||||
* wurde. Sonst {@code null}.
|
||||
*/
|
||||
private AudioInputStream audioStream;
|
||||
|
||||
/**
|
||||
* Die Line für die Ausgabe, falls diese schon geöffnet wurde. Sonst
|
||||
* {@code null}.
|
||||
*/
|
||||
private SourceDataLine audioLine;
|
||||
|
||||
/**
|
||||
* Die Lautstärke der Musik.
|
||||
*/
|
||||
private float volume = 0.8f;
|
||||
|
||||
EventDispatcher<Audio, AudioListener> events;
|
||||
{
|
||||
events = new EventDispatcher<>();
|
||||
events.registerEventType("start", (a,l) -> l.start(a));
|
||||
events.registerEventType("stop", (a,l) -> l.stop(a));
|
||||
}
|
||||
|
||||
public Music( String source ) {
|
||||
Validator.requireNotNull(source);
|
||||
this.audioSource = source;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public void addListener( AudioListener listener ) {
|
||||
events.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener( AudioListener listener ) {
|
||||
events.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
return playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isLooping() {
|
||||
if( !playing ) {
|
||||
looping = false;
|
||||
}
|
||||
return looping;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setVolume( double volume ) {
|
||||
this.volume = (float) volume;
|
||||
if( audioLine != null ) {
|
||||
applyVolume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
|
||||
* anzuwenden.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void play() {
|
||||
if( openLine() ) {
|
||||
TaskRunner.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stream();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void playAndWait() {
|
||||
if( openLine() ) {
|
||||
stream();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
looping = true;
|
||||
play();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
playing = false;
|
||||
looping = false;
|
||||
dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
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;
|
||||
events.dispatchEvent("start", Music.this);
|
||||
|
||||
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 ) {
|
||||
LOG.warn(ex, "Error while playing Music source <%s>", audioSource);
|
||||
}
|
||||
|
||||
// Wait for the remaining audio to play
|
||||
/*while( audioLine.isRunning() ) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch( InterruptedException ex ) {
|
||||
// Just keep waiting ...
|
||||
}
|
||||
}*/
|
||||
|
||||
playing = false;
|
||||
streamingStopped();
|
||||
events.dispatchEvent("stop", Music.this);
|
||||
}
|
||||
|
||||
private boolean openLine() {
|
||||
if( audioLine != null ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
URL url = ResourceStreamProvider.getResourceURL(audioSource);
|
||||
if( url != null ) {
|
||||
final AudioInputStream inStream = AudioSystem.getAudioInputStream(url);
|
||||
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 {
|
||||
LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource);
|
||||
}
|
||||
} catch( UnsupportedAudioFileException ex ) {
|
||||
LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource);
|
||||
} catch( LineUnavailableException ex ) {
|
||||
LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource);
|
||||
} catch( IOException ex ) {
|
||||
LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void streamingStopped() {
|
||||
dispose();
|
||||
|
||||
if( looping ) {
|
||||
if( openLine() ) {
|
||||
stream();
|
||||
} else {
|
||||
playing = false;
|
||||
looping = false;
|
||||
}
|
||||
} else {
|
||||
playing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(Music.class);
|
||||
|
||||
}
|
||||
309
src/main/java/schule/ngb/zm/media/Sound.java
Normal file
309
src/main/java/schule/ngb/zm/media/Sound.java
Normal file
@@ -0,0 +1,309 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden.
|
||||
* <p>
|
||||
* In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer Sounds,
|
||||
* die zusammen mit bestimmten Aktionen wiedergegeben werden (zum Beispiel, wenn
|
||||
* die Spielfigur springt, wenn zwei Objekte kollidieren, usw.). Sounds werden
|
||||
* komplett in den Speicher geladen und können dadurch immer wieder, als
|
||||
* Schleife oder auch nur Abschnittsweise abgespielt werden.
|
||||
* <p>
|
||||
* Für längre Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher die
|
||||
* KLasse {@link Music} an.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Sound implements Audio {
|
||||
|
||||
/**
|
||||
* Ob der Sound gerade abgespielt wird.
|
||||
*/
|
||||
private boolean playing = false;
|
||||
|
||||
/**
|
||||
* Ob der Sound gerade in einer Schleife abgespielt wird.
|
||||
*/
|
||||
private boolean looping = false;
|
||||
|
||||
/**
|
||||
* Die Quelle des Musikstücks.
|
||||
*/
|
||||
private String audioSource;
|
||||
|
||||
/**
|
||||
* Der Clip, falls er schon geladen wurde, sonst {@code null}.
|
||||
*/
|
||||
private Clip audioClip;
|
||||
|
||||
/**
|
||||
* Ob die Resourcen des Clips im Speicher nach dem nächsten Abspielen
|
||||
* freigegeben werden sollen.
|
||||
*/
|
||||
private boolean disposeAfterPlay = false;
|
||||
|
||||
/**
|
||||
* Die Lautstärke des Clips.
|
||||
*/
|
||||
private float volume = 0.8f;
|
||||
|
||||
/**
|
||||
* Erstellt einen Sound aus der angegebene Quelle.
|
||||
*
|
||||
* @param source Ein Dateipfad oder eine Webadresse.
|
||||
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
||||
*/
|
||||
public Sound( String source ) {
|
||||
Validator.requireNotNull(source);
|
||||
this.audioSource = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
// return audioClip != null && audioClip.isRunning();
|
||||
return playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isLooping() {
|
||||
if( !playing ) {
|
||||
looping = false;
|
||||
}
|
||||
return looping;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setVolume( double volume ) {
|
||||
this.volume = (float) volume;
|
||||
if( audioClip != null ) {
|
||||
applyVolume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
|
||||
* anzuwenden.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
looping = false;
|
||||
if( audioClip.isRunning() ) {
|
||||
audioClip.stop();
|
||||
}
|
||||
playing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void play() {
|
||||
if( this.openClip() ) {
|
||||
audioClip.start();
|
||||
playing = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
|
||||
* frei.
|
||||
* <p>
|
||||
* Der Aufruf ist effektiv gleich zu
|
||||
* <pre><code>
|
||||
* clip.playAndWait();
|
||||
* clip.dispose();
|
||||
* </code></pre>
|
||||
* allerdings wird der aufrufende Thread nicht blockiert und
|
||||
* {@link #dispose()} automatisch am Ende aufgerufen.
|
||||
*/
|
||||
public void playOnce() {
|
||||
disposeAfterPlay = true;
|
||||
play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
|
||||
* frei.
|
||||
* <p>
|
||||
* Der Aufruf entspricht
|
||||
* <pre><code>
|
||||
* clip.playAndWait();
|
||||
* clip.dispose();
|
||||
* </code></pre>
|
||||
*/
|
||||
public void playOnceAndWait() {
|
||||
disposeAfterPlay = true;
|
||||
playAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
loop(Clip.LOOP_CONTINUOUSLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und stoppt
|
||||
* die Wiedergabe dann.
|
||||
* @param count Anzahl der Wiederholungen.
|
||||
*/
|
||||
public void loop( int count ) {
|
||||
if( count > 0 ) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
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 {
|
||||
LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource);
|
||||
}
|
||||
} catch( UnsupportedAudioFileException ex ) {
|
||||
LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource);
|
||||
} catch( LineUnavailableException ex ) {
|
||||
LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource);
|
||||
} catch( IOException ex ) {
|
||||
LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Interne Methode, die aufgerufen wird, wenn die Wiedergabe gestoppt wird.
|
||||
* Entweder durch einen Aufruf von {@link #stop()} oder, weil die Wiedergabe
|
||||
* nach {@link #playOnce()} beendet wurde.
|
||||
*/
|
||||
private void playbackStopped() {
|
||||
playing = false;
|
||||
if( disposeAfterPlay ) {
|
||||
this.dispose();
|
||||
disposeAfterPlay = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(Sound.class);
|
||||
|
||||
}
|
||||
106
src/main/java/schule/ngb/zm/shapes/Arc.java
Normal file
106
src/main/java/schule/ngb/zm/shapes/Arc.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.geom.Arc2D;
|
||||
|
||||
public class Arc extends Shape {
|
||||
|
||||
protected double width;
|
||||
|
||||
protected double height;
|
||||
|
||||
protected double angle;
|
||||
|
||||
protected double startingangle;
|
||||
|
||||
protected Options.PathType type = OPEN;
|
||||
|
||||
public Arc( double x, double y, double radius, double angle ) {
|
||||
this(x, y, radius, radius, angle, 0.0);
|
||||
}
|
||||
|
||||
public Arc( double x, double y, double radius, double angle, double startingangle ) {
|
||||
this(x, y, radius, radius, angle, startingangle);
|
||||
}
|
||||
|
||||
public Arc( double x, double y, double width, double height, double angle, double startingangle ) {
|
||||
super(x, y);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.angle = angle;
|
||||
this.startingangle = startingangle;
|
||||
setAnchor(CENTER);
|
||||
|
||||
noFill();
|
||||
}
|
||||
|
||||
public Arc( Arc arc ) {
|
||||
this(arc.x, arc.y, arc.width, arc.height, arc.angle, arc.startingangle);
|
||||
copyFrom(arc);
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth( double width ) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight( double height ) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public double getAngle() {
|
||||
return angle;
|
||||
}
|
||||
|
||||
public void setAngle( double angle ) {
|
||||
this.angle = angle;
|
||||
}
|
||||
|
||||
public double getStartingangle() {
|
||||
return startingangle;
|
||||
}
|
||||
|
||||
public void setStartingangle( double startingangle ) {
|
||||
this.startingangle = startingangle;
|
||||
}
|
||||
|
||||
public Options.PathType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType( Options.PathType type ) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape copy() {
|
||||
return new Arc(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Arc ) {
|
||||
Arc arc = (Arc) shape;
|
||||
width = arc.width;
|
||||
height = arc.height;
|
||||
angle = arc.angle;
|
||||
startingangle = arc.startingangle;
|
||||
type = arc.type;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new Arc2D.Double(0, 0, width, height, startingangle, angle, type.awt_type);
|
||||
}
|
||||
|
||||
}
|
||||
178
src/main/java/schule/ngb/zm/shapes/Arrow.java
Normal file
178
src/main/java/schule/ngb/zm/shapes/Arrow.java
Normal file
@@ -0,0 +1,178 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.Vector;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
|
||||
public class Arrow extends Line {
|
||||
|
||||
// Spitzenarten
|
||||
public static final int OPEN = 101;
|
||||
|
||||
public static final int CLOSED = 102;
|
||||
|
||||
private static final double BASE_HEADSIZE = 5.0;
|
||||
|
||||
protected Options.ArrowHead arrowhead = LINES;
|
||||
|
||||
protected double headsize = 1.0;
|
||||
|
||||
public Arrow( double x1, double y1, double x2, double y2 ) {
|
||||
super(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen Pfeil, der den übergebenen Vektor darstellt.
|
||||
*
|
||||
* @param vector
|
||||
*/
|
||||
public Arrow( Vector vector ) {
|
||||
this(0, 0, vector.x, vector.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen Pfeil, der den Differenzvektor zwischen den übergebenen
|
||||
* Vektoren darstellt.
|
||||
*
|
||||
* @param vector1
|
||||
* @param vector2
|
||||
*/
|
||||
public Arrow( Vector vector1, Vector vector2 ) {
|
||||
this(vector1.x, vector1.y, vector2.x, vector2.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen Pfeil, der den übergebenen Vektor um die angegeben
|
||||
* Koordinaten verschoben darstellt.
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param vector
|
||||
*/
|
||||
public Arrow( double x, double y, Vector vector ) {
|
||||
this(x, y, x + vector.x, y + vector.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen Pfeil als Kopie einer vorgegebenen Linie.
|
||||
*
|
||||
* @param line
|
||||
*/
|
||||
public Arrow( Line line ) {
|
||||
this(line.x, line.y, line.x2, line.y2);
|
||||
this.copyFrom(line);
|
||||
}
|
||||
|
||||
public double getHeadsize() {
|
||||
return headsize;
|
||||
}
|
||||
|
||||
public void setHeadsize( double headsize ) {
|
||||
this.headsize = headsize;
|
||||
}
|
||||
|
||||
public Options.ArrowHead getArrowhead() {
|
||||
return arrowhead;
|
||||
}
|
||||
|
||||
public void setArrowhead( Options.ArrowHead arrowhead ) {
|
||||
this.arrowhead = arrowhead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kopiert die Werte des angegebenen Vektors.
|
||||
*
|
||||
* @param vector
|
||||
*/
|
||||
public void copyFrom( Vector vector ) {
|
||||
if( vector != null ) {
|
||||
x2 = x + vector.x;
|
||||
y2 = y + vector.y;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Arrow ) {
|
||||
Arrow pArrow = (Arrow) shape;
|
||||
headsize = pArrow.headsize;
|
||||
arrowhead = pArrow.arrowhead;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Arrow copy() {
|
||||
return new Arrow(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
/*Path2D.Double gruppe = new Path2D.Double();
|
||||
gruppe.append(super.getShape(), false);
|
||||
gruppe.append(getPfeilspitze(), false);
|
||||
|
||||
return gruppe;*/
|
||||
return super.getShape();
|
||||
}
|
||||
|
||||
protected java.awt.Shape getHeadShape() {
|
||||
AffineTransform af;
|
||||
switch( arrowhead ) {
|
||||
case LINES:
|
||||
double len = BASE_HEADSIZE * headsize;
|
||||
Path2D.Double sOPEN = new Path2D.Double();
|
||||
sOPEN.moveTo(-len, -len);
|
||||
sOPEN.lineTo(0, 0);
|
||||
sOPEN.lineTo(-len, len);
|
||||
|
||||
af = new AffineTransform();
|
||||
af.translate(x2-x, y2-y);
|
||||
af.rotate(Math.atan2(y2 - y, x2 - x));
|
||||
return af.createTransformedShape(sOPEN);
|
||||
case FILLED:
|
||||
default:
|
||||
int ix = (int) x2, iy = (int) y2, pg = (int) (BASE_HEADSIZE * headsize);
|
||||
java.awt.Polygon sCLOSED = new java.awt.Polygon(
|
||||
new int[]{0, -pg, -pg},
|
||||
new int[]{0, -pg, pg},
|
||||
3
|
||||
);
|
||||
|
||||
af = new AffineTransform();
|
||||
af.translate(x2-x, y2-y);
|
||||
af.rotate(Math.atan2(y2 - y, x2 - x));
|
||||
return af.createTransformedShape(sCLOSED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
if( !visible ) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.draw(graphics, transform);
|
||||
|
||||
java.awt.Shape head = getHeadShape();
|
||||
if( transform != null ) {
|
||||
head = transform.createTransformedShape(head);
|
||||
}
|
||||
|
||||
Color currentColor = graphics.getColor();
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(new BasicStroke((float) strokeWeight));
|
||||
if( arrowhead == FILLED ) {
|
||||
graphics.fill(head);
|
||||
} else {
|
||||
graphics.draw(head);
|
||||
}
|
||||
}
|
||||
graphics.setColor(currentColor);
|
||||
}
|
||||
|
||||
}
|
||||
30
src/main/java/schule/ngb/zm/shapes/Bounds.java
Normal file
30
src/main/java/schule/ngb/zm/shapes/Bounds.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
public class Bounds extends Rectangle2D.Double {
|
||||
|
||||
public Bounds( double x, double y, double w, double h ) {
|
||||
super(x,y,w,h);
|
||||
}
|
||||
|
||||
public Bounds( Shape shape ) {
|
||||
if( shape != null ) {
|
||||
java.awt.Shape s = shape.getShape();
|
||||
if( s != null ) {
|
||||
s = shape.getTransform().createTransformedShape(s);
|
||||
Rectangle2D bounds = s.getBounds2D();
|
||||
x = bounds.getX();
|
||||
y = bounds.getY();
|
||||
width = bounds.getWidth();
|
||||
height = bounds.getHeight();
|
||||
} else {
|
||||
x = shape.getX();
|
||||
y = shape.getY();
|
||||
width = 0;
|
||||
height = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
81
src/main/java/schule/ngb/zm/shapes/Circle.java
Normal file
81
src/main/java/schule/ngb/zm/shapes/Circle.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Drawable;
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.geom.Ellipse2D;
|
||||
|
||||
public class Circle extends Shape implements Drawable {
|
||||
|
||||
protected double radius;
|
||||
|
||||
public Circle( double x, double y, double radius ) {
|
||||
super(x, y);
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
public Circle( Circle circle ) {
|
||||
this(circle.x, circle.y, circle.radius);
|
||||
copyFrom(circle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return radius+radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return radius+radius;
|
||||
}
|
||||
|
||||
public double getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
||||
public void setRadius( double radius ) {
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
radius *= factor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Circle ) {
|
||||
radius = ((Circle) shape).radius;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Circle copy() {
|
||||
return new Circle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new Ellipse2D.Double(0, 0, radius + radius, radius + radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object o ) {
|
||||
if( this == o ) return true;
|
||||
if( o == null || getClass() != o.getClass() ) return false;
|
||||
Circle pCircle = (Circle) o;
|
||||
return super.equals(o) && Double.compare(pCircle.radius, radius) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getCanonicalName() + "[" +
|
||||
"x=" + x +
|
||||
",y=" + y +
|
||||
",radius=" + radius +
|
||||
']';
|
||||
}
|
||||
|
||||
}
|
||||
195
src/main/java/schule/ngb/zm/shapes/Curve.java
Normal file
195
src/main/java/schule/ngb/zm/shapes/Curve.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import java.awt.geom.CubicCurve2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.QuadCurve2D;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Curve extends Shape {
|
||||
|
||||
protected double[] coordinates;
|
||||
|
||||
public Curve( double x, double y, double cx, double cy, double x2, double y2 ) {
|
||||
super(x, y);
|
||||
|
||||
coordinates = new double[]{
|
||||
cx, cy, x2, y2
|
||||
};
|
||||
|
||||
noFill();
|
||||
}
|
||||
|
||||
public Curve( double x, double y, double cx1, double cy1, double cx2, double cy2, double x2, double y2 ) {
|
||||
super(x, y);
|
||||
|
||||
coordinates = new double[]{
|
||||
cx1, cy1, cx2, cy2, x2, y2
|
||||
};
|
||||
|
||||
noFill();
|
||||
}
|
||||
|
||||
public Curve( Curve curve ) {
|
||||
super(curve.x, curve.y);
|
||||
coordinates = Arrays.copyOf(curve.coordinates, curve.coordinates.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return Math.abs(x-coordinates[coordinates.length-2]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return Math.abs(y-coordinates[coordinates.length-1]);
|
||||
}
|
||||
|
||||
public Point2D getStart() {
|
||||
return new Point2D.Double(x, y);
|
||||
}
|
||||
|
||||
public void setStart( double x, double y ) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Point2D getEnd() {
|
||||
return new Point2D.Double(
|
||||
coordinates[coordinates.length - 2],
|
||||
coordinates[coordinates.length - 1]
|
||||
);
|
||||
}
|
||||
|
||||
public void setEnd( double x, double y ) {
|
||||
coordinates[coordinates.length - 2] = x;
|
||||
coordinates[coordinates.length - 1] = y;
|
||||
}
|
||||
|
||||
public Point2D getControl1() {
|
||||
return new Point2D.Double(
|
||||
coordinates[0],
|
||||
coordinates[1]
|
||||
);
|
||||
}
|
||||
|
||||
public void setControl1( double x, double y ) {
|
||||
coordinates[0] = x;
|
||||
coordinates[1] = y;
|
||||
}
|
||||
|
||||
public Point2D getControl2() {
|
||||
return new Point2D.Double(
|
||||
coordinates[coordinates.length - 4],
|
||||
coordinates[coordinates.length - 3]
|
||||
);
|
||||
}
|
||||
|
||||
public void setControl2( double x, double y ) {
|
||||
coordinates[coordinates.length - 4] = x;
|
||||
coordinates[coordinates.length - 3] = y;
|
||||
}
|
||||
|
||||
public void setPoints( double x, double y, double cx, double cy, double x2, double y2 ) {
|
||||
setStart(x, y);
|
||||
coordinates = coordinates = new double[]{
|
||||
cx, cy, x2, y2
|
||||
};
|
||||
}
|
||||
|
||||
public void setPoints( double x, double y, double cx1, double cy1, double cx2, double cy2, double x2, double y2 ) {
|
||||
setStart(x, y);
|
||||
coordinates = new double[]{
|
||||
cx1, cy1, cx2, cy2, x2, y2
|
||||
};
|
||||
}
|
||||
|
||||
public boolean isCubic() {
|
||||
return coordinates.length == 6;
|
||||
}
|
||||
|
||||
public boolean isQuad() {
|
||||
return coordinates.length == 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape copy() {
|
||||
return new Curve(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Curve ) {
|
||||
Curve k = (Curve) shape;
|
||||
coordinates = Arrays.copyOf(k.coordinates, k.coordinates.length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
if( isCubic() ) {
|
||||
return new CubicCurve2D.Double(
|
||||
0, 0,
|
||||
coordinates[0] - x, coordinates[1] - y,
|
||||
coordinates[2] - x, coordinates[3] - y,
|
||||
coordinates[4] - x, coordinates[5] - y
|
||||
);
|
||||
} else {
|
||||
return new QuadCurve2D.Double(
|
||||
0, 0,
|
||||
coordinates[0] - x, coordinates[1] - y,
|
||||
coordinates[2] - x, coordinates[3] - y
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
/*for( int i = 0; i < coordinates.length; i++ ) {
|
||||
coordinates[i] *= factor;
|
||||
}*/
|
||||
coordinates[coordinates.length - 4] *= factor;
|
||||
coordinates[coordinates.length - 3] *= factor;
|
||||
coordinates[coordinates.length - 2] *= factor;
|
||||
coordinates[coordinates.length - 1] *= factor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move( double dx, double dy ) {
|
||||
super.move(dx, dy);
|
||||
for( int i = 0; i < coordinates.length; i += 2 ) {
|
||||
coordinates[i] = coordinates[i] + dx;
|
||||
coordinates[i + 1] = coordinates[i + 1] + dy;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo( double x, double y ) {
|
||||
double dx = x - x, dy = y - y;
|
||||
move(dx, dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object o ) {
|
||||
if( this == o ) return true;
|
||||
if( o == null || getClass() != o.getClass() ) return false;
|
||||
Curve pCurve = (Curve) o;
|
||||
return super.equals(o) &&
|
||||
getStart().equals(pCurve.getStart()) &&
|
||||
getControl1().equals(pCurve.getControl1()) &&
|
||||
getControl2().equals(pCurve.getControl2()) &&
|
||||
getEnd().equals(pCurve.getEnd());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getCanonicalName() + "[" +
|
||||
"x=" + x +
|
||||
",y=" + y +
|
||||
"x2=" + coordinates[coordinates.length - 2] +
|
||||
",y2=" + coordinates[coordinates.length - 1] +
|
||||
']';
|
||||
}
|
||||
|
||||
}
|
||||
75
src/main/java/schule/ngb/zm/shapes/CustomShape.java
Normal file
75
src/main/java/schule/ngb/zm/shapes/CustomShape.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import java.awt.geom.Path2D;
|
||||
|
||||
public class CustomShape extends Shape {
|
||||
|
||||
protected Path2D.Double path;
|
||||
|
||||
private double width = 0.0, height = 0.0;
|
||||
|
||||
public CustomShape( double x, double y ) {
|
||||
super(x, y);
|
||||
path = new Path2D.Double();
|
||||
}
|
||||
|
||||
public CustomShape( CustomShape custom ) {
|
||||
super(custom.x, custom.y);
|
||||
path = custom.path;
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
private void calculateBounds() {
|
||||
java.awt.Rectangle s = path.getBounds();
|
||||
width = s.width;
|
||||
height = s.height;
|
||||
}
|
||||
|
||||
public void lineTo( double x, double y ) {
|
||||
path.lineTo(x - x, y - y);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public void arcTo( double x1, double y1, double x2, double y2 ) {
|
||||
path.quadTo(x1 - x, y1 - y, x2 - x, y2 - y);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public void curveTo( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
||||
path.curveTo(x1 - x, y1 - y, x2 - x, y2 - y, x3 - x, y3 - y);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
path.lineTo(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape copy() {
|
||||
return new CustomShape(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof CustomShape ) {
|
||||
path = new Path2D.Double(path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new Path2D.Double(path);
|
||||
}
|
||||
|
||||
}
|
||||
90
src/main/java/schule/ngb/zm/shapes/Ellipse.java
Normal file
90
src/main/java/schule/ngb/zm/shapes/Ellipse.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.geom.Ellipse2D;
|
||||
|
||||
public class Ellipse extends Shape {
|
||||
|
||||
protected double width;
|
||||
|
||||
protected double height;
|
||||
|
||||
public Ellipse( double x, double y, double width, double height ) {
|
||||
super(x, y);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.anchor = Options.Direction.CENTER;
|
||||
}
|
||||
|
||||
public Ellipse( Ellipse ellipse ) {
|
||||
this(ellipse.x, ellipse.y, ellipse.width, ellipse.height);
|
||||
copyFrom(ellipse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setWidth( double width ) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public void setHeight( double height ) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Ellipse ) {
|
||||
Ellipse e = (Ellipse) shape;
|
||||
this.width = e.width;
|
||||
this.height = e.height;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ellipse copy() {
|
||||
return new Ellipse(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
width *= factor;
|
||||
height *= factor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new Ellipse2D.Double(0, 0, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object o ) {
|
||||
if( this == o ) return true;
|
||||
if( o == null || getClass() != o.getClass() ) return false;
|
||||
Ellipse ellipse = (Ellipse) o;
|
||||
return super.equals(o) &&
|
||||
Double.compare(ellipse.width, width) == 0 &&
|
||||
Double.compare(ellipse.height, height) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getCanonicalName() + '[' +
|
||||
"width=" + width +
|
||||
",height=" + height +
|
||||
",x=" + x +
|
||||
",y=" + y +
|
||||
']';
|
||||
}
|
||||
|
||||
}
|
||||
45
src/main/java/schule/ngb/zm/shapes/FilledShape.java
Normal file
45
src/main/java/schule/ngb/zm/shapes/FilledShape.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
public abstract class FilledShape extends StrokedShape {
|
||||
|
||||
protected Color fillColor = STD_FILLCOLOR;
|
||||
|
||||
public Color getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
public void setFillColor( Color color ) {
|
||||
fillColor = color;
|
||||
}
|
||||
|
||||
public void setFillColor( Color color, int alpha ) {
|
||||
setFillColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
public void setFillColor( int gray ) {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
setFillColor(null);
|
||||
}
|
||||
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
setFillColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
setFillColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void resetFill() {
|
||||
setFillColor(STD_FILLCOLOR);
|
||||
}
|
||||
|
||||
}
|
||||
115
src/main/java/schule/ngb/zm/shapes/Kite.java
Normal file
115
src/main/java/schule/ngb/zm/shapes/Kite.java
Normal file
@@ -0,0 +1,115 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.geom.Path2D;
|
||||
|
||||
public class Kite extends Shape {
|
||||
|
||||
protected double width;
|
||||
|
||||
protected double height;
|
||||
|
||||
protected double ratio;
|
||||
|
||||
public Kite( double x, double y, double width, double height ) {
|
||||
this(x, y, width, height, 0.5);
|
||||
}
|
||||
|
||||
public Kite( double x, double y, double width, double height, double ratio ) {
|
||||
super(x, y);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.ratio = ratio;
|
||||
this.anchor = Options.Direction.CENTER;
|
||||
}
|
||||
|
||||
public Kite( Kite pKite ) {
|
||||
this(pKite.x, pKite.y, pKite.width, pKite.height, pKite.ratio);
|
||||
copyFrom(pKite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth( double width ) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight( double height ) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public double getRatio() {
|
||||
return ratio;
|
||||
}
|
||||
|
||||
public void setRatio( double ratio ) {
|
||||
this.ratio = ratio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Kite ) {
|
||||
Kite d = (Kite) shape;
|
||||
width = d.width;
|
||||
height = d.height;
|
||||
ratio = d.ratio;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kite copy() {
|
||||
return new Kite(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
width *= factor;
|
||||
height *= factor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
double hHalf = width * .5, hRatio = ratio * height;
|
||||
Path2D shape = new Path2D.Double();
|
||||
shape.moveTo(hHalf, 0);
|
||||
shape.lineTo(width, hRatio);
|
||||
shape.lineTo(hHalf, height);
|
||||
shape.lineTo(0, hRatio);
|
||||
shape.closePath();
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object o ) {
|
||||
if( this == o ) return true;
|
||||
if( o == null || getClass() != o.getClass() ) return false;
|
||||
Kite d = (Kite) o;
|
||||
return super.equals(o) &&
|
||||
Double.compare(d.width, width) == 0 &&
|
||||
Double.compare(d.height, height) == 0 &&
|
||||
Double.compare(d.ratio, ratio) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getCanonicalName() + '[' +
|
||||
"width=" + width +
|
||||
",height=" + height +
|
||||
",verhaeltnis=" + ratio +
|
||||
",x=" + x +
|
||||
",y=" + y +
|
||||
']';
|
||||
}
|
||||
|
||||
}
|
||||
118
src/main/java/schule/ngb/zm/shapes/Line.java
Normal file
118
src/main/java/schule/ngb/zm/shapes/Line.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import java.awt.geom.Line2D;
|
||||
|
||||
public class Line extends Shape {
|
||||
|
||||
protected double x2;
|
||||
|
||||
protected double y2;
|
||||
|
||||
public Line(double x1, double y1, double x2, double y2) {
|
||||
super(x1, y1);
|
||||
this.x2 = x2;
|
||||
this.y2 = y2;
|
||||
}
|
||||
|
||||
public Line(Line line) {
|
||||
this(line.x, line.y, line.x2, line.y2);
|
||||
copyFrom(line);
|
||||
}
|
||||
|
||||
public double getX2() {
|
||||
return x2;
|
||||
}
|
||||
|
||||
public void setX2(double x) {
|
||||
this.x2 = x;
|
||||
}
|
||||
|
||||
public double getY2() {
|
||||
return y2;
|
||||
}
|
||||
|
||||
public void setY2(double y) {
|
||||
this.y2 = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return Math.abs(x2 - x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return Math.abs(y2 - y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bounds getBounds() {
|
||||
return new Bounds(Math.min(x, x2), Math.min(y, y2), getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale(double factor) {
|
||||
super.scale(factor);
|
||||
x2 *= factor;
|
||||
y2 *= factor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom(Shape shape) {
|
||||
super.copyFrom(shape);
|
||||
if (shape instanceof Line) {
|
||||
Line pLine = (Line) shape;
|
||||
x2 = pLine.x2;
|
||||
y2 = pLine.y2;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Line copy() {
|
||||
return new Line(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move(double dx, double dy) {
|
||||
super.move(dx, dy);
|
||||
x2 += dx;
|
||||
y2 += dx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(double x, double y) {
|
||||
double dx = x2 - this.x;
|
||||
double dy = y2 - this.y;
|
||||
super.moveTo(x, y);
|
||||
x2 += dx;
|
||||
y2 += dy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new Line2D.Double(0, 0, x2 - x, y2 - y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
Line pLine = (Line) o;
|
||||
return super.equals(o) &&
|
||||
Double.compare(pLine.x2, x2) == 0 &&
|
||||
Double.compare(pLine.y2, y2) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getCanonicalName() + "[" +
|
||||
"x1=" + x +
|
||||
",y1=" + y +
|
||||
",x2=" + x2 +
|
||||
",y2=" + y2 +
|
||||
']';
|
||||
}
|
||||
|
||||
}
|
||||
169
src/main/java/schule/ngb/zm/shapes/Picture.java
Normal file
169
src/main/java/schule/ngb/zm/shapes/Picture.java
Normal file
@@ -0,0 +1,169 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class Picture extends Shape {
|
||||
|
||||
private BufferedImage image;
|
||||
|
||||
private double imgWidth;
|
||||
|
||||
private double imgHeight;
|
||||
|
||||
public Picture( String source ) {
|
||||
this(0, 0, source);
|
||||
}
|
||||
|
||||
public Picture( double x, double y, String source ) {
|
||||
super(x, y);
|
||||
image = ImageLoader.loadImage(source);
|
||||
|
||||
if( image == null ) {
|
||||
throw new IllegalArgumentException("Could not initialize image from source " + source);
|
||||
}
|
||||
|
||||
imgWidth = image.getWidth();
|
||||
imgHeight = image.getHeight();
|
||||
this.anchor = Options.Direction.CENTER;
|
||||
}
|
||||
|
||||
public Picture( Picture picture ) {
|
||||
super(picture.getX(), picture.getY());
|
||||
copyFrom(picture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape copy() {
|
||||
return new Picture(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Picture ) {
|
||||
Picture pic = (Picture) shape;
|
||||
image = new BufferedImage(pic.image.getWidth(), pic.image.getHeight(), pic.image.getType());
|
||||
Graphics2D g = image.createGraphics();
|
||||
g.drawImage(pic.image, 0, 0, null);
|
||||
g.dispose();
|
||||
|
||||
imgWidth = image.getWidth();
|
||||
imgHeight = image.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return imgWidth;
|
||||
}
|
||||
|
||||
public void setWidth( double width ) {
|
||||
scale(width / this.imgWidth);
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return imgHeight;
|
||||
}
|
||||
|
||||
public void setHeight( double height ) {
|
||||
scale(height / this.imgHeight);
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
imgWidth *= factor;
|
||||
imgHeight *= factor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new Rectangle2D.Double(0, 0, imgWidth, imgHeight);
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public AffineTransform getVerzerrung() {
|
||||
AffineTransform verzerrung = new AffineTransform();
|
||||
verzerrung.translate(x, y);
|
||||
verzerrung.scale(skalierung, skalierung);
|
||||
verzerrung.rotate(Math.toRadians(drehwinkel));
|
||||
verzerrung.translate(-anker.x, -anker.y);
|
||||
return verzerrung;
|
||||
}
|
||||
*/
|
||||
|
||||
public void flip( Options.Direction dir ) {
|
||||
int w = image.getWidth();
|
||||
int h = image.getHeight();
|
||||
|
||||
BufferedImage new_image = ImageLoader.createImage(w, h, image.getColorModel().getTransparency());
|
||||
Graphics2D g = new_image.createGraphics();
|
||||
if( dir.contains(LEFT) || dir.contains(RIGHT) ) {
|
||||
g.drawImage(image, 0, 0, w, h, w, 0, 0, h, null);
|
||||
} else {
|
||||
g.drawImage(image, 0, 0, w, h, 0, h, w, 0, null);
|
||||
}
|
||||
g.dispose();
|
||||
image = new_image;
|
||||
}
|
||||
|
||||
public void tint( Color tintColor ) {
|
||||
BufferedImage mask = generateMask(tintColor, 0.5);
|
||||
|
||||
int imgWidth = image.getWidth();
|
||||
int imgHeight = image.getHeight();
|
||||
|
||||
BufferedImage new_image = ImageLoader.createImage(imgWidth, imgHeight, image.getColorModel().getTransparency());
|
||||
Graphics2D g2 = new_image.createGraphics();
|
||||
g2.drawImage(image, 0, 0, null);
|
||||
g2.drawImage(mask, 0, 0, null);
|
||||
g2.dispose();
|
||||
|
||||
image = new_image;
|
||||
}
|
||||
|
||||
private BufferedImage generateMask( Color color, double alpha ) {
|
||||
int imgWidth = image.getWidth();
|
||||
int imgHeight = image.getHeight();
|
||||
|
||||
BufferedImage imgMask = ImageLoader.createImage(imgWidth, imgHeight, Transparency.TRANSLUCENT);
|
||||
imgMask.coerceData(true);
|
||||
|
||||
Graphics2D g2 = imgMask.createGraphics();
|
||||
|
||||
g2.drawImage(image, 0, 0, null);
|
||||
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, (float)alpha));
|
||||
g2.setColor(color.getJavaColor());
|
||||
|
||||
g2.fillRect(0, 0, imgWidth, imgHeight);
|
||||
g2.dispose();
|
||||
|
||||
return imgMask;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
if( !visible ) {
|
||||
return;
|
||||
}
|
||||
|
||||
AffineTransform current = graphics.getTransform();
|
||||
graphics.transform(transform);
|
||||
graphics.drawImage(image, 0, 0, (int) imgWidth, (int) imgHeight, null);
|
||||
graphics.setTransform(current);
|
||||
}
|
||||
|
||||
}
|
||||
58
src/main/java/schule/ngb/zm/shapes/Point.java
Normal file
58
src/main/java/schule/ngb/zm/shapes/Point.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
public class Point extends Circle {
|
||||
|
||||
private static final double POINT_RADIUS = 2.0;
|
||||
|
||||
/**
|
||||
* Erstellt ein Punktobjekt mit den angegebenen Koordinaten.
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
public Point( double x, double y ) {
|
||||
super(x, y, POINT_RADIUS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein Punktobjekt mit den Koordinaten des übergebenen
|
||||
* {@link schule.ngb.zm.Vector Vektors}.
|
||||
* @param vector
|
||||
*/
|
||||
public Point( schule.ngb.zm.Vector vector ) {
|
||||
super(vector.x, vector.y, POINT_RADIUS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein Punktobjekt mit den Koordinaten des übergebenen
|
||||
* {@link java.awt.geom.Point2D Punktes}.
|
||||
* @param point
|
||||
*/
|
||||
public Point( Point2D point ) {
|
||||
super(point.getX(), point.getY(), POINT_RADIUS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein Punktobjekt mit den Koordinaten der angegeben Form.
|
||||
* @param shape
|
||||
*/
|
||||
public Point( Shape shape ) {
|
||||
super(shape.getX(), shape.getY(), POINT_RADIUS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
// Skalierung ist für Punkte deaktiviert
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnchor( Options.Direction anchor ) {
|
||||
// Punkte sind immer im Zentrum verankert
|
||||
super.setAnchor(Options.Direction.CENTER);
|
||||
}
|
||||
|
||||
}
|
||||
107
src/main/java/schule/ngb/zm/shapes/Polygon.java
Normal file
107
src/main/java/schule/ngb/zm/shapes/Polygon.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
public class Polygon extends Shape {
|
||||
|
||||
protected Point2D[] points;
|
||||
|
||||
private double width = 0.0, height = 0.0;
|
||||
|
||||
public Polygon( double x, double y, Point2D... points ) {
|
||||
super(x, y);
|
||||
|
||||
this.points = new Point2D[points.length];
|
||||
for( int i = 0; i < points.length; i++ ) {
|
||||
this.points[i] = new Point2D.Double(points[i].getX() - x, points[i].getY() - y);
|
||||
}
|
||||
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public Polygon( Point2D... points ) {
|
||||
this.points = new Point2D[points.length];
|
||||
for( int i = 0; i < points.length; i++ ) {
|
||||
if( i == 0 ) {
|
||||
x = points[i].getX();
|
||||
y = points[i].getY();
|
||||
}
|
||||
this.points[i] = new Point2D.Double(points[i].getX() - x, points[i].getY() - y);
|
||||
}
|
||||
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public Polygon( double x1, double y1, double x2, double y2, double... coordinates ) {
|
||||
super(x1, y1);
|
||||
|
||||
int numPoints = coordinates.length / 2 + 2;
|
||||
|
||||
points = new Point2D[numPoints];
|
||||
this.points[0] = new Point2D.Double(x1, y1);
|
||||
this.points[1] = new Point2D.Double(x2 - x1, y2 - y1);
|
||||
for( int i = 0; i < numPoints - 2; i += 1 ) {
|
||||
this.points[i + 2] = new Point2D.Double(coordinates[i * 2] - x1, coordinates[i * 2 + 1] - y1);
|
||||
}
|
||||
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public Polygon( Polygon polygon ) {
|
||||
super(polygon.x, polygon.y);
|
||||
copyFrom(polygon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
private void calculateBounds() {
|
||||
java.awt.Rectangle s = getShape().getBounds();
|
||||
width = s.width;
|
||||
height = s.height;
|
||||
}
|
||||
|
||||
public Point2D[] getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Polygon ) {
|
||||
Polygon v = (Polygon) shape;
|
||||
|
||||
points = new Point2D[v.points.length];
|
||||
for( int i = 0; i < v.points.length; i++ ) {
|
||||
points[i] = new Point2D.Double(v.points[i].getX(), v.points[i].getY());
|
||||
}
|
||||
}
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape copy() {
|
||||
return new Polygon(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
Path2D shape = new Path2D.Double();
|
||||
//shape.moveTo(points[0].getX(), points[0].getY());
|
||||
shape.moveTo(0, 0);
|
||||
for( int i = 1; i < points.length; i++ ) {
|
||||
shape.lineTo(points[i].getX(), points[i].getY());
|
||||
}
|
||||
shape.closePath();
|
||||
return shape;
|
||||
}
|
||||
|
||||
}
|
||||
36
src/main/java/schule/ngb/zm/shapes/Quad.java
Normal file
36
src/main/java/schule/ngb/zm/shapes/Quad.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Quad extends Polygon {
|
||||
|
||||
public Quad( double x, double y, Point2D... points ) {
|
||||
super(x, y, Arrays.copyOf(points, 4));
|
||||
if( points.length < 4 ) {
|
||||
throw new IllegalArgumentException("A quadrilateral requires exactly four points.");
|
||||
}
|
||||
}
|
||||
|
||||
public Quad( Point2D... points ) {
|
||||
super(Arrays.copyOf(points, 4));
|
||||
if( points.length < 4 ) {
|
||||
throw new IllegalArgumentException("A quadrilateral requires exactly four points.");
|
||||
}
|
||||
}
|
||||
|
||||
public Quad( double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4 ) {
|
||||
super(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
}
|
||||
|
||||
public Quad( Quad pViereck ) {
|
||||
super(pViereck.x, pViereck.y);
|
||||
copyFrom(pViereck);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape copy() {
|
||||
return new Quad(this);
|
||||
}
|
||||
|
||||
}
|
||||
104
src/main/java/schule/ngb/zm/shapes/Rectangle.java
Normal file
104
src/main/java/schule/ngb/zm/shapes/Rectangle.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
public class Rectangle extends Shape {
|
||||
|
||||
protected double width;
|
||||
|
||||
protected double height;
|
||||
|
||||
public Rectangle( double x, double y, double width, double height ) {
|
||||
super(x, y);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.anchor = Options.Direction.NORTHWEST;
|
||||
}
|
||||
|
||||
public Rectangle( Rectangle pRechteck ) {
|
||||
this(
|
||||
pRechteck.x, pRechteck.y,
|
||||
pRechteck.width, pRechteck.height);
|
||||
copyFrom(pRechteck);
|
||||
}
|
||||
|
||||
public Rectangle( Shape pShape ) {
|
||||
java.awt.Shape s = pShape.getShape();
|
||||
s = pShape.getTransform().createTransformedShape(s);
|
||||
Rectangle2D bounds = s.getBounds2D();
|
||||
x = bounds.getX();
|
||||
y = bounds.getY();
|
||||
width = bounds.getWidth();
|
||||
height = bounds.getHeight();
|
||||
fillColor = null;
|
||||
strokeType = DASHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setWidth( double width ) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public void setHeight( double height ) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle copy() {
|
||||
return new Rectangle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Rectangle ) {
|
||||
Rectangle rechteck = (Rectangle) shape;
|
||||
width = rechteck.width;
|
||||
height = rechteck.height;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
width *= factor;
|
||||
height *= factor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new Rectangle2D.Double(0, 0, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object o ) {
|
||||
if( this == o ) return true;
|
||||
if( o == null || getClass() != o.getClass() ) return false;
|
||||
Rectangle rechteck = (Rectangle) o;
|
||||
return super.equals(o) &&
|
||||
Double.compare(rechteck.width, width) == 0 &&
|
||||
Double.compare(rechteck.height, height) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getCanonicalName() + "[" +
|
||||
"x=" + x +
|
||||
",y=" + y +
|
||||
",width=" + width +
|
||||
",height=" + height +
|
||||
']';
|
||||
}
|
||||
|
||||
}
|
||||
31
src/main/java/schule/ngb/zm/shapes/Rhombus.java
Normal file
31
src/main/java/schule/ngb/zm/shapes/Rhombus.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
public class Rhombus extends Kite {
|
||||
|
||||
public Rhombus( double x, double y, double width, double height ) {
|
||||
super(x, y, width, height, 0.5);
|
||||
}
|
||||
|
||||
public Rhombus( Rhombus rhombus ) {
|
||||
this(rhombus.x, rhombus.y, rhombus.width, rhombus.height);
|
||||
this.copyFrom(rhombus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRatio( double ratio ) {
|
||||
// Für eine Raute ist das Verhältnis immer 50/5ß
|
||||
super.setRatio(0.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
super.setRatio(0.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kite copy() {
|
||||
return new Kite(this);
|
||||
}
|
||||
|
||||
}
|
||||
57
src/main/java/schule/ngb/zm/shapes/RoundedRectangle.java
Normal file
57
src/main/java/schule/ngb/zm/shapes/RoundedRectangle.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
public class RoundedRectangle extends Rectangle {
|
||||
|
||||
protected double borderRadius = 1.0;
|
||||
|
||||
public RoundedRectangle( double x, double y, double width, double height, double borderRadius ) {
|
||||
super(x, y, width, height);
|
||||
this.borderRadius = borderRadius;
|
||||
}
|
||||
|
||||
public RoundedRectangle( Rectangle pRechteck ) {
|
||||
super(
|
||||
pRechteck.x, pRechteck.y,
|
||||
pRechteck.width, pRechteck.height);
|
||||
copyFrom(pRechteck);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof RoundedRectangle ) {
|
||||
RoundedRectangle rechteck = (RoundedRectangle) shape;
|
||||
borderRadius = rechteck.borderRadius;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new RoundRectangle2D.Double(
|
||||
0, 0, width, height, borderRadius, borderRadius
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object o ) {
|
||||
if( this == o ) return true;
|
||||
if( o == null || getClass() != o.getClass() ) return false;
|
||||
RoundedRectangle rechteck = (RoundedRectangle) o;
|
||||
return super.equals(o) &&
|
||||
Double.compare(rechteck.borderRadius, borderRadius) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getCanonicalName() + "[" +
|
||||
"x=" + x +
|
||||
",y=" + y +
|
||||
",width=" + width +
|
||||
",height=" + height +
|
||||
",rundung=" + borderRadius +
|
||||
']';
|
||||
}
|
||||
|
||||
}
|
||||
417
src/main/java/schule/ngb/zm/shapes/Shape.java
Normal file
417
src/main/java/schule/ngb/zm/shapes/Shape.java
Normal file
@@ -0,0 +1,417 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
public abstract class Shape extends FilledShape {
|
||||
|
||||
protected double x;
|
||||
|
||||
protected double y;
|
||||
|
||||
protected double rotation = 0.0;
|
||||
|
||||
protected double scale = 1.0;
|
||||
|
||||
protected boolean visible = true;
|
||||
|
||||
protected Options.Direction anchor = Options.Direction.CENTER;
|
||||
|
||||
public Shape( double x, double y ) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Shape() {
|
||||
this(0.0, 0.0);
|
||||
}
|
||||
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX( double x ) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY( double y ) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Breite dieser Form zurück.
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract double getWidth();
|
||||
|
||||
/**
|
||||
* Gibt die Höhe dieser Form zurück.
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract double getHeight();
|
||||
|
||||
public double getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public double getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
public void toggle() {
|
||||
visible = !visible;
|
||||
}
|
||||
|
||||
public Options.Direction getAnchor() {
|
||||
return anchor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param anchor
|
||||
*/
|
||||
public void setAnchor( Options.Direction anchor ) {
|
||||
if( anchor != null ) {
|
||||
this.anchor = anchor;
|
||||
}
|
||||
}
|
||||
|
||||
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 zum gesetzten
|
||||
* {@link #setAnchor(Options.Direction) Ankerpunkt}.
|
||||
*
|
||||
* @param anchor Die Richtung des Ankerpunktes.
|
||||
* @return Der relative Ankerpunkt.
|
||||
*/
|
||||
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
|
||||
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
||||
|
||||
// anchor == CENTER
|
||||
Point2D.Double anchorPoint = new Point2D.Double(
|
||||
wHalf * (anchor.x - this.anchor.x),
|
||||
hHalf * (anchor.y - this.anchor.y)
|
||||
);
|
||||
|
||||
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
|
||||
* {@code super.copyFrom(shape)} sollten die Basiseigenschaften
|
||||
* kopiert werden.
|
||||
*
|
||||
* @param shape
|
||||
*/
|
||||
public void copyFrom( Shape shape ) {
|
||||
if( shape != null ) {
|
||||
moveTo(shape.x, shape.y);
|
||||
setFillColor(shape.getFillColor());
|
||||
setStrokeColor(shape.getStrokeColor());
|
||||
setStrokeWeight(shape.getStrokeWeight());
|
||||
setStrokeType(shape.getStrokeType());
|
||||
visible = shape.isVisible();
|
||||
rotation = shape.rotation;
|
||||
scale(shape.scale);
|
||||
setAnchor(shape.getAnchor());
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Shape copy();
|
||||
|
||||
public abstract java.awt.Shape getShape();
|
||||
|
||||
public Bounds getBounds() {
|
||||
return new Bounds(this);
|
||||
}
|
||||
|
||||
public void move( double dx, double dy ) {
|
||||
x += dx;
|
||||
y += dy;
|
||||
}
|
||||
|
||||
public void moveTo( double x, double y ) {
|
||||
this.x = x;
|
||||
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 dir
|
||||
* @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;
|
||||
}
|
||||
|
||||
public void scaleBy( double factor ) {
|
||||
scale(scale * factor);
|
||||
}
|
||||
|
||||
public void rotate( double angle ) {
|
||||
this.rotation += angle % 360;
|
||||
}
|
||||
|
||||
public void rotateTo( double angle ) {
|
||||
this.rotation = angle % 360;
|
||||
}
|
||||
|
||||
public void rotate( Point2D center, double angle ) {
|
||||
rotate(center.getX(), center.getY(), angle);
|
||||
}
|
||||
|
||||
public void rotate( double x, double y, double angle ) {
|
||||
this.rotation += angle % 360;
|
||||
|
||||
// Rotate x/y position
|
||||
double x1 = this.x - x, y1 = this.y - y;
|
||||
|
||||
double rad = Math.toRadians(angle);
|
||||
double x2 = x1 * Math.cos(rad) - y1 * Math.sin(rad);
|
||||
double y2 = x1 * Math.sin(rad) + y1 * Math.cos(rad);
|
||||
|
||||
this.x = x2 + x;
|
||||
this.y = y2 + y;
|
||||
}
|
||||
|
||||
/*public void shear( double dx, double dy ) {
|
||||
verzerrung.shear(dx, dy);
|
||||
}*/
|
||||
|
||||
public AffineTransform getTransform() {
|
||||
// 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(basePoint.x, basePoint.y);
|
||||
return transform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet die Form.
|
||||
*
|
||||
* @param graphics
|
||||
*/
|
||||
@Override
|
||||
public final void draw( Graphics2D graphics ) {
|
||||
draw(graphics, getTransform());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
if( !visible ) {
|
||||
return;
|
||||
}
|
||||
|
||||
java.awt.Shape shape = getShape();
|
||||
if( shape != null ) {
|
||||
if( transform != null ) {
|
||||
shape = transform.createTransformedShape(shape);
|
||||
}
|
||||
|
||||
Color currentColor = graphics.getColor();
|
||||
if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
graphics.setColor(fillColor.getJavaColor());
|
||||
graphics.fill(shape);
|
||||
}
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.draw(shape);
|
||||
}
|
||||
graphics.setColor(currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vergleicht die Form mit einem anderen Objekt. Handelt es sich bei dem
|
||||
* Objekt um eine andere Form, werden Position, Drehwinkel und Skalierung
|
||||
* verglichen. Unterklassen überschreiben die Methode, um weitere
|
||||
* Eigenschaften zu berücksichtigen.
|
||||
* <p>
|
||||
* Die Eigenschaften von {@link FilledShape} und {@link StrokedShape} werden
|
||||
* nicht verglichen.
|
||||
*
|
||||
* @param o Ein anderes Objekt.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean equals( Object o ) {
|
||||
if( this == o ) return true;
|
||||
if( o == null || getClass() != o.getClass() ) return false;
|
||||
Shape pShape = (Shape) o;
|
||||
return Double.compare(pShape.x, x) == 0 &&
|
||||
Double.compare(pShape.y, y) == 0 &&
|
||||
Double.compare(pShape.rotation, rotation) == 0 &&
|
||||
Double.compare(pShape.scale, scale) == 0;
|
||||
}
|
||||
|
||||
}
|
||||
185
src/main/java/schule/ngb/zm/shapes/ShapeGroup.java
Normal file
185
src/main/java/schule/ngb/zm/shapes/ShapeGroup.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
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;
|
||||
|
||||
private double groupWidth = -1.0;
|
||||
|
||||
private double groupHeight = -1.0;
|
||||
|
||||
public ShapeGroup() {
|
||||
super();
|
||||
shapes = new ArrayList<>(10);
|
||||
}
|
||||
|
||||
public ShapeGroup( double x, double y ) {
|
||||
super(x, y);
|
||||
shapes = new ArrayList<>(10);
|
||||
}
|
||||
|
||||
public ShapeGroup( double x, double y, Shape... shapes ) {
|
||||
super(x, y);
|
||||
this.shapes = new ArrayList<>(shapes.length);
|
||||
for( Shape pShape : shapes ) {
|
||||
this.shapes.add(pShape);
|
||||
}
|
||||
}
|
||||
|
||||
public Shape copy() {
|
||||
// TODO: implement?
|
||||
return null;
|
||||
}
|
||||
|
||||
public void add( Shape... shapes ) {
|
||||
for( Shape shape : shapes ) {
|
||||
add(shape, false);
|
||||
}
|
||||
invalidateBounds();
|
||||
}
|
||||
|
||||
public void add( Shape pShape, boolean relative ) {
|
||||
if( relative ) {
|
||||
pShape.x = pShape.x - x;
|
||||
pShape.y = pShape.y - y;
|
||||
}
|
||||
shapes.add(pShape);
|
||||
invalidateBounds();
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
shapes.clear();
|
||||
invalidateBounds();
|
||||
}
|
||||
|
||||
public List<Shape> getShapes() {
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public <ShapeType extends Shape> List<ShapeType> getShapes( Class<ShapeType> typeClass ) {
|
||||
LinkedList<ShapeType> list = new LinkedList<>();
|
||||
for( Shape s : shapes ) {
|
||||
if( typeClass.getClass().isInstance(s) ) {
|
||||
list.add((ShapeType) s);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void remove( Shape shape ) {
|
||||
shapes.remove(shape);
|
||||
invalidateBounds();
|
||||
}
|
||||
|
||||
public Shape get( int index ) {
|
||||
if( index < shapes.size() ) {
|
||||
return shapes.get(index);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains( Shape shape ) {
|
||||
return shapes.contains(shape);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return shapes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
if( groupWidth < 0 ) {
|
||||
calculateBounds();
|
||||
}
|
||||
return groupWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
if( groupHeight < 0 ) {
|
||||
calculateBounds();
|
||||
}
|
||||
return groupHeight;
|
||||
}
|
||||
|
||||
private void invalidateBounds() {
|
||||
groupWidth = -1.0;
|
||||
groupHeight = -1.0;
|
||||
}
|
||||
|
||||
private void calculateBounds() {
|
||||
double minx = Double.MAX_VALUE, miny = Double.MAX_VALUE,
|
||||
maxx = Double.MIN_VALUE, maxy = Double.MIN_VALUE;
|
||||
for( Shape pShape : shapes ) {
|
||||
Bounds bounds = pShape.getBounds();
|
||||
minx = Math.min(minx, bounds.x);
|
||||
maxx = Math.max(maxx, bounds.x + bounds.width);
|
||||
miny = Math.min(miny, bounds.y);
|
||||
maxy = Math.max(maxy, bounds.y + bounds.height);
|
||||
}
|
||||
|
||||
groupWidth = maxx - minx;
|
||||
groupHeight = maxy - miny;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
Path2D.Double gruppe = new Path2D.Double();
|
||||
for( Shape pShape : shapes ) {
|
||||
gruppe.append(pShape.getShape(), false);
|
||||
}
|
||||
return gruppe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
if( !visible ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
AffineTransform verzerrung = new AffineTransform();
|
||||
verzerrung.translate(x, y);
|
||||
verzerrung.rotate(Math.toRadians(rotation));
|
||||
//verzerrung.scale(skalierung, skalierung);
|
||||
verzerrung.translate(-anchor.x, -anchor.y);
|
||||
*/
|
||||
|
||||
for( Shape f : shapes ) {
|
||||
AffineTransform af = f.getTransform();
|
||||
af.preConcatenate(transform);
|
||||
f.draw(graphics, af);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
123
src/main/java/schule/ngb/zm/shapes/ShapesLayer.java
Normal file
123
src/main/java/schule/ngb/zm/shapes/ShapesLayer.java
Normal file
@@ -0,0 +1,123 @@
|
||||
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 {
|
||||
|
||||
protected boolean clearBeforeDraw = true;
|
||||
|
||||
private LinkedList<Shape> shapes;
|
||||
|
||||
public ShapesLayer() {
|
||||
super();
|
||||
shapes = new LinkedList<Shape>();
|
||||
}
|
||||
|
||||
public ShapesLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
shapes = new LinkedList<Shape>();
|
||||
}
|
||||
|
||||
public Shape getShape( int index ) {
|
||||
return shapes.get(index);
|
||||
}
|
||||
|
||||
public <ST extends Shape> ST getShape( Class<ST> shapeClass ) {
|
||||
for( Shape s : shapes ) {
|
||||
if( shapeClass.isInstance(s) ) {
|
||||
return (ST) s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public java.util.List<Shape> getShapes() {
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public <ST extends Shape> java.util.List<ST> getShapes( Class<ST> shapeClass ) {
|
||||
java.util.List<ST> result = new LinkedList<>();
|
||||
for( Shape s : shapes ) {
|
||||
if( shapeClass.isInstance(s) ) {
|
||||
result.add((ST) s);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void add( Shape... shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void add( Collection<Shape> shapes ) {
|
||||
synchronized( shapes ) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
synchronized( shapes ) {
|
||||
shapes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void showAll() {
|
||||
synchronized( shapes ) {
|
||||
for( Shape pShape : shapes ) {
|
||||
pShape.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void hideAll() {
|
||||
synchronized( shapes ) {
|
||||
for( Shape pShape : shapes ) {
|
||||
pShape.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
if( clearBeforeDraw ) {
|
||||
clear();
|
||||
}
|
||||
|
||||
synchronized( shapes ) {
|
||||
for( Shape pShape : shapes ) {
|
||||
if( pShape.isVisible() ) {
|
||||
pShape.draw(drawing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(pGraphics);
|
||||
}
|
||||
|
||||
}
|
||||
118
src/main/java/schule/ngb/zm/shapes/StrokedShape.java
Normal file
118
src/main/java/schule/ngb/zm/shapes/StrokedShape.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Drawable;
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
|
||||
public abstract class StrokedShape extends Constants implements Drawable {
|
||||
|
||||
protected Color strokeColor = STD_STROKECOLOR;
|
||||
|
||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
protected Stroke stroke = null;
|
||||
|
||||
public Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color color ) {
|
||||
this.strokeColor = color;
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color color , int alpha ) {
|
||||
setStrokeColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray ) {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
setStrokeColor(null);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
setStrokeColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
setStrokeColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
setStrokeColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public double getStrokeWeight() {
|
||||
return strokeWeight;
|
||||
}
|
||||
|
||||
public void setStrokeWeight( double weight ) {
|
||||
this.strokeWeight = weight;
|
||||
this.stroke = null;
|
||||
}
|
||||
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return strokeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
||||
* {@link #DOTTED} und {@link #SOLID}.
|
||||
* @param type
|
||||
*/
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
this.strokeType = DASHED;
|
||||
this.stroke = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void draw( Graphics2D graphics );
|
||||
|
||||
/**
|
||||
* Erstellt ein {@link Stroke} Objekt für den Konturtyp.
|
||||
* @return
|
||||
*/
|
||||
protected Stroke createStroke() {
|
||||
if( stroke == null ) {
|
||||
switch( strokeType ) {
|
||||
case DOTTED:
|
||||
stroke = new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
||||
break;
|
||||
case DASHED:
|
||||
stroke = new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{5.0f}, 0.0f);
|
||||
break;
|
||||
case SOLID:
|
||||
default:
|
||||
stroke = new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stroke;
|
||||
}
|
||||
|
||||
public void resetStroke() {
|
||||
setStrokeColor(STD_STROKECOLOR);
|
||||
setStrokeWeight(STD_STROKEWEIGHT);
|
||||
setStrokeType(SOLID);
|
||||
}
|
||||
|
||||
}
|
||||
234
src/main/java/schule/ngb/zm/shapes/Text.java
Normal file
234
src/main/java/schule/ngb/zm/shapes/Text.java
Normal file
@@ -0,0 +1,234 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.FontLoader;
|
||||
|
||||
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;
|
||||
|
||||
public class Text extends Shape {
|
||||
|
||||
protected String text;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public Text( Text text ) {
|
||||
super(text.getX(), text.getY());
|
||||
copyFrom(text);
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
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;
|
||||
}
|
||||
|
||||
public void setFontsize( double size ) {
|
||||
font = font.deriveFont((float) size);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public double getFontsize() {
|
||||
return font.getSize2D();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText( String pText ) {
|
||||
text = pText;
|
||||
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);
|
||||
Canvas metricsCanvas = new Canvas();
|
||||
FontMetrics metrics = metricsCanvas.getFontMetrics(font);
|
||||
width = metrics.stringWidth(text);
|
||||
//height = metrics.getHeight();
|
||||
height = metrics.getDescent() + metrics.getAscent();
|
||||
ascent = metrics.getMaxAscent();
|
||||
}
|
||||
|
||||
public Shape copy() {
|
||||
return new Text(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Text ) {
|
||||
Text pText = (Text) shape;
|
||||
this.text = pText.getText();
|
||||
this.font = pText.getFont();
|
||||
calculateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
setFontsize(font.getSize2D() * factor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.awt.Shape getShape() {
|
||||
return new Rectangle2D.Double(0, 0, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||
if( !visible ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Aktuelle Werte speichern
|
||||
Font currentFont = graphics.getFont();
|
||||
java.awt.Color currentColor = graphics.getColor();
|
||||
AffineTransform af = graphics.getTransform();
|
||||
graphics.transform(transform);
|
||||
|
||||
// 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);
|
||||
graphics.setColor(currentColor);
|
||||
graphics.setFont(currentFont);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object o ) {
|
||||
if( this == o ) return true;
|
||||
if( o == null || getClass() != o.getClass() ) return false;
|
||||
Text text = (Text) o;
|
||||
return super.equals(o) &&
|
||||
text.equals(text.text) &&
|
||||
font.equals(text.font);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getCanonicalName() + "[" +
|
||||
"text=" + text + ',' +
|
||||
"font=" + font.getName() + ',' +
|
||||
"size=" + font.getSize() +
|
||||
']';
|
||||
}
|
||||
|
||||
}
|
||||
36
src/main/java/schule/ngb/zm/shapes/Triangle.java
Normal file
36
src/main/java/schule/ngb/zm/shapes/Triangle.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Triangle extends Polygon {
|
||||
|
||||
public Triangle( double x, double y, Point2D... points ) {
|
||||
super(x, y, Arrays.copyOf(points, 3));
|
||||
if( points.length < 3 ) {
|
||||
throw new IllegalArgumentException("A triangle requires exactly three points.");
|
||||
}
|
||||
}
|
||||
|
||||
public Triangle( Point2D... points ) {
|
||||
super(Arrays.copyOf(points, 3));
|
||||
if( points.length < 3 ) {
|
||||
throw new IllegalArgumentException("A triangle requires exactly three points.");
|
||||
}
|
||||
}
|
||||
|
||||
public Triangle( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
||||
super(x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
public Triangle( Triangle triangle ) {
|
||||
super(triangle.x, triangle.y);
|
||||
copyFrom(triangle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape copy() {
|
||||
return new Triangle(this);
|
||||
}
|
||||
|
||||
}
|
||||
81
src/main/java/schule/ngb/zm/tasks/TaskRunner.java
Normal file
81
src/main/java/schule/ngb/zm/tasks/TaskRunner.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.concurrent.*;
|
||||
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 Future<?> run( Runnable task ) {
|
||||
TaskRunner r = getTaskRunner();
|
||||
return r.pool.submit(task);
|
||||
}
|
||||
|
||||
public static <T> Future<T> run( Runnable task, T result ) {
|
||||
TaskRunner r = getTaskRunner();
|
||||
return r.pool.submit(task, result);
|
||||
}
|
||||
|
||||
public static Future<?> schedule( Runnable task, int ms ) {
|
||||
TaskRunner r = getTaskRunner();
|
||||
return 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.setName("TaskRunner-" + t.getName());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(TaskRunner.class);
|
||||
|
||||
}
|
||||
419
src/main/java/schule/ngb/zm/turtle/TurtleLayer.java
Normal file
419
src/main/java/schule/ngb/zm/turtle/TurtleLayer.java
Normal file
@@ -0,0 +1,419 @@
|
||||
package schule.ngb.zm.turtle;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.Vector;
|
||||
import schule.ngb.zm.shapes.FilledShape;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Stack;
|
||||
|
||||
public class TurtleLayer extends Layer {
|
||||
|
||||
// Rotating by the clock
|
||||
public static final int H1 = 30;
|
||||
|
||||
public static final int H2 = 60;
|
||||
|
||||
public static final int H3 = 90;
|
||||
|
||||
public static final int H4 = 120;
|
||||
|
||||
public static final int H5 = 150;
|
||||
|
||||
public static final int H6 = 180;
|
||||
|
||||
public static final int H7 = 210;
|
||||
|
||||
public static final int H8 = 240;
|
||||
|
||||
public static final int H9 = 270;
|
||||
|
||||
public static final int H10 = 300;
|
||||
|
||||
public static final int H11 = 330;
|
||||
|
||||
public static final int H12 = 360;
|
||||
|
||||
private static Stack<Color> turtleColors;
|
||||
|
||||
static {
|
||||
turtleColors = new Stack<>();
|
||||
turtleColors.add(Color.ORANGE);
|
||||
turtleColors.add(Color.YELLOW);
|
||||
turtleColors.add(Color.MAGENTA);
|
||||
turtleColors.add(Color.GREEN);
|
||||
turtleColors.add(Color.RED);
|
||||
turtleColors.add(Color.BLUE);
|
||||
}
|
||||
|
||||
private Turtle mainTurtle = null;
|
||||
|
||||
private ArrayList<Turtle> turtles = new ArrayList<Turtle>(6);
|
||||
|
||||
public TurtleLayer() {
|
||||
super();
|
||||
mainTurtle = newTurtle();
|
||||
}
|
||||
|
||||
public TurtleLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
mainTurtle = newTurtle();
|
||||
}
|
||||
|
||||
public Turtle newTurtle() {
|
||||
Turtle newTurtle = new Turtle();
|
||||
|
||||
newTurtle.position.x = getWidth() / 2.0;
|
||||
newTurtle.position.y = getHeight() / 2.0;
|
||||
newTurtle.direction.x = 0.0;
|
||||
newTurtle.direction.y = -1.0;
|
||||
synchronized( turtles ) {
|
||||
if( !turtleColors.isEmpty() ) {
|
||||
newTurtle.setStrokeColor(turtleColors.pop());
|
||||
}
|
||||
turtles.add(newTurtle);
|
||||
}
|
||||
return newTurtle;
|
||||
}
|
||||
|
||||
public Turtle getTurtle() {
|
||||
return mainTurtle;
|
||||
}
|
||||
|
||||
public Turtle getTurtle( int i ) {
|
||||
return turtles.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSize( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
mainTurtle.position.x = getWidth() / 2.0;
|
||||
mainTurtle.position.y = getHeight() / 2.0;
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public void clear() {
|
||||
drawing.setBackground(STD_BACKGROUND.getColor());
|
||||
drawing.clearRect(0, 0, buffer.getWidth(), buffer.getHeight());
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics ) {
|
||||
super.draw(graphics);
|
||||
|
||||
synchronized( turtles ) {
|
||||
for( Turtle t : turtles ) {
|
||||
if( t.visible ) {
|
||||
t.draw(graphics);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Begin of delegate methods (auto-generated)
|
||||
|
||||
public void fill() {
|
||||
mainTurtle.fill();
|
||||
}
|
||||
|
||||
public void fd( double dist ) {
|
||||
mainTurtle.fd(dist);
|
||||
}
|
||||
|
||||
public void forward( double dist ) {
|
||||
mainTurtle.forward(dist);
|
||||
}
|
||||
|
||||
public void bk( double dist ) {
|
||||
mainTurtle.bk(dist);
|
||||
}
|
||||
|
||||
public void back( double dist ) {
|
||||
mainTurtle.back(dist);
|
||||
}
|
||||
|
||||
public void left() {
|
||||
mainTurtle.left();
|
||||
}
|
||||
|
||||
public void lt( double angle ) {
|
||||
mainTurtle.lt(angle);
|
||||
}
|
||||
|
||||
public void left( double angle ) {
|
||||
mainTurtle.left(angle);
|
||||
}
|
||||
|
||||
public void right() {
|
||||
mainTurtle.right();
|
||||
}
|
||||
|
||||
public void rt( double angle ) {
|
||||
mainTurtle.rt(angle);
|
||||
}
|
||||
|
||||
public void right( double angle ) {
|
||||
mainTurtle.right(angle);
|
||||
}
|
||||
|
||||
public void penUp() {
|
||||
mainTurtle.penUp();
|
||||
}
|
||||
|
||||
public void penDown() {
|
||||
mainTurtle.penDown();
|
||||
}
|
||||
|
||||
public void moveTo( double x, double y ) {
|
||||
mainTurtle.moveTo(x, y);
|
||||
}
|
||||
|
||||
public Color getFillColor() {
|
||||
return mainTurtle.getFillColor();
|
||||
}
|
||||
|
||||
public void setFillColor( Color color ) {
|
||||
mainTurtle.setFillColor(color);
|
||||
}
|
||||
|
||||
public void setFillColor( int gray ) {
|
||||
mainTurtle.setFillColor(gray);
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
mainTurtle.noFill();
|
||||
}
|
||||
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
mainTurtle.setFillColor(gray, alpha);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
mainTurtle.setFillColor(red, green, blue);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
mainTurtle.setFillColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
public void resetFill() {
|
||||
mainTurtle.resetFill();
|
||||
}
|
||||
|
||||
public Color getStrokeColor() {
|
||||
return mainTurtle.getStrokeColor();
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color color ) {
|
||||
mainTurtle.setStrokeColor(color);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray ) {
|
||||
mainTurtle.setStrokeColor(gray);
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
mainTurtle.noStroke();
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
mainTurtle.setStrokeColor(gray, alpha);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
mainTurtle.setStrokeColor(red, green, blue);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
mainTurtle.setStrokeColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
public double getStrokeWeight() {
|
||||
return mainTurtle.getStrokeWeight();
|
||||
}
|
||||
|
||||
public void setStrokeWeight( double weight ) {
|
||||
mainTurtle.setStrokeWeight(weight);
|
||||
}
|
||||
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return mainTurtle.getStrokeType();
|
||||
}
|
||||
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
mainTurtle.setStrokeType(type);
|
||||
}
|
||||
|
||||
public void resetStroke() {
|
||||
mainTurtle.resetStroke();
|
||||
}
|
||||
|
||||
public void addPosToPath() {
|
||||
mainTurtle.addPosToPath();
|
||||
}
|
||||
|
||||
public void closePath() {
|
||||
mainTurtle.closePath();
|
||||
}
|
||||
|
||||
// End of delegate methods (auto-generated)
|
||||
|
||||
|
||||
public class Turtle extends FilledShape {
|
||||
|
||||
private static final int STD_SIZE = 12;
|
||||
|
||||
boolean penDown = true;
|
||||
|
||||
boolean visible = true;
|
||||
|
||||
Vector position = new Vector(0, 0);
|
||||
|
||||
Vector direction = new Vector(0, -1);
|
||||
|
||||
Path2D.Double path = new Path2D.Double();
|
||||
|
||||
boolean pathOpen = false;
|
||||
|
||||
Turtle() {}
|
||||
|
||||
private void addPosToPath() {
|
||||
if( !pathOpen ) {
|
||||
path.reset();
|
||||
path.moveTo(position.x, position.y);
|
||||
pathOpen = true;
|
||||
} else {
|
||||
path.lineTo(position.x, position.y);
|
||||
}
|
||||
}
|
||||
|
||||
private void closePath() {
|
||||
if( pathOpen ) {
|
||||
addPosToPath();
|
||||
path.closePath();
|
||||
path.trimToSize();
|
||||
pathOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void fill() {
|
||||
closePath();
|
||||
|
||||
if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
drawing.setColor(fillColor.getJavaColor());
|
||||
drawing.fill(path);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics ) {
|
||||
/*Shape shape = new RoundRectangle2D.Double(
|
||||
-12, -5, 16, 10, 5, 3
|
||||
);*/
|
||||
Path2D path = new Path2D.Double();
|
||||
path.moveTo(STD_SIZE, 0);
|
||||
path.lineTo(-STD_SIZE, -STD_SIZE/2);
|
||||
path.lineTo(-STD_SIZE, STD_SIZE/2);
|
||||
path.lineTo(STD_SIZE, 0);
|
||||
|
||||
AffineTransform verzerrung = new AffineTransform();
|
||||
verzerrung.translate(position.x, position.y);
|
||||
verzerrung.rotate(Math.toRadians(direction.angle()));
|
||||
|
||||
Shape shape = verzerrung.createTransformedShape(path);
|
||||
|
||||
if( strokeColor != null ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(STD_STROKECOLOR.getJavaColor());
|
||||
}
|
||||
graphics.fill(shape);
|
||||
graphics.setColor(Color.BLACK.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.draw(shape);
|
||||
}
|
||||
|
||||
public void fd( double dist ) {
|
||||
this.forward(dist);
|
||||
}
|
||||
|
||||
public void forward( double dist ) {
|
||||
addPosToPath();
|
||||
|
||||
Vector positionStart = position.copy();
|
||||
position.add(Vector.setLength(direction, dist));
|
||||
|
||||
if( penDown && strokeColor != null ) {
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.setStroke(createStroke());
|
||||
drawing.drawLine((int) positionStart.x, (int) positionStart.y, (int) position.x, (int) position.y);
|
||||
}
|
||||
}
|
||||
|
||||
public void bk( double dist ) {
|
||||
this.forward(-1 * dist);
|
||||
}
|
||||
|
||||
public void back( double dist ) {
|
||||
this.forward(-1 * dist);
|
||||
}
|
||||
|
||||
public void left() {
|
||||
this.left(90);
|
||||
}
|
||||
|
||||
public void lt( double angle ) {
|
||||
this.left(angle);
|
||||
}
|
||||
|
||||
public void left( double angle ) {
|
||||
direction.rotate(-angle);
|
||||
}
|
||||
|
||||
public void right() {
|
||||
this.right(90);
|
||||
}
|
||||
|
||||
public void rt( double angle ) {
|
||||
this.right(angle);
|
||||
}
|
||||
|
||||
public void right( double angle ) {
|
||||
direction.rotate(angle);
|
||||
}
|
||||
|
||||
public void penUp() {
|
||||
penDown = false;
|
||||
}
|
||||
|
||||
public void penDown() {
|
||||
penDown = true;
|
||||
}
|
||||
|
||||
public void moveTo( double x, double y ) {
|
||||
addPosToPath();
|
||||
|
||||
position.x = x;
|
||||
position.y = y;
|
||||
|
||||
if( penDown && strokeColor != null ) {
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.setStroke(createStroke());
|
||||
drawing.drawLine((int) x, (int) y, (int) position.x, (int) position.y);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
59
src/main/java/schule/ngb/zm/util/FontLoader.java
Normal file
59
src/main/java/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);
|
||||
|
||||
}
|
||||
406
src/main/java/schule/ngb/zm/util/ImageLoader.java
Normal file
406
src/main/java/schule/ngb/zm/util/ImageLoader.java
Normal file
@@ -0,0 +1,406 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
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.ColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
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 caching = true;
|
||||
|
||||
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, caching);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
||||
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden
|
||||
* konnte. Ist ein Bild mit der angegebenen Quelle im Cache, wird das
|
||||
* gespeicherte Bild zurückgegeben. Dies kann mit {@code caching = false}
|
||||
* verhindert werden.
|
||||
* <p>
|
||||
* 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 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 caching ) {
|
||||
Validator.requireNotNull(source, "Image source may not be null");
|
||||
Validator.requireNotEmpty(source, "Image source may not be empty.");
|
||||
|
||||
if( caching && isCached(source) ) {
|
||||
return getCache(source);
|
||||
}
|
||||
|
||||
BufferedImage img = null;
|
||||
try( InputStream in = ResourceStreamProvider.getResourceStream(source) ) {
|
||||
//URL url = ResourceStreamProvider.getResourceURL(source);
|
||||
//BufferedImage img = ImageIO.read(url);
|
||||
|
||||
img = ImageIO.read(in);
|
||||
|
||||
if( caching && img != null ) {
|
||||
putCache(source, img);
|
||||
}
|
||||
} catch( IOException ioex ) {
|
||||
LOG.error(ioex, "Error loading image file from source <%s>.", source);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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( img != null ) {
|
||||
putCache(name, img);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Name des Bildes im Zwischenspeicher.
|
||||
* @param img ZU speicherndes Bild.
|
||||
*/
|
||||
public static void preloadImage( String name, BufferedImage img ) {
|
||||
putCache(name, img);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob zum angegebenen Namen ein Bild im Cache gespeichert ist.
|
||||
*
|
||||
* @param name Name des Bildes im Cache.
|
||||
* @return {@code true}, wenn es ein Bild zum Namen gibt, sonst
|
||||
* {@code false}.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert ein Bild als {@link SoftReference} im Cache.
|
||||
*
|
||||
* @param name Name des Bildes im Zwischenspeicher.
|
||||
* @param img Das zu speichernde Bild.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(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 {
|
||||
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() ) {
|
||||
throw new IOException("File already exists and is not writeable. Change permissions before saving again.");
|
||||
}
|
||||
}
|
||||
|
||||
// Dateiformat anhand der Dateiendung ermitteln oder PNG nutzen
|
||||
String filename = file.getName();
|
||||
String formatName = "png";
|
||||
if( filename.lastIndexOf('.') >= 0 ) {
|
||||
formatName = filename.substring(filename.lastIndexOf('.') + 1);
|
||||
} else {
|
||||
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()
|
||||
.getDefaultScreenDevice()
|
||||
.getDefaultConfiguration()
|
||||
.createCompatibleImage(width, height, type);
|
||||
}
|
||||
|
||||
private ImageLoader() {
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(ImageLoader.class);
|
||||
|
||||
}
|
||||
376
src/main/java/schule/ngb/zm/util/Log.java
Normal file
376
src/main/java/schule/ngb/zm/util/Log.java
Normal file
@@ -0,0 +1,376 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
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 final String DEFAULT_LOG_FORMAT = "[%1$tT] [%4$11s] %5$s %6$s%n";
|
||||
|
||||
private static final String DEFAULT_DEBUG_FORMAT = "[%1$tT] [%4$11s] (%2$s) %5$s %6$s%n";
|
||||
|
||||
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 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 void enableGlobalLevel( Level level ) {
|
||||
int lvl = Validator.requireNotNull(level).intValue();
|
||||
ensureRootLoggerIntialized();
|
||||
|
||||
// Decrease level of root level ConsoleHandlers for output
|
||||
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);
|
||||
handler.setFormatter(new LogFormatter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger LOG = Logger.getLogger(ROOT_LOGGER);
|
||||
LOG.fine("Debug logging enabled.");
|
||||
}
|
||||
|
||||
public static Log getLogger( Class<?> clazz ) {
|
||||
ensureRootLoggerIntialized();
|
||||
return new Log(clazz);
|
||||
}
|
||||
|
||||
private static void ensureRootLoggerIntialized() {
|
||||
if( LOGGING_INIT ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger rootLogger = Logger.getLogger(ROOT_LOGGER);
|
||||
rootLogger.setLevel(Level.INFO);
|
||||
|
||||
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null
|
||||
&& LogManager.getLogManager().getProperty("java.util.logging.SimpleFormatter.format") == null ) {
|
||||
System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT);
|
||||
rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()));
|
||||
rootLogger.setUseParentHandlers(false);
|
||||
}
|
||||
if( rootLogger.getUseParentHandlers() ) {
|
||||
// This logger was not configured somewhere else
|
||||
// Add a Handler and Formatter
|
||||
|
||||
//StreamHandler rootHandler = new StreamHandler(System.out, new SimpleFormatter());
|
||||
//rootLogger.addHandler(rootHandler);
|
||||
//rootLogger.setUseParentHandlers(false);
|
||||
}
|
||||
|
||||
LOGGING_INIT = true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static final class LogFormatter extends Formatter {
|
||||
|
||||
@Override
|
||||
public String format( LogRecord record ) {
|
||||
ZonedDateTime zdt = ZonedDateTime.ofInstant(
|
||||
record.getInstant(), ZoneId.systemDefault());
|
||||
String source;
|
||||
if (record.getSourceClassName() != null) {
|
||||
source = record.getSourceClassName();
|
||||
if (record.getSourceMethodName() != null) {
|
||||
source += " " + record.getSourceMethodName();
|
||||
}
|
||||
} else {
|
||||
source = record.getLoggerName();
|
||||
}
|
||||
String message = formatMessage(record);
|
||||
String throwable = "";
|
||||
if (record.getThrown() != null) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
pw.println();
|
||||
record.getThrown().printStackTrace(pw);
|
||||
pw.close();
|
||||
throwable = sw.toString();
|
||||
}
|
||||
return String.format(DEFAULT_LOG_FORMAT,
|
||||
zdt,
|
||||
source,
|
||||
record.getLoggerName(),
|
||||
record.getLevel().getLocalizedName(),
|
||||
message,
|
||||
throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
171
src/main/java/schule/ngb/zm/util/ResourceStreamProvider.java
Normal file
171
src/main/java/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/main/java/schule/ngb/zm/util/Validator.java
Normal file
152
src/main/java/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