Projektstruktur an Gradle angepasst

This commit is contained in:
ngb
2022-07-10 22:46:55 +02:00
parent 0a72b97d56
commit b60ffa9ef3
75 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,570 @@
package schule.ngb.zm;
/**
* Repräsentiert eine Farbe in der Zeichenmaschine.
* <p>
* Farben bestehen entweder aus einem Grauwert (zwischen <code>0</code> und
* <code>255</code>) oder einem Rot-, Grün- und Blauanteil (jeweils zwischen
* <code>0</code> und <code>255</code>).
* <p>
* Eine Farbe hat außerdem einen Transparenzwert zwischen <code>0</code>
* (unsichtbar) und <code>255</code> (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 <code>0</code> und <code>255</code>.
*/
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 <code>0</code> und <code>255</code>.
*/
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 <code>0</code> und <code>255</code>.
*
* @param red Rotwert zwischen <code>0</code> und <code>255</code>.
* @param green Grünwert zwischen <code>0</code> und <code>255</code>.
* @param blue Blauwert zwischen <code>0</code> und <code>255</code>.
* @return Ein passendes Farbobjekt.
*/
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 <code>0</code> und <code>255</code>.
* <var>alpha</var> gibt den den Transparentwert an (auch zwischen
* code>0</code> und <code>255</code>), wobei
* <code>0</code> komplett durchsichtig ist und <code>255</code> komplett
* deckend.
*
* @param red Rotwert zwischen <code>0</code> und <code>255</code>.
* @param green Grünwert zwischen <code>0</code> und <code>255</code>.
* @param blue Blauwert zwischen <code>0</code> und <code>255</code>.
* @param alpha Transparentwert zwischen <code>0</code> und <code>255</code>.
* @return Ein passendes Farbobjekt.
*/
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>#</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;
}
}
}

View 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());
}
}

File diff suppressed because it is too large Load Diff

View 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</code>, 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 );
}

View 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);
}
}

View 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());
}
}

View File

@@ -0,0 +1,11 @@
package schule.ngb.zm;
import java.awt.Graphics2D;
public class GraphicsLayer extends Layer {
public Graphics2D getGraphics() {
return drawing;
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}
}

View 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);
}
}

View 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());
}
}
}
}
}

View 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</code>, 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 );
}

View 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 + "]";
}
}

View 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 {
}

View 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 <code>0</code>.
* @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 <code>0</code>).
*
* @param i Index der Ebene (beginnend bei <code>0</code>).
* @return Die Ebene am Index <var>i</var> oder <code>null</code>.
* @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</code> 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() );
}
}
}
}

File diff suppressed because it is too large Load Diff

View 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);
}
}
}

View 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;
}
}

View 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);
}
}

View 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 );
}

View 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);
}
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -0,0 +1,70 @@
package schule.ngb.zm.media;
/**
* Interface für Audio-Medien.
*/
public interface Audio {
/**
* 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 @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 );
/**
* Startet die Wiedergabe des Mediums und beendet die Methode. Das
* Audio-Medium wird einmal abgespielt und stoppt dann.
*/
void playOnce();
/**
* Startet die Wiedergabe des Mediums und blockiert das Programm, bis die
* Wiedergabe beendet ist.
*/
void playOnceAndWait();
/**
* 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();
}

View File

@@ -0,0 +1,129 @@
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 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);
}
@Override
public boolean isPlaying() {
return audios.stream().anyMatch(aw -> aw.audio.isPlaying());
}
@Override
public boolean isLooping() {
return audios.stream().anyMatch(aw -> aw.audio.isLooping());
}
@Override
public void setVolume( double pVolume ) {
volume = (float) pVolume;
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * pVolume));
}
@Override
public void playOnce() {
audios.stream().forEach(aw -> aw.audio.playOnce());
}
@Override
public void playOnceAndWait() {
audios.stream().forEach(aw -> aw.audio.playOnce());
while( audios.stream().anyMatch(aw -> aw.audio.isPlaying()) ) {
try {
Thread.sleep(10);
} catch( InterruptedException e ) {
// Just keep waiting
}
}
}
@Override
public void loop() {
audios.stream().forEach(aw -> aw.audio.loop());
}
@Override
public void stop() {
audios.stream().forEach(aw -> aw.audio.stop());
}
@Override
public void dispose() {
if( isPlaying() ) {
stop();
}
audios.stream().forEach(aw -> aw.audio.dispose());
}
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;
if( !isPlaying() ) {
playOnce();
}
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);
}
});
}
}

View File

@@ -0,0 +1,207 @@
package schule.ngb.zm.media;
import schule.ngb.zm.tasks.TaskRunner;
import schule.ngb.zm.util.ResourceStreamProvider;
import javax.sound.sampled.*;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Music implements Audio {
// size of the byte buffer used to read/write the audio stream
private static final int BUFFER_SIZE = 4096;
private boolean playing = false;
private boolean looping = false;
private String audioSource;
private AudioInputStream audioStream;
private SourceDataLine audioLine;
private float volume = 0.8f;
public Music( String source ) {
this.audioSource = source;
}
@Override
public boolean isPlaying() {
return playing;
}
@Override
public boolean isLooping() {
if( !playing ) {
looping = false;
}
return looping;
}
@Override
public void setVolume( double volume ) {
this.volume = (float) volume;
if( audioLine != null ) {
applyVolume();
}
}
private void applyVolume() {
FloatControl gainControl =
(FloatControl) audioLine.getControl(FloatControl.Type.MASTER_GAIN);
float vol = 20f * (float) Math.log10(volume);
// vol = (float) Constants.limit(vol, gainControl.getMinimum(), gainControl.getMaximum());
gainControl.setValue(vol);
}
@Override
public void playOnce() {
if( openLine() ) {
TaskRunner.run(new Runnable() {
@Override
public void run() {
stream();
}
});
}
}
@Override
public void playOnceAndWait() {
if( openLine() ) {
stream();
}
}
@Override
public void loop() {
looping = true;
playOnce();
}
@Override
public void stop() {
playing = false;
looping = false;
dispose();
}
@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;
byte[] bytesBuffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
try {
while (playing && (bytesRead = audioStream.read(bytesBuffer)) != -1) {
audioLine.write(bytesBuffer, 0, bytesRead);
}
audioLine.drain();
audioLine.stop();
} catch( IOException ex ) {
LOGGER.warning("Error while playing Music source <" + audioSource + ">");
LOGGER.throwing("Music", "stream", ex);
}
// Wait for the remaining audio to play
/*while( audioLine.isRunning() ) {
try {
Thread.sleep(10);
} catch( InterruptedException ex ) {
// Just keep waiting ...
}
}*/
playing = false;
streamingStopped();
}
private boolean openLine() {
if( audioLine != null ) {
return true;
}
try {
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 {
LOGGER.warning("Sound source <" + audioSource + "> could not be played: No audio source found.");
}
} catch( UnsupportedAudioFileException ex ) {
LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: The specified audio file is not supported.", ex);
} catch( LineUnavailableException ex ) {
LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Audio line for playing back is unavailable.", ex);
} catch( IOException ex ) {
LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Error playing the audio file.", ex);
}
return false;
}
private void streamingStopped() {
dispose();
if( looping ) {
if( openLine() ) {
stream();
} else {
playing = false;
looping = false;
}
} else {
playing = false;
}
}
//private static final Logger LOGGER = Logger.getLogger("schule.ngb.zm.media.Music");
private static final Logger LOGGER = Logger.getLogger(Music.class.getName());
}

View File

@@ -0,0 +1,204 @@
package schule.ngb.zm.media;
import schule.ngb.zm.util.ResourceStreamProvider;
import javax.sound.sampled.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
public class Sound implements Audio {
private boolean playing = false;
private boolean looping = false;
private String audioSource;
private Clip audioClip;
private boolean disposeAfterPlay = false;
private float volume = 0.8f;
public Sound( String source ) {
this.audioSource = source;
}
public boolean isPlaying() {
// return audioClip != null && audioClip.isRunning();
return playing;
}
public boolean isLooping() {
if( !playing ) {
looping = false;
}
return looping;
}
public void setVolume( double volume ) {
this.volume = (float) volume;
if( audioClip != null ) {
applyVolume();
}
}
private void applyVolume() {
FloatControl gainControl =
(FloatControl) audioClip.getControl(FloatControl.Type.MASTER_GAIN);
float vol = 20f * (float) Math.log10(volume);
// vol = (float) Constants.limit(vol, gainControl.getMinimum(), gainControl.getMaximum());
gainControl.setValue(vol);
}
public void stop() {
looping = false;
if( audioClip.isRunning() ) {
audioClip.stop();
}
playing = false;
}
public void play() {
if( this.openClip() ) {
audioClip.start();
playing = true;
}
}
public void playOnce() {
disposeAfterPlay = true;
play();
}
public void playOnceAndWait() {
disposeAfterPlay = true;
playAndWait();
}
public void playAndWait() {
this.play();
long audioLen = audioClip.getMicrosecondLength();
while( playing ) {
try {
long ms = (audioLen - audioClip.getMicrosecondPosition()) / 1000L;
Thread.sleep(ms);
} catch( InterruptedException ex ) {
// Ignore
}
}
audioClip.close();
}
public void loop() {
loop(Clip.LOOP_CONTINUOUSLY);
}
public void loop( int count ) {
int loopCount = count;
if( loopCount != Clip.LOOP_CONTINUOUSLY ) {
if( loopCount <= 0 ) {
return;
}
// Adjust Number of loops
loopCount -= 1;
}
if( openClip() ) {
looping = true;
audioClip.loop(loopCount);
playing = true;
}
}
public void dispose() {
if( audioClip != null ) {
if( audioClip.isRunning() ) {
audioClip.stop();
}
audioClip.close();
audioClip = null;
}
}
private boolean openClip() {
if( audioClip != null ) {
audioClip.setFramePosition(0);
return true;
}
try {
InputStream in = ResourceStreamProvider.getResourceStream(audioSource);
if( in != null ) {
AudioInputStream audioStream = AudioSystem.getAudioInputStream(in);
AudioFormat format = audioStream.getFormat();
DataLine.Info info = new DataLine.Info(Clip.class, format);
audioClip = (Clip) AudioSystem.getLine(info);
audioClip.addLineListener(new LineListener() {
@Override
public void update( LineEvent event ) {
if( event.getType() == LineEvent.Type.STOP ) {
playbackStopped();
}
}
});
audioClip.open(audioStream);
applyVolume();
return true;
} else {
LOGGER.warning("Sound source " + audioSource + " could not be played: No audio source found.");
}
} catch( UnsupportedAudioFileException ex ) {
LOGGER.warning("Sound source " + audioSource + " could not be played: The specified audio file is not supported.");
LOGGER.throwing("Sound", "openClip", ex);
} catch( LineUnavailableException ex ) {
LOGGER.warning("Sound source " + audioSource + " could not be played: Audio line for playing back is unavailable.");
LOGGER.throwing("Sound", "openClip", ex);
} catch( IOException ex ) {
LOGGER.warning("Sound source " + audioSource + " could not be played: Error playing the audio file.");
LOGGER.throwing("Sound", "openClip", ex);
}
return false;
}
/*@Override
public void update( LineEvent event ) {
LineEvent.Type type = event.getType();
if( type == LineEvent.Type.START ) {
playing = true;
} else if( type == LineEvent.Type.STOP ) {
playing = false;
if( disposeAfterPlay ) {
this.dispose();
disposeAfterPlay = false;
}
}
}*/
private void playbackStopped() {
playing = false;
if( disposeAfterPlay ) {
this.dispose();
disposeAfterPlay = false;
}
}
/*
public void addLineListener( LineListener listener ) {
if( audioClip == null ) {
openClip();
}
if( audioClip != null ) {
audioClip.addLineListener(listener);
}
}
*/
private static final Logger LOGGER = Logger.getLogger(Sound.class.getName());
}

View 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);
}
}

View 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);
}
}

View 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;
}
}
}
}

View 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 +
']';
}
}

View 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] +
']';
}
}

View 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);
}
}

View 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 +
']';
}
}

View 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);
}
}

View 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 +
']';
}
}

View 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 +
']';
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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 +
']';
}
}

View 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);
}
}

View 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 +
']';
}
}

View 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)</code> 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 anchor
* @param buff
*/
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
Point2D ap = shape.getAbsAnchorPoint(dir);
this.x = ap.getX() + dir.x * buff;
this.y = ap.getY() + dir.y * buff;
}
/**
* Richtet die Form entlang der angegebenen Richtung am Rand der
* Zeichenfläche aus.
*
* @param dir Die Richtung der Ausrichtung.
*/
public void alignTo( Options.Direction dir ) {
alignTo(dir, 0.0);
}
public void alignTo( Options.Direction dir, double buff ) {
Point2D anchorShape = Shape.getAnchorPoint(width, height, dir);
Point2D anchorThis = this.getAbsAnchorPoint(dir);
this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
}
public void alignTo( Shape shape, Options.Direction dir ) {
alignTo(shape, dir, 0.0);
}
/**
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
* aus. Für {@code DOWN} wird beispielsweise die y-Koordinate der unteren
* Kante dieser Form an der unteren Kante der angegebenen Form {@code shape}
* ausgerichtet. Die x-Koordinate wird nicht verändert. {@code buff} gibt
* einen Abstand ab, um den diese From versetzt ausgerichtet werden soll.
*
* @param shape
* @param dir
* @param buff
*/
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
Point2D anchorThis = this.getAbsAnchorPoint(dir);
this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
}
public void nextTo( Shape shape, Options.Direction dir ) {
nextTo(shape, dir, STD_BUFFER);
}
/**
* Bewegt die Form neben eine andere in Richtung des angegebenen
* Ankerpunktes. Im Gegensatz zu
* {@link #moveTo(Shape, Options.Direction, double)} wird die Breite bzw.
* Höhe der Formen berücksichtigt und die Formen so platziert, dass keine
* Überlappungen vorhanden sind.
*
* @param shape
* @param dir
* @param buff
*/
public void nextTo( Shape shape, Options.Direction dir, double buff ) {
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
Point2D anchorThis = this.getAbsAnchorPoint(dir.inverse());
this.x += (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
}
public void scale( double factor ) {
scale = factor;
}
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;
}
}

View 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);
}
}
}

View 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);
}
}

View 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);
}
}

View 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() +
']';
}
}

View 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);
}
}

View 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);
}

View 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);
}
}
}
}

View 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);
}

View 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</code>, 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);
}

View 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);
}
}
}

View 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);
}

View 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() {
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB