mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-10-12 09:56:33 +02:00
Compare commits
32 Commits
zeichenfen
...
ec30afd441
| Author | SHA1 | Date | |
|---|---|---|---|
| ec30afd441 | |||
| d3bdbdbffb | |||
| 8cc7167d7e | |||
| 9e4271c304 | |||
| 4f13f5177d | |||
| 6321a7d421 | |||
| 135af10729 | |||
| 912f68c58f | |||
| 7f1d9012e9 | |||
| 60ed045986 | |||
| dc16608333 | |||
| 7b6398fe52 | |||
| 8f98ddc56d | |||
| 782ce33540 | |||
| 537527e525 | |||
| 8e93866b5e | |||
| fcb536ff96 | |||
| 70c607f2e8 | |||
| 6126ed3c15 | |||
| b0353c53a0 | |||
| c93a203ab9 | |||
| f1d32685b4 | |||
| 91842b511f | |||
| 52b480b46b | |||
| 4d2ade899d | |||
| dcdca893b7 | |||
| ebf0135486 | |||
| fea1083926 | |||
| 250d9d17d3 | |||
| 2a71243fc6 | |||
| 03d37222bf | |||
| 687d7d35b7 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -48,3 +48,11 @@ gradle-app.setting
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
# Python mkdocs
|
||||
.venv
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
@@ -6,6 +6,14 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- `FilledShape` und `StrokedShape` durch `Fillable` und `Strokeable` Interfaces ersetzt.
|
||||
- `Shape` erweitert nun `BasisDrawable` als abstrakte Grundlage.
|
||||
- `io` Klassen nutzen nun mehr der `java.nio` Funktionen.
|
||||
- Package-Struktur angepasst.
|
||||
|
||||
## Version 0.0.23
|
||||
|
||||
### Added
|
||||
- System für EventListener.
|
||||
- `AudioListener` und `AnimationListener` als erste Anwendungsfälle.
|
||||
|
||||
@@ -7,13 +7,14 @@ plugins {
|
||||
zmVersion {
|
||||
major = 0;
|
||||
minor = 0;
|
||||
rev = 21;
|
||||
rev = 32;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
version '{$zmVersion.major}.{$zmVersion.minor}.{$zmVersion.rev}-SNAPSHOT'
|
||||
*/
|
||||
|
||||
group 'schule.ngb'
|
||||
version '0.0.22-SNAPSHOT'
|
||||
//version '{$zmVersion.major}.{$zmVersion.minor}.{$zmVersion.rev}-SNAPSHOT'
|
||||
version '0.0.32-SNAPSHOT'
|
||||
|
||||
compileJava {
|
||||
options.release = 11
|
||||
|
||||
0
docs/quickstart.md
Normal file
0
docs/quickstart.md
Normal file
236
src/main/java/schule/ngb/zm/BasicDrawable.java
Normal file
236
src/main/java/schule/ngb/zm/BasicDrawable.java
Normal file
@@ -0,0 +1,236 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* Basisimplementierung der {@link Strokeable} und {@link Fillable} APIs.
|
||||
*
|
||||
* Die Klasse bietet eine Grundlage zur Implementierung eigener Zeichenobjekte,
|
||||
* die eine Füllung und eine Konturlinie haben können.
|
||||
*/
|
||||
public abstract class BasicDrawable extends Constants implements Strokeable, Fillable {
|
||||
|
||||
/**
|
||||
* Ob das Objekt gezeichnet werden soll.
|
||||
*/
|
||||
protected boolean visible = true;
|
||||
|
||||
/**
|
||||
* Aktuelle Farbe der Konturlinie oder {@code null}, wenn das Objekt ohne
|
||||
* Kontur dargestellt werden soll.
|
||||
*/
|
||||
protected schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||
|
||||
/**
|
||||
* Die Dicke der Konturlinie. Wird nicht kleiner als 0.
|
||||
*/
|
||||
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||
|
||||
/**
|
||||
* Die Art der Konturlinie.
|
||||
*/
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
/**
|
||||
* Cache für den aktuellen {@code Stroke} der Kontur. Wird nach Änderung
|
||||
* einer der Kontureigenschaften auf {@code null} gesetzt und beim nächsten
|
||||
* Zeichnen neu erstellt.
|
||||
*/
|
||||
protected Stroke stroke = null;
|
||||
|
||||
/**
|
||||
* Die aktuelle Füllfarbe der Form oder {@code null}, wenn das Objekt nicht
|
||||
* gefüllt werden soll.
|
||||
*/
|
||||
protected Color fillColor = DEFAULT_FILLCOLOR;
|
||||
|
||||
/**
|
||||
* Der aktuelle Farbverlauf des Objektes oder {@code null}, wenn es keinen
|
||||
* Farbverlauf besitzt.
|
||||
*/
|
||||
protected MultipleGradientPaint fill = null;
|
||||
|
||||
|
||||
// Implementierung Drawable Interface
|
||||
|
||||
/**
|
||||
* Ob das Objekt angezeigt bzw. gezeichnet werden soll.
|
||||
*
|
||||
* @return {@code true}, wenn das Objekt angezeigt werden soll,
|
||||
* {@code false} sonst.
|
||||
*/
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Versteckt das Objekt.
|
||||
*/
|
||||
public void hide() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt das Objekt an.
|
||||
*/
|
||||
public void show() {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Versteckt da Objekt, wenn es derzeit angezeigt wird und zeigt es
|
||||
* andernfalls an.
|
||||
*/
|
||||
public void toggle() {
|
||||
visible = !visible;
|
||||
}
|
||||
|
||||
public abstract void draw( Graphics2D graphics );
|
||||
|
||||
|
||||
// Implementierung Fillable Interface
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setFill( Paint fill ) {
|
||||
if( fill == null ) {
|
||||
this.fill = null;
|
||||
} else if( fill instanceof Color ) {
|
||||
this.fillColor = ((Color) fill);
|
||||
this.fill = null;
|
||||
} else if( fill instanceof MultipleGradientPaint ) {
|
||||
this.fillColor = null;
|
||||
this.fill = (MultipleGradientPaint) fill;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Paint getFill() {
|
||||
if( fill != null ) {
|
||||
return fill;
|
||||
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
return fillColor;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean hasFillColor() {
|
||||
return fillColor != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean hasGradient() {
|
||||
return fill != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Color getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setFillColor( Color color ) {
|
||||
fillColor = color;
|
||||
fill = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MultipleGradientPaint getGradient() {
|
||||
return fill;
|
||||
}
|
||||
|
||||
|
||||
// Implementierung Strokeable Interface
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setStroke( Stroke stroke ) {
|
||||
this.stroke = stroke;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Stroke getStroke() {
|
||||
if( stroke == null ) {
|
||||
stroke = Strokeable.createStroke(strokeType, strokeWeight);
|
||||
}
|
||||
return stroke;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setStrokeColor( Color color ) {
|
||||
strokeColor = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getStrokeWeight() {
|
||||
return strokeWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setStrokeWeight( double weight ) {
|
||||
strokeWeight = weight;
|
||||
this.stroke = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return strokeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
strokeType = type;
|
||||
this.stroke = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import java.awt.image.ColorModel;
|
||||
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
|
||||
* (deckend).
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Color implements Paint {
|
||||
|
||||
|
||||
@@ -151,7 +152,7 @@ public class Color implements Paint {
|
||||
* @param alpha Transparentwert zwischen 0 und 255.
|
||||
*/
|
||||
public Color( int red, int green, int blue, int alpha ) {
|
||||
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | ((blue & 0xFF) << 0);
|
||||
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +161,7 @@ public class Color implements Paint {
|
||||
* @param color
|
||||
*/
|
||||
public Color( Color color ) {
|
||||
this(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
this(color.getRGBA(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,8 +209,7 @@ public class Color implements Paint {
|
||||
* @return
|
||||
*/
|
||||
public static Color getRGBColor( int rgba ) {
|
||||
Color c = new Color(rgba, true);
|
||||
return c;
|
||||
return new Color(rgba, true);
|
||||
}
|
||||
|
||||
public static Color getHSBColor( double h, double s, double b ) {
|
||||
@@ -259,8 +259,6 @@ public class Color implements Paint {
|
||||
} 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));
|
||||
@@ -307,12 +305,12 @@ public class Color implements Paint {
|
||||
if( c == 0 ) {
|
||||
h_ = 0;
|
||||
} else if( max == r ) {
|
||||
h_ = (float) (g - b) / c;
|
||||
h_ = (g - b) / c;
|
||||
if( h_ < 0 ) h_ += 6.f;
|
||||
} else if( max == g ) {
|
||||
h_ = (float) (b - r) / c + 2.f;
|
||||
h_ = (b - r) / c + 2.f;
|
||||
} else if( max == b ) {
|
||||
h_ = (float) (r - g) / c + 4.f;
|
||||
h_ = (r - g) / c + 4.f;
|
||||
}
|
||||
float h = 60.f * h_;
|
||||
|
||||
@@ -484,7 +482,6 @@ public class Color implements Paint {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
||||
*
|
||||
@@ -494,6 +491,7 @@ public class Color implements Paint {
|
||||
* @param obj Das zu vergleichende Objekt.
|
||||
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals( Object obj ) {
|
||||
if( obj == null ) {
|
||||
return false;
|
||||
|
||||
@@ -5,19 +5,22 @@ import schule.ngb.zm.util.io.ImageLoader;
|
||||
import schule.ngb.zm.util.Noise;
|
||||
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Font;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
/**
|
||||
* Basisklasse für die meisten Objekte der Zeichemaschine, die von Nutzern
|
||||
* Basisklasse für die meisten Objekte der Zeichenmaschine, die von Nutzern
|
||||
* erweitert werden können.
|
||||
* <p>
|
||||
* Die Konstanten stellen viele Funktionen zur einfachen Programmierung bereit
|
||||
* und enthält auch einige dynamische Werte, die von der Zeichenmaschine laufend
|
||||
* aktuell gehalten werden (beispielsweise {@link #runtime}.
|
||||
* aktuell gehalten werden (beispielsweise {@link #runtime}).
|
||||
* <p>
|
||||
* Für die Implementierung eigener Klassen ist es oft hilfreich von
|
||||
* {@code Constants} zu erben, um die Methoden und Konstanten einfach im
|
||||
@@ -64,13 +67,49 @@ public class Constants {
|
||||
/**
|
||||
* Patchversion der Zeichenmaschine.
|
||||
*/
|
||||
public static final int APP_VERSION_REV = 22;
|
||||
public static final int APP_VERSION_REV = 32;
|
||||
|
||||
/**
|
||||
* Version der Zeichenmaschine als Text-String.
|
||||
*/
|
||||
public static final String APP_VERSION = APP_VERSION_MAJ + "." + APP_VERSION_MIN + "." + APP_VERSION_REV;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter macOS gestartet wurde.
|
||||
*/
|
||||
public static final boolean MACOS;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter Windows gestartet wurde.
|
||||
*/
|
||||
public static final boolean WINDOWS;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter Linux gestartet wurde.
|
||||
*/
|
||||
public static final boolean LINUX;
|
||||
|
||||
static {
|
||||
final String name = System.getProperty("os.name");
|
||||
|
||||
if( name.contains("Mac") ) {
|
||||
MACOS = true;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
} else if( name.contains("Windows") ) {
|
||||
MACOS = false;
|
||||
WINDOWS = true;
|
||||
LINUX = false;
|
||||
} else if( name.equals("Linux") ) { // true for the ibm vm
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = true;
|
||||
} else {
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardbreite eines Zeichenfensters.
|
||||
@@ -405,6 +444,21 @@ public class Constants {
|
||||
*/
|
||||
public static final double TWO_PI = Math.PI * 2.0;
|
||||
|
||||
/**
|
||||
* Konstante für fette Schrift.
|
||||
*/
|
||||
public static final int BOLD = Font.BOLD;
|
||||
|
||||
/**
|
||||
* Konstante für kursive Schrift.
|
||||
*/
|
||||
public static final int ITALIC = Font.ITALIC;
|
||||
|
||||
/**
|
||||
* Konstante für normale Schrift.
|
||||
*/
|
||||
public static final int PLAIN = Font.PLAIN;
|
||||
|
||||
/*
|
||||
* Globale Variablen, die von allen Klassen genutzt werden dürfen. Änderungen
|
||||
* wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der
|
||||
@@ -1280,6 +1334,26 @@ public class Constants {
|
||||
return getRandom().nextGaussian();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt ein zufälliges Element aus dem Array aus.
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
*/
|
||||
public static final int choice( int[] values ) {
|
||||
return values[random(0, values.length - 1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt ein zufälliges Element aus dem Array aus.
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
*/
|
||||
public static final double choice( double[] values ) {
|
||||
return values[random(0, values.length - 1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt ein zufälliges Element aus dem Array aus.
|
||||
*
|
||||
@@ -1296,15 +1370,149 @@ public class Constants {
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @param n Anzahl der auszuwählenden Elemente.
|
||||
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||
* {@code values.length < n}, also nicht
|
||||
* genug Werte zur Wahl stehen.
|
||||
*/
|
||||
public static final int[] choice( int[] values, int n, boolean unique ) {
|
||||
if( unique && values.length < n )
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||
);
|
||||
|
||||
int[] result = new int[n];
|
||||
int[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
int j = random(0, valuesCopy.length - 1);
|
||||
int l = valuesCopy.length - 1;
|
||||
|
||||
result[i] = valuesCopy[j];
|
||||
valuesCopy[j] = valuesCopy[l];
|
||||
valuesCopy[l] = result[i];
|
||||
|
||||
if( unique )
|
||||
valuesCopy = Arrays.copyOf(valuesCopy, l);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt die angegebene Anzahl Elemente aus dem Array aus.
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @param n Anzahl der auszuwählenden Elemente.
|
||||
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||
* {@code values.length < n}, also nicht
|
||||
* genug Werte zur Wahl stehen.
|
||||
*/
|
||||
public static final double[] choice( double[] values, int n, boolean unique ) {
|
||||
if( unique && values.length < n )
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||
);
|
||||
|
||||
double[] result = new double[n];
|
||||
double[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
int j = random(0, valuesCopy.length - 1);
|
||||
int l = valuesCopy.length - 1;
|
||||
|
||||
result[i] = valuesCopy[j];
|
||||
valuesCopy[j] = valuesCopy[l];
|
||||
valuesCopy[l] = result[i];
|
||||
|
||||
if( unique )
|
||||
valuesCopy = Arrays.copyOf(valuesCopy, l);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt die angegebene Anzahl Elemente aus dem Array aus.
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @param n Anzahl der auszuwählenden Elemente.
|
||||
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||
* @param <T> Datentyp der Elemente.
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||
* {@code values.length < n}, also nicht
|
||||
* genug Werte zur Wahl stehen.
|
||||
*/
|
||||
public static final <T> T[] choice( T[] values, int n ) {
|
||||
Object[] result = new Object[n];
|
||||
public static final <T> T[] choice( T[] values, int n, boolean unique ) {
|
||||
if( unique && values.length < n )
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||
);
|
||||
|
||||
T[] result = Arrays.copyOf(values, n);
|
||||
T[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
result[i] = choice(values);
|
||||
int last = valuesCopy.length - 1;
|
||||
int j = random(0, last);
|
||||
|
||||
result[i] = valuesCopy[j];
|
||||
valuesCopy[j] = valuesCopy[last];
|
||||
valuesCopy[last] = result[i];
|
||||
|
||||
if( unique )
|
||||
valuesCopy = Arrays.copyOf(valuesCopy, last);
|
||||
}
|
||||
return (T[]) result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bringt die Zahlen im Array in eine zufällige Reihenfolge.
|
||||
*
|
||||
* @param values Ein Array mit Zahlen, die gemischt werden sollen.
|
||||
* @return Das Array in zufälliger Reihenfolge.
|
||||
*/
|
||||
public static final int[] shuffle( int[] values ) {
|
||||
for( int i = 0; i < values.length - 1; i++ ) {
|
||||
int j = random(i, values.length - 1);
|
||||
int tmp = values[i];
|
||||
values[i] = values[j];
|
||||
values[j] = tmp;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bringt die Zahlen im Array in eine zufällige Reihenfolge.
|
||||
*
|
||||
* @param values Ein Array mit Zahlen, die gemischt werden sollen.
|
||||
* @return Das Array in zufälliger Reihenfolge.
|
||||
*/
|
||||
public static final double[] shuffle( double[] values ) {
|
||||
for( int i = 0; i < values.length - 1; i++ ) {
|
||||
int j = random(i, values.length - 1);
|
||||
double tmp = values[i];
|
||||
values[i] = values[j];
|
||||
values[j] = tmp;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bringt die Werte im Array in eine zufällige Reihenfolge.
|
||||
*
|
||||
* @param values Ein Array mit Werte, die gemischt werden sollen.
|
||||
* @param <T> Datentyp der Elemente.
|
||||
* @return Das Array in zufälliger Reihenfolge.
|
||||
*/
|
||||
public static final <T> T[] shuffle( T[] values ) {
|
||||
java.util.List<T> valueList = Arrays.asList(values);
|
||||
Collections.shuffle(valueList, random);
|
||||
return valueList.toArray(values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1474,7 +1682,7 @@ public class Constants {
|
||||
}
|
||||
|
||||
public static final double asDouble( char value ) {
|
||||
return (double) value;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static final double asDouble( byte value ) {
|
||||
|
||||
279
src/main/java/schule/ngb/zm/Fillable.java
Normal file
279
src/main/java/schule/ngb/zm/Fillable.java
Normal file
@@ -0,0 +1,279 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.shapes.Shape;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* {@link Drawable} Klassen, die mit einer Füllung versehen werden können.
|
||||
* <p>
|
||||
* Das {@code Fillable} Interface dient hauptsächlich zur Vereinheitlichung der
|
||||
* API für Füllungen. Durch Implementation wird sichergestellt, dass alle
|
||||
* Objekte, die eine Füllung haben können, dieselben Methoden zur Verfügung
|
||||
* stellen. Wenn eine {@link Shape} eine
|
||||
* {@link Fillable#setFillColor(Color, int)} Methode hat, dann sollte auch eine
|
||||
* {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode anbieten. Im
|
||||
* Einzelfall kann es sinnvoll sein, weitere Methoden für Füllungen zur
|
||||
* Verfügung zu stellen. Allerdings sollte davon nach Möglichkeit zugunsten
|
||||
* einer einheitlichen API abgesehen werden.
|
||||
* <p>
|
||||
* Das Äquivalent für Konturlinien stellt {@link Strokeable} dar.
|
||||
* <p>
|
||||
* Im einfachsten Fall reicht es {@link #setFill(Paint)} und {@link #getFill()}
|
||||
* zu implementieren. Die anderen Methoden besitzen Standardimplementierungen,
|
||||
* die sich auf die beiden Methoden beziehen. Allerdings ist es in vielen Fällen
|
||||
* sinnvoll, einige der Methoden gezielt zu überschreiben, um sie an spezifische
|
||||
* Situationen anzupassen.
|
||||
*/
|
||||
public interface Fillable extends Drawable {
|
||||
|
||||
/**
|
||||
* Setzt die Füllung direkt auf das angegebene {@code Paint}-Objekt.
|
||||
*
|
||||
* @param fill Die neue Füllung.
|
||||
*/
|
||||
void setFill( Paint fill );
|
||||
|
||||
/**
|
||||
* Gibt die aktuell gesetzte Füllung zurück.
|
||||
* <p>
|
||||
* Die Art der Füllung kann anhand der Abfragen {@link #hasFillColor()} und
|
||||
* {@link #hasGradient()} ermittelt werden.
|
||||
*
|
||||
* @return Die aktuelle Füllung.
|
||||
*/
|
||||
Paint getFill();
|
||||
|
||||
/**
|
||||
* Gibt an, ob aktuell eine sichtbare Füllung konfiguriert ist.
|
||||
* <p>
|
||||
* Eine Füllung gilt als sichtbar, wenn eine nciht transparente Füllfarbe
|
||||
* oder ein Farbverlauf eingestellt ist.
|
||||
*
|
||||
* @return {@code true}, wenn die Füllung sichtbar ist, {@code false} sonst.
|
||||
*/
|
||||
default boolean hasFill() {
|
||||
return (hasFillColor() && getFillColor().getAlpha() > 0) || hasGradient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt an, ob eine Füllfarbe konfiguriert ist.
|
||||
* <p>
|
||||
* Im Gegensatz zu {@link #hasFill()} prüft die Methode <em>nicht</em>, ob
|
||||
* die Füllfarbe transparent ist.
|
||||
*
|
||||
* @return {@code true}, wenn eine Füllfarbe gesetzt ist.
|
||||
*/
|
||||
default boolean hasFillColor() {
|
||||
Paint fill = getFill();
|
||||
return fill instanceof Color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt an, ob ein Farbverlauf konfiguriert ist.
|
||||
*
|
||||
* @return {@code true}, wenn ein Farbverlauf gesetzt ist.
|
||||
*/
|
||||
default boolean hasGradient() {
|
||||
Paint fill = getFill();
|
||||
return fill instanceof MultipleGradientPaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Füllfarbe der Form zurück.
|
||||
*
|
||||
* @return Die aktuelle Füllfarbe oder {@code null}.
|
||||
*/
|
||||
default Color getFillColor() {
|
||||
if( hasFillColor() ) {
|
||||
return (Color) getFill();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die angegebene Farbe.
|
||||
*
|
||||
* @param color Die neue Füllfarbe oder {@code null}.
|
||||
* @see Color
|
||||
*/
|
||||
default void setFillColor( Color color ) {
|
||||
setFill(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die angegebene Farbe und setzt die Transparenz
|
||||
* auf den angegebenen Wert. 0 is komplett durchsichtig und 255 komplett
|
||||
* deckend.
|
||||
*
|
||||
* @param color Die neue Füllfarbe oder {@code null}.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(Color, int)
|
||||
*/
|
||||
default void setFillColor( Color color, int alpha ) {
|
||||
setFillColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität. 0
|
||||
* entspricht schwarz, 255 entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @see Color#Color(int)
|
||||
*/
|
||||
default void setFillColor( int gray ) {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität und
|
||||
* dem angegebenen Transparenzwert. Der Grauwert 0 entspricht schwarz, 255
|
||||
* entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(int, int)
|
||||
*/
|
||||
default void setFillColor( int gray, int alpha ) {
|
||||
setFillColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||
* Blauanteilen.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @see Color#Color(int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
default void setFillColor( int red, int green, int blue ) {
|
||||
setFillColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||
* Blauanteilen und dem angegebenen Transparenzwert.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
||||
* @see Color#Color(int, int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
default void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt die Füllung der Form.
|
||||
*/
|
||||
default void noFill() {
|
||||
setFillColor(null);
|
||||
noGradient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf den Standardwert zurück.
|
||||
*
|
||||
* @see schule.ngb.zm.Constants#DEFAULT_FILLCOLOR
|
||||
*/
|
||||
default void resetFill() {
|
||||
setFillColor(Constants.DEFAULT_FILLCOLOR);
|
||||
noGradient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den aktuellen Farbverlauf der Form zurück.
|
||||
*
|
||||
* @return Der aktuelle Farbverlauf oder {@code null}.
|
||||
*/
|
||||
default MultipleGradientPaint getGradient() {
|
||||
if( hasGradient() ) {
|
||||
return (MultipleGradientPaint) getFill();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen linearen Farbverlauf, der am Punkt
|
||||
* ({@code fromX}, {@code fromY}) mit der Farbe {@code from} startet und am
|
||||
* Punkt (({@code toX}, {@code toY}) mit der Farbe {@code to} endet.
|
||||
*
|
||||
* @param fromX x-Koordinate des Startpunktes.
|
||||
* @param fromY y-Koordinate des Startpunktes.
|
||||
* @param from Farbe am Startpunkt.
|
||||
* @param toX x-Koordinate des Endpunktes.
|
||||
* @param toY y-Koordinate des Endpunktes.
|
||||
* @param to Farbe am Endpunkt.
|
||||
*/
|
||||
default void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||
setFill(new LinearGradientPaint(
|
||||
(float) fromX, (float) fromY,
|
||||
(float) toX, (float) toY,
|
||||
new float[]{0f, 1f},
|
||||
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen linearen Farbverlauf, der in die angegebene
|
||||
* Richtung verläuft.
|
||||
*
|
||||
* @param from Farbe am Startpunkt.
|
||||
* @param to Farbe am Endpunkt.
|
||||
* @param dir Richtung des Farbverlaufs.
|
||||
*/
|
||||
default void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||
int whalf = (Constants.canvasWidth / 2);
|
||||
int hhalf = (Constants.canvasHeight / 2);
|
||||
setGradient(whalf - dir.x * whalf, hhalf - dir.y * hhalf, from, whalf + dir.x * whalf, hhalf + dir.y * hhalf, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, mit dem
|
||||
* Zentrum im Punkt ({@code centerX}, {@code centerY}) und dem angegebenen
|
||||
* Radius. Der Verlauf starte im Zentrum mit der Farbe {@code from} und
|
||||
* endet am Rand des durch den Radius beschriebenen Kreises mit der Farbe
|
||||
* {@code to}.
|
||||
*
|
||||
* @param centerX x-Koordinate des Kreismittelpunktes.
|
||||
* @param centerY y-Koordinate des Kreismittelpunktes.
|
||||
* @param radius Radius des Kreises.
|
||||
* @param from Farbe im Zentrum des Kreises.
|
||||
* @param to Farbe am Rand des Kreises.
|
||||
*/
|
||||
default void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||
setFill(new RadialGradientPaint(
|
||||
(float) centerX, (float) centerY, (float) radius,
|
||||
new float[]{0f, 1f},
|
||||
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, der im
|
||||
* Zentrum beginnt.
|
||||
*
|
||||
* @param from Farbe im Zentrum.
|
||||
* @param to Farbe am Rand.
|
||||
*/
|
||||
default void setGradient( Color from, Color to ) {
|
||||
int whalf = (Constants.canvasWidth / 2);
|
||||
int hhalf = (Constants.canvasHeight / 2);
|
||||
setGradient(whalf, hhalf, Math.min(whalf, hhalf), from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt den Farbverlauf von der Form.
|
||||
*/
|
||||
default void noGradient() {
|
||||
setFill(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Resourcen der Ebene frei.
|
||||
* Gibt die Ressourcen der Ebene frei.
|
||||
*/
|
||||
public void dispose() {
|
||||
drawing.dispose();
|
||||
@@ -139,14 +139,14 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet den Puffer auf die Grafik-Instanz.
|
||||
* Zeichnet den Puffer auf den Grafikkontext.
|
||||
*
|
||||
* @param pGraphics
|
||||
* @param graphics Der Grafikkontext, auf den gezeichnet wird.
|
||||
*/
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
if( visible ) {
|
||||
pGraphics.drawImage(buffer, 0, 0, null);
|
||||
graphics.drawImage(buffer, 0, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,12 +36,13 @@ public final class Options {
|
||||
INITIALIZING,
|
||||
INITIALIZED,
|
||||
RUNNING,
|
||||
UPDATING,
|
||||
DRAWING,
|
||||
PAUSED,
|
||||
STOPPED,
|
||||
TERMINATED,
|
||||
QUITING,
|
||||
IDLE,
|
||||
UPDATING,
|
||||
DRAWING,
|
||||
DELAYED,
|
||||
DISPATCHING
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public class Spielemaschine extends Zeichenmaschine {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
clear();
|
||||
List<Drawable> it = List.copyOf(drawables);
|
||||
for( Drawable d: it ) {
|
||||
@@ -116,7 +116,7 @@ public class Spielemaschine extends Zeichenmaschine {
|
||||
d.draw(drawing);
|
||||
}
|
||||
}
|
||||
super.draw(pGraphics);
|
||||
super.draw(graphics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,51 +1,68 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Drawable;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.shapes.Shape;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Stroke;
|
||||
|
||||
|
||||
/**
|
||||
* Basisklasse für Formen, die eine Konturlinie besitzen.
|
||||
* {@link Drawable} Klassen, die mit einer Konturlinie versehen werden können.
|
||||
* <p>
|
||||
* Das {@code Strokeable} Interface dient hauptsächlich zur Vereinheitlichung
|
||||
* der API für Konturlinien. Durch Implementation wird sichergestellt, dass alle
|
||||
* Objekte, die eine Konturlinie haben können, dieselben Methoden zur Verfügung
|
||||
* stellen. Wenn eine {@link Shape} eine
|
||||
* {@link Strokeable#setStrokeColor(Color, int)} Methode hat, dann sollte auch
|
||||
* eine {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode
|
||||
* anbieten. Im Einzelfall kann es sinnvoll sein, weitere Methoden für
|
||||
* Konturlinien zur erfügung zu stellen. Allerdings sollte davon nach
|
||||
* Möglichkeit zugunsten einer einheitlichen API abgesehen werden.
|
||||
* <p>
|
||||
* Das Äquivalent für Füllungen stellt {@link Fillable} dar.
|
||||
*/
|
||||
public abstract class StrokedShape extends Constants implements Drawable {
|
||||
public interface Strokeable extends Drawable {
|
||||
|
||||
/**
|
||||
* Aktuelle Farbe der Konturlinie oder {@code null}, wenn die Form ohne
|
||||
* kontur dargestellt werden soll.
|
||||
* Setzt den {@code Stroke} für die Konturlinie direkt.
|
||||
*
|
||||
* @param stroke Ein {@code Stroke}-Objekt.
|
||||
*/
|
||||
protected Color strokeColor = DEFAULT_STROKECOLOR;
|
||||
void setStroke( Stroke stroke );
|
||||
|
||||
/**
|
||||
* Die Dicke der Konturlinie. Wird nicht kleiner als 0.
|
||||
* Gibt ein {@code Stroke}-Objekt mit den aktuell gesetzten Eigenschaften
|
||||
* zurück.
|
||||
*
|
||||
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
||||
*/
|
||||
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||
Stroke getStroke();
|
||||
|
||||
/**
|
||||
* Die Art der Konturlinie.
|
||||
* Gibt an, ob die aktuell gesetzten Eigenschaften eine sichtbare
|
||||
* Konturlinie erzeugen.
|
||||
* <p>
|
||||
* Die Konturlinie gilt als sichtbar, wenn sie eine nicht transparente Farbe
|
||||
* und eine Dicke größer 0 besitzt.
|
||||
* <p>
|
||||
* Das bedeutet, falls die Methode {@code false} zurückgibt, dann kann
|
||||
* {@link #getStroke()} trotzdem ein gültiges {@link Stroke}-Objekt
|
||||
* zurückgeben, beispielsweise wenn keine Farbe gesetzt wurde.
|
||||
*
|
||||
* @return {@code true}, wenn die Konturlinie sichtbar ist, {@code false}
|
||||
* sonst.
|
||||
*/
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
/**
|
||||
* Cache für den aktuellen {@code Stroke} der Kontur. Wird nach Änderung
|
||||
* einer der Kontureigenschaften auf {@code null} gesetzt und beim nächsten
|
||||
* Zeichnen neu erstellt.
|
||||
*/
|
||||
protected Stroke stroke = null;
|
||||
default boolean hasStroke() {
|
||||
Color strokeColor = getStrokeColor();
|
||||
double strokeWeight = getStrokeWeight();
|
||||
return strokeColor != null && strokeColor.getAlpha() > 0 && strokeWeight > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Farbe der Konturlinie zurück.
|
||||
*
|
||||
* @return Die Konturfarbe oder {@code null}.
|
||||
*/
|
||||
public Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
Color getStrokeColor();
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe.
|
||||
@@ -53,9 +70,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
* @param color Die neue Farbe der Konturlinie.
|
||||
* @see Color
|
||||
*/
|
||||
public void setStrokeColor( Color color ) {
|
||||
this.strokeColor = color;
|
||||
}
|
||||
void setStrokeColor( Color color );
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe und setzt die
|
||||
@@ -66,7 +81,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(Color, int)
|
||||
*/
|
||||
public void setStrokeColor( Color color, int alpha ) {
|
||||
default void setStrokeColor( Color color, int alpha ) {
|
||||
setStrokeColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
@@ -77,7 +92,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @see Color#Color(int)
|
||||
*/
|
||||
public void setStrokeColor( int gray ) {
|
||||
default void setStrokeColor( int gray ) {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
@@ -90,7 +105,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(int, int)
|
||||
*/
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
default void setStrokeColor( int gray, int alpha ) {
|
||||
setStrokeColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
@@ -105,7 +120,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
default void setStrokeColor( int red, int green, int blue ) {
|
||||
setStrokeColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
@@ -121,14 +136,14 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
default void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
setStrokeColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt die Kontur der Form.
|
||||
*/
|
||||
public void noStroke() {
|
||||
default void noStroke() {
|
||||
setStrokeColor(null);
|
||||
}
|
||||
|
||||
@@ -139,10 +154,10 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
* @see schule.ngb.zm.Constants#DEFAULT_STROKEWEIGHT
|
||||
* @see schule.ngb.zm.Constants#SOLID
|
||||
*/
|
||||
public void resetStroke() {
|
||||
setStrokeColor(DEFAULT_STROKECOLOR);
|
||||
setStrokeWeight(DEFAULT_STROKEWEIGHT);
|
||||
setStrokeType(SOLID);
|
||||
default void resetStroke() {
|
||||
setStrokeColor(Constants.DEFAULT_STROKECOLOR);
|
||||
setStrokeWeight(Constants.DEFAULT_STROKEWEIGHT);
|
||||
setStrokeType(Constants.SOLID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,9 +165,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
*
|
||||
* @return Die aktuelle Dicke der Linie.
|
||||
*/
|
||||
public double getStrokeWeight() {
|
||||
return strokeWeight;
|
||||
}
|
||||
double getStrokeWeight();
|
||||
|
||||
/**
|
||||
* Setzt die Dicke der Konturlinie. Die Dicke muss größer 0 sein. Wird 0
|
||||
@@ -160,9 +173,8 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
*
|
||||
* @param weight Die Dicke der Konturlinie.
|
||||
*/
|
||||
public void setStrokeWeight( double weight ) {
|
||||
this.strokeWeight = max(0.0, weight);
|
||||
this.stroke = null;
|
||||
default void setStrokeWeight( double weight ) {
|
||||
setStroke(createStroke(getStrokeType(), weight));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,25 +183,19 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
* @return Die aktuelle Art der Konturlinie.
|
||||
* @see Options.StrokeType
|
||||
*/
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return strokeType;
|
||||
}
|
||||
Options.StrokeType getStrokeType();
|
||||
|
||||
/**
|
||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
||||
* {@link #DOTTED} und {@link #SOLID}.
|
||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link Constants#DASHED},
|
||||
* {@link Constants#DOTTED} und {@link Constants#SOLID}.
|
||||
*
|
||||
* @param type Eine der möglichen Konturarten.
|
||||
* @see Options.StrokeType
|
||||
*/
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
this.strokeType = type;
|
||||
this.stroke = null;
|
||||
default void setStrokeType( Options.StrokeType type ) {
|
||||
setStroke(createStroke(type, getStrokeWeight()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void draw( Graphics2D graphics );
|
||||
|
||||
/**
|
||||
* Hilfsmethode, um ein {@link Stroke} Objekt mit den aktuellen
|
||||
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
|
||||
@@ -197,52 +203,26 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
*
|
||||
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
||||
*/
|
||||
protected Stroke createStroke() {
|
||||
// TODO: Used global cached Stroke Objects?
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit den aktuellen
|
||||
* Kontureigenschaften auf den Grafik-Kontext zu zeichnen. Die Methode
|
||||
* verändert gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt
|
||||
* sie nicht auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
||||
*
|
||||
* @param shape Die zu zeichnende Java-AWT Form
|
||||
* @param graphics Das Grafikobjekt.
|
||||
*/
|
||||
protected void strokeShape( java.awt.Shape shape, Graphics2D graphics ) {
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.draw(shape);
|
||||
static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight ) {
|
||||
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);
|
||||
case SOLID:
|
||||
default:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.awt.geom.Point2D;
|
||||
* Der Vektor der Zeichenmaschine erweitert die Klasse {@link Point2D} und lässt
|
||||
* sich dadurch einfach mit den Klassen des {@link java.awt} Pakets benutzen.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Vector extends Point2D.Double {
|
||||
|
||||
/**
|
||||
@@ -127,7 +128,7 @@ public class Vector extends Point2D.Double {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen neuen Vektor mit derselben Richtun wie der angegebene
|
||||
* Erzeugt einen neuen Vektor mit derselben Richtung wie der angegebene
|
||||
* Vektor und der Länge 1.
|
||||
*
|
||||
* @param vector Der original Vektor.
|
||||
@@ -197,6 +198,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param y Der neue y-Wert.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector set( double x, double y ) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
@@ -210,6 +212,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param vector Ein Vektor.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector set( Vector vector ) {
|
||||
x = vector.x;
|
||||
y = vector.y;
|
||||
@@ -223,6 +226,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param pPunkt Ein Punkt.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector set( Point2D pPunkt ) {
|
||||
x = pPunkt.getX();
|
||||
x = pPunkt.getY();
|
||||
@@ -271,6 +275,7 @@ public class Vector extends Point2D.Double {
|
||||
|
||||
/**
|
||||
* Legt die Länge des Vektors fest.
|
||||
*
|
||||
* @param length Die neue Länge des Vektors.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@@ -294,9 +299,9 @@ public class Vector extends Point2D.Double {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector normalize() {
|
||||
double len = length();
|
||||
if( len != 0 && len != 1 ) {
|
||||
@@ -308,9 +313,11 @@ public class Vector extends Point2D.Double {
|
||||
|
||||
/**
|
||||
* Addiert den Vektor {@code vector} zu diesem.
|
||||
*
|
||||
* @param vector Ein anderer Vektor.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector add( Vector vector ) {
|
||||
x += vector.x;
|
||||
y += vector.y;
|
||||
@@ -319,10 +326,12 @@ public class Vector extends Point2D.Double {
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector add( double x, double y ) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
@@ -342,24 +351,28 @@ public class Vector extends Point2D.Double {
|
||||
return vec;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector sub( Vector vector ) {
|
||||
x -= vector.x;
|
||||
y -= vector.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector sub( double x, double y ) {
|
||||
this.x -= x;
|
||||
this.y -= y;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector scale( double scalar ) {
|
||||
x *= scalar;
|
||||
y *= scalar;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector div( double scalar ) {
|
||||
if( scalar == 0.0 ) {
|
||||
throw new IllegalArgumentException("Can't divide by zero.");
|
||||
@@ -487,6 +500,7 @@ public class Vector extends Point2D.Double {
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #setLength(double)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector limit( double max ) {
|
||||
if( lengthSq() > max * max ) {
|
||||
setLength(max);
|
||||
@@ -503,6 +517,7 @@ public class Vector extends Point2D.Double {
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #setLength(double)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector limit( double min, double max ) {
|
||||
if( min > max ) {
|
||||
double t = min;
|
||||
@@ -556,6 +571,7 @@ public class Vector extends Point2D.Double {
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #rotate(double)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector rotateRad( double rad ) {
|
||||
double temp = x;
|
||||
x = x * Math.cos(rad) - y * Math.sin(rad);
|
||||
@@ -587,6 +603,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param t Ein Wert zwischen 0 und 1.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector morph( Vector vector, double t ) {
|
||||
double tt = Math.min(Math.max(t, 0.0), 1.0);
|
||||
x = x + (vector.x - x) * tt;
|
||||
@@ -631,6 +648,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param t Ein Wert zwischen 0 und 1.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector interpolate( Vector vector, double t ) {
|
||||
x = x + (vector.x - x) * t;
|
||||
y = y + (vector.y - y) * t;
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -60,8 +61,8 @@ public class Zeichenfenster extends JFrame {
|
||||
private GraphicsDevice displayDevice;
|
||||
|
||||
/**
|
||||
* Bevorzugte Abmessungen der Zeichenleinwand. Für das Zeichenfenster hat
|
||||
* es Priorität die Leinwand auf dieser Größe zu halten. Gegebenenfalls unter
|
||||
* Bevorzugte Abmessungen der Zeichenleinwand. Für das Zeichenfenster hat es
|
||||
* Priorität die Leinwand auf dieser Größe zu halten. Gegebenenfalls unter
|
||||
* Missachtung anderer Größenvorgaben. Allerdings kann das Fenster keine
|
||||
* Garantie für die Größe der Leinwand übernehmen.
|
||||
*/
|
||||
@@ -78,12 +79,13 @@ public class Zeichenfenster extends JFrame {
|
||||
* verlassen. Wird von {@link #setFullscreen(boolean)} automatisch
|
||||
* hinzugefügt und entfernt.
|
||||
*/
|
||||
private KeyListener fullscreenExitListener = new KeyAdapter() {
|
||||
private final KeyListener fullscreenExitListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed( KeyEvent e ) {
|
||||
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
|
||||
// canvas.removeKeyListener(this);
|
||||
setFullscreen(false);
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -110,12 +112,11 @@ public class Zeichenfenster extends JFrame {
|
||||
|
||||
this.canvasPreferredWidth = canvas.getWidth();
|
||||
this.canvasPreferredHeight = canvas.getHeight();
|
||||
//this.add(canvas, BorderLayout.CENTER);
|
||||
this.add(canvas);
|
||||
this.add(canvas, BorderLayout.CENTER);
|
||||
this.canvas = canvas;
|
||||
|
||||
// Konfiguration des Frames
|
||||
this.setTitle(title == null ? "Zeichenfenster " + Constants.APP_VERSION: title);
|
||||
this.setTitle(title == null ? "Zeichenfenster " + Constants.APP_VERSION : title);
|
||||
// Kann vom Aufrufenden überschrieben werden
|
||||
this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
|
||||
@@ -129,7 +130,7 @@ public class Zeichenfenster extends JFrame {
|
||||
taskbar.setIconImage(icon);
|
||||
} else {
|
||||
ArrayList<Image> icons = new ArrayList<>(4);
|
||||
for( int size = 32; size <= 512; size *= size ) {
|
||||
for( int size: new int[]{32, 64, 128, 512} ) {
|
||||
icons.add(ImageIO.read(new File("icon_" + size + ".png")));
|
||||
}
|
||||
|
||||
@@ -144,6 +145,7 @@ public class Zeichenfenster extends JFrame {
|
||||
// Fenster zusammenbauen, auf dem Bildschirm positionieren ...
|
||||
this.pack();
|
||||
this.setResizable(false);
|
||||
this.setFocusable(true);
|
||||
this.setLocationByPlatform(true);
|
||||
// this.centerFrame();
|
||||
}
|
||||
@@ -153,6 +155,7 @@ public class Zeichenfenster extends JFrame {
|
||||
}
|
||||
|
||||
public Rectangle getScreenBounds() {
|
||||
// return GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
|
||||
return displayDevice.getDefaultConfiguration().getBounds();
|
||||
}
|
||||
|
||||
@@ -163,6 +166,7 @@ public class Zeichenfenster extends JFrame {
|
||||
public Rectangle getCanvasBounds() {
|
||||
return canvas.getBounds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
|
||||
*/
|
||||
@@ -176,7 +180,7 @@ public class Zeichenfenster extends JFrame {
|
||||
}
|
||||
|
||||
public void setCanvasSize( int newWidth, int newHeight ) {
|
||||
// TODO: (ngb) Put constains on max/min frame/canvas size
|
||||
// TODO: (ngb) Put constrains on max/min frame/canvas size
|
||||
if( fullscreen ) {
|
||||
canvasPreferredWidth = newWidth;
|
||||
canvasPreferredHeight = newHeight;
|
||||
@@ -205,27 +209,53 @@ public class Zeichenfenster extends JFrame {
|
||||
public final void setFullscreen( boolean pEnable ) {
|
||||
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
|
||||
if( displayDevice.isFullScreenSupported() ) {
|
||||
// Temporarily stop rendering
|
||||
while( canvas.isRendering() ) {
|
||||
try {
|
||||
canvas.suspendRendering();
|
||||
} catch( InterruptedException ex ) {
|
||||
LOG.info(ex, "setFullsceen(true) was interrupted and canceled.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if( pEnable && !fullscreen ) {
|
||||
// frame.setUndecorated(true);
|
||||
this.setResizable(false); // Should be set anyway
|
||||
// Activate fullscreen
|
||||
dispose();
|
||||
setUndecorated(true);
|
||||
setResizable(false);
|
||||
displayDevice.setFullScreenWindow(this);
|
||||
|
||||
java.awt.Rectangle bounds = getScreenBounds();
|
||||
// TODO: (ngb) We need to switch layouts to allow the LayoutManger to maximize the canvas
|
||||
canvas.setSize(bounds.width, bounds.height);
|
||||
// Register ESC to exit fullscreen
|
||||
canvas.addKeyListener(fullscreenExitListener);
|
||||
|
||||
// Reset canvas size to its new bounds to recreate buffer and drawing surface
|
||||
java.awt.Rectangle canvasBounds = getCanvasBounds();
|
||||
canvas.setSize(canvasBounds.width, canvasBounds.height);
|
||||
//canvas.requestFocus();
|
||||
canvas.requestFocus();
|
||||
|
||||
fullscreen = true;
|
||||
} else if( !pEnable && fullscreen ) {
|
||||
fullscreen = false;
|
||||
displayDevice.setFullScreenWindow(null);
|
||||
dispose();
|
||||
setUndecorated(false);
|
||||
setResizable(false);
|
||||
|
||||
canvas.removeKeyListener(fullscreenExitListener);
|
||||
displayDevice.setFullScreenWindow(null);
|
||||
canvas.setSize(canvasPreferredWidth, canvasPreferredHeight);
|
||||
this.pack();
|
||||
// frame.setUndecorated(false);
|
||||
|
||||
setVisible(true);
|
||||
pack();
|
||||
|
||||
//canvas.requestFocus();
|
||||
canvas.requestFocus();
|
||||
|
||||
fullscreen = false;
|
||||
}
|
||||
|
||||
// Resume rendering
|
||||
canvas.resumeRendering();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.layers.ColorLayer;
|
||||
import schule.ngb.zm.layers.ShapesLayer;
|
||||
import schule.ngb.zm.util.Log;
|
||||
|
||||
import java.awt.*;
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
|
||||
@@ -24,10 +22,14 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
*/
|
||||
public class Zeichenleinwand extends Canvas {
|
||||
|
||||
private final Object[] renderLock = new Object[0];
|
||||
|
||||
/**
|
||||
* Liste der hinzugefügten Ebenen.
|
||||
*/
|
||||
private LinkedList<Layer> layers;
|
||||
private final List<Layer> layers;
|
||||
|
||||
private boolean rendering = false, suspended = false;
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Zeichenleinwand mit einer festen Größe.
|
||||
@@ -41,13 +43,47 @@ public class Zeichenleinwand extends Canvas {
|
||||
this.setMinimumSize(getSize());
|
||||
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
|
||||
|
||||
// Liste der Ebenen initialisieren und die Standardebenen einfügen
|
||||
layers = new LinkedList<>();
|
||||
synchronized( layers ) {
|
||||
layers.add(new ColorLayer(width, height, Constants.DEFAULT_BACKGROUND));
|
||||
layers = Collections.synchronizedList(new LinkedList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ob die Leinwand ihren Inhalt gerade zeichnet.
|
||||
*
|
||||
* @return {@code true}, wenn die Inhalte gerade gezeichnet werden.
|
||||
*/
|
||||
public boolean isRendering() {
|
||||
return rendering;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pausiert das Zeichnen der Leinwand kurzzeitig.
|
||||
* <p>
|
||||
* Falls die Leinwand gerade beim Zeichnen ist
|
||||
* ({@code isRendering() == true}, blockt die Methode den aufrufenden Thread
|
||||
* so lange, bis das Rendern beendet ist. Danach wird die Ebene nicht mehr
|
||||
* neu gezeichnet, bis {@link #resumeRendering()} aufgerufen wird.
|
||||
* <p>
|
||||
* Das Zeichnen sollte nur dann unterbrochen werden, wenn sich der Kontext
|
||||
* der Canvas-Komponente in seinem Elterncontainer ändert, um Fehler bei
|
||||
* einer fehlenden Container-Hierarchie zu vermeiden.
|
||||
*
|
||||
* @throws InterruptedException Falls der Thread beim Warten unterbrochen
|
||||
* wird.
|
||||
*/
|
||||
public void suspendRendering() throws InterruptedException {
|
||||
synchronized( renderLock ) {
|
||||
if( isRendering() ) {
|
||||
renderLock.wait();
|
||||
}
|
||||
|
||||
suspended = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void resumeRendering() {
|
||||
suspended = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ändert die Größe der Zeichenleinwand auf die angegebene Größe in Pixeln.
|
||||
* <p>
|
||||
@@ -78,32 +114,28 @@ public class Zeichenleinwand extends Canvas {
|
||||
*/
|
||||
public void addLayer( Layer layer ) {
|
||||
if( layer != null ) {
|
||||
synchronized( layers ) {
|
||||
layer.setSize(getWidth(), getHeight());
|
||||
layers.add(layer);
|
||||
}
|
||||
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
|
||||
* {@link #getLayerCount() Anzahl der Ebenen} ist), dann wird die neue Ebene
|
||||
* als letzte eingefügt. Die aufrufende Methode kann also nicht sicher sein,
|
||||
* dass die neue Ebene am Ende wirklich am Index {@code i} steht.
|
||||
*
|
||||
* @param i Index der Ebene, beginnend mit 0.
|
||||
* @param i Index der Ebene beginnend mit 0.
|
||||
* @param layer Die neue Ebene.
|
||||
*/
|
||||
public void addLayer( int i, Layer layer ) {
|
||||
if( layer != null ) {
|
||||
synchronized( layers ) {
|
||||
layer.setSize(getWidth(), getHeight());
|
||||
if( i > layers.size() ) {
|
||||
layers.add(layer);
|
||||
} else {
|
||||
layers.add(i, layer);
|
||||
}
|
||||
layer.setSize(getWidth(), getHeight());
|
||||
if( i > layers.size() ) {
|
||||
layers.add(layer);
|
||||
} else {
|
||||
layers.add(i, layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,19 +150,19 @@ public class Zeichenleinwand extends Canvas {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Liste der bisher hinzugefügten Ebenen zurück.
|
||||
* Gibt eine Kopie der Liste der bisher hinzugefügten Ebenen zurück.
|
||||
*
|
||||
* @return Liste der Ebenen.
|
||||
*/
|
||||
public java.util.List<Layer> getLayers() {
|
||||
return layers;
|
||||
public List<Layer> getLayers() {
|
||||
return List.copyOf(layers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt die Ebene am Index <var>i</var> (beginnend bei 0).
|
||||
* Holt die Ebene am Index {@code i} (beginnend bei 0).
|
||||
*
|
||||
* @param i Index der Ebene (beginnend bei 0).
|
||||
* @return Die Ebene am Index <var>i</var> oder {@code null}.
|
||||
* @return Die Ebene am Index {@code i} oder {@code null}.
|
||||
* @throws IndexOutOfBoundsException Falls der Index nicht existiert.
|
||||
*/
|
||||
public Layer getLayer( int i ) {
|
||||
@@ -145,14 +177,16 @@ public class Zeichenleinwand extends Canvas {
|
||||
* Sucht die erste Ebene des angegebenen Typs aus der Liste der Ebenen.
|
||||
* Existiert keine solche Ebene, wird {@code null} zurückgegeben.
|
||||
*
|
||||
* @param clazz Typ der Ebene.
|
||||
* @param <L>
|
||||
* @param type Klasse der Ebenen, die abgefragt werden.
|
||||
* @param <L> Typ der Ebenen, die abgefragt werden.
|
||||
* @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);
|
||||
public <L extends Layer> L getLayer( Class<L> type ) {
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
if( layer.getClass().equals(type) ) {
|
||||
return type.cast(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -163,44 +197,41 @@ public class Zeichenleinwand extends Canvas {
|
||||
* 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
|
||||
* @param type Klasse der Ebenen, die abgefragt werden.
|
||||
* @param <L> Typ der Ebenen, die abgefragt werden.
|
||||
* @return Eine Liste mit den vorhandenen Ebenen des abgefragten Typs.
|
||||
*/
|
||||
public <L extends Layer> java.util.List<L> getLayers( Class<L> pClazz ) {
|
||||
public <L extends Layer> List<L> getLayers( Class<L> type ) {
|
||||
ArrayList<L> result = new ArrayList<>(layers.size());
|
||||
for( Layer layer : layers ) {
|
||||
if( layer.getClass().equals(pClazz) ) {
|
||||
result.add(pClazz.cast(layer));
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
if( layer.getClass().equals(type) ) {
|
||||
result.add(type.cast(layer));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean removeLayer( Layer pLayer ) {
|
||||
synchronized( layers ) {
|
||||
return layers.remove(pLayer);
|
||||
}
|
||||
return layers.remove(pLayer);
|
||||
}
|
||||
|
||||
public void removeLayers( Layer... pLayers ) {
|
||||
public void removeLayers( Layer... removeLayers ) {
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : pLayers ) {
|
||||
for( Layer layer : removeLayers ) {
|
||||
layers.remove(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clearLayers() {
|
||||
synchronized( layers ) {
|
||||
layers.clear();
|
||||
}
|
||||
layers.clear();
|
||||
}
|
||||
|
||||
public void updateLayers( double delta ) {
|
||||
synchronized( layers ) {
|
||||
List<Layer> it = List.copyOf(layers);
|
||||
for( Layer layer : it ) {
|
||||
for( Layer layer : List.copyOf(layers) ) {
|
||||
layer.update(delta);
|
||||
}
|
||||
}
|
||||
@@ -237,8 +268,7 @@ public class Zeichenleinwand extends Canvas {
|
||||
public void draw( Graphics graphics ) {
|
||||
Graphics2D g2d = (Graphics2D) graphics.create();
|
||||
synchronized( layers ) {
|
||||
List<Layer> it = List.copyOf(layers);
|
||||
for( Layer layer : it ) {
|
||||
for( Layer layer : layers ) {
|
||||
layer.draw(g2d);
|
||||
}
|
||||
}
|
||||
@@ -249,37 +279,41 @@ public class Zeichenleinwand extends Canvas {
|
||||
* Zeigt den aktuellen Inhalt der Zeichenleinwand an.
|
||||
*/
|
||||
public void render() {
|
||||
if( getBufferStrategy() == null ) {
|
||||
allocateBuffer();
|
||||
}
|
||||
if( !suspended && isDisplayable() ) {
|
||||
if( getBufferStrategy() == null ) {
|
||||
allocateBuffer();
|
||||
}
|
||||
|
||||
if( isDisplayable() ) {
|
||||
BufferStrategy strategy = this.getBufferStrategy();
|
||||
if( strategy != null ) {
|
||||
do {
|
||||
synchronized( renderLock ) {
|
||||
rendering = true;
|
||||
BufferStrategy strategy = this.getBufferStrategy();
|
||||
if( strategy != null ) {
|
||||
do {
|
||||
Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
|
||||
g2d.clearRect(0, 0, getWidth(), getHeight());
|
||||
do {
|
||||
Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
|
||||
g2d.clearRect(0, 0, getWidth(), getHeight());
|
||||
|
||||
synchronized( layers ) {
|
||||
List<Layer> it = List.copyOf(layers);
|
||||
for( Layer layer : it ) {
|
||||
layer.draw(g2d);
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : List.copyOf(layers) ) {
|
||||
layer.draw(g2d);
|
||||
}
|
||||
}
|
||||
|
||||
g2d.dispose();
|
||||
} while( strategy.contentsRestored() );
|
||||
|
||||
// Display the buffer
|
||||
if( !strategy.contentsLost() ) {
|
||||
strategy.show();
|
||||
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
}
|
||||
|
||||
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() );
|
||||
// Repeat the rendering if the drawing buffer was lost
|
||||
} while( strategy.contentsLost() );
|
||||
}
|
||||
rendering = false;
|
||||
renderLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Hauptklasse der Zeichenmaschine.
|
||||
@@ -37,43 +38,6 @@ public class Zeichenmaschine extends Constants {
|
||||
IN_BLUEJ = System.getProperty("java.class.path").contains("bluej");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter macOS gestartet wurde.
|
||||
*/
|
||||
public static final boolean MACOS;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter Windows gestartet wurde.
|
||||
*/
|
||||
public static final boolean WINDOWS;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter Linux gestartet wurde.
|
||||
*/
|
||||
public static final boolean LINUX;
|
||||
|
||||
static {
|
||||
final String name = System.getProperty("os.name");
|
||||
|
||||
if( name.contains("Mac") ) {
|
||||
MACOS = true;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
} else if( name.contains("Windows") ) {
|
||||
MACOS = false;
|
||||
WINDOWS = true;
|
||||
LINUX = false;
|
||||
} else if( name.equals("Linux") ) { // true for the ibm vm
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = true;
|
||||
} else {
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Objektvariablen, die von Unterklassen benutzt werden können.
|
||||
*/
|
||||
@@ -111,14 +75,14 @@ public class Zeichenmaschine extends Constants {
|
||||
/**
|
||||
* Das Zeichenfenster der Zeichenmaschine
|
||||
*/
|
||||
private Zeichenfenster frame;
|
||||
protected Zeichenfenster frame;
|
||||
|
||||
// Aktueller Zustand der Zeichenmaschine.
|
||||
|
||||
/**
|
||||
* Zustand der Zeichenmaschine insgesamt
|
||||
*/
|
||||
private Options.AppState state = Options.AppState.INITIALIZING;
|
||||
private Options.AppState state;
|
||||
|
||||
/**
|
||||
* Zustand des update/draw Threads
|
||||
@@ -128,7 +92,7 @@ public class Zeichenmaschine extends Constants {
|
||||
/**
|
||||
* Ob der Zeichenthread noch laufen soll, oder beendet.
|
||||
*/
|
||||
private boolean running = false;
|
||||
private boolean running;
|
||||
|
||||
private boolean terminateImediately = false;
|
||||
|
||||
@@ -141,7 +105,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* Ob die ZM bei nicht überschriebener update() Methode stoppen soll,
|
||||
* oder trotzdem weiterläuft.
|
||||
*/
|
||||
private boolean run_once = true;
|
||||
private final boolean run_once;
|
||||
|
||||
/**
|
||||
* Aktuelle Frames pro Sekunde der Zeichenmaschine.
|
||||
@@ -151,17 +115,17 @@ public class Zeichenmaschine extends Constants {
|
||||
/**
|
||||
* Hauptthread der Zeichenmaschine.
|
||||
*/
|
||||
private Thread mainThread;
|
||||
private final Thread mainThread;
|
||||
|
||||
/**
|
||||
* Queue für geplante Aufgaben
|
||||
*/
|
||||
private DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
|
||||
private final DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
|
||||
|
||||
/**
|
||||
* Queue für abgefangene InputEvents
|
||||
*/
|
||||
private BlockingQueue<InputEvent> eventQueue = new LinkedBlockingQueue<>();
|
||||
private final BlockingQueue<InputEvent> eventQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
/**
|
||||
* Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
|
||||
@@ -274,9 +238,15 @@ public class Zeichenmaschine extends Constants {
|
||||
* @param run_once {@code true} beendet die Zeichenmaschine nach einem
|
||||
* Aufruf von {@code draw()}.
|
||||
*/
|
||||
@SuppressWarnings("static-access")
|
||||
public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
|
||||
LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
|
||||
|
||||
// Register Cmd+Q on macOS
|
||||
if( Constants.MACOS ) {
|
||||
System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");
|
||||
}
|
||||
|
||||
// Erstellen der Leinwand
|
||||
canvas = new Zeichenleinwand(width, height);
|
||||
|
||||
@@ -312,8 +282,15 @@ public class Zeichenmaschine extends Constants {
|
||||
canvas.addMouseWheelListener(inputListener);
|
||||
canvas.addKeyListener(inputListener);
|
||||
|
||||
/*KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
|
||||
@Override
|
||||
public boolean dispatchKeyEvent( KeyEvent e ) {
|
||||
enqueueEvent(e);
|
||||
return false;
|
||||
}
|
||||
});*/
|
||||
|
||||
// Programm beenden, wenn Fenster geschlossen wird
|
||||
// TODO: (ngb) Der Listener hat zu viel FUnktionalität -> nach quit() / exit() auslagern
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing( WindowEvent e ) {
|
||||
@@ -350,17 +327,15 @@ public class Zeichenmaschine extends Constants {
|
||||
*
|
||||
* @param title
|
||||
*/
|
||||
private final Zeichenfenster createFrame( Zeichenleinwand c, String title ) {
|
||||
private Zeichenfenster createFrame( Zeichenleinwand c, String title ) {
|
||||
while( frame == null ) {
|
||||
try {
|
||||
TaskRunner.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Zeichenfenster.setLookAndFeel();
|
||||
frame = new Zeichenfenster(canvas, title);
|
||||
}
|
||||
TaskRunner.invokeLater(() -> {
|
||||
Zeichenfenster.setLookAndFeel();
|
||||
frame = new Zeichenfenster(canvas, title);
|
||||
}).get();
|
||||
} catch( InterruptedException e ) {
|
||||
// Keep waiting
|
||||
} catch( ExecutionException e ) {
|
||||
LOG.error(e, "Error initializing application frame: %s", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
@@ -392,10 +367,18 @@ public class Zeichenmaschine extends Constants {
|
||||
public final void setFullscreen( boolean pEnable ) {
|
||||
if( pEnable && !frame.isFullscreen() ) {
|
||||
frame.setFullscreen(true);
|
||||
|
||||
canvasWidth = canvas.getWidth();
|
||||
canvasHeight = canvas.getHeight();
|
||||
|
||||
if( frame.isFullscreen() )
|
||||
fullscreenChanged();
|
||||
} else if( !pEnable && frame.isFullscreen() ) {
|
||||
frame.setFullscreen(false);
|
||||
|
||||
canvasWidth = canvas.getWidth();
|
||||
canvasHeight = canvas.getHeight();
|
||||
|
||||
if( !frame.isFullscreen() )
|
||||
fullscreenChanged();
|
||||
}
|
||||
@@ -444,9 +427,18 @@ public class Zeichenmaschine extends Constants {
|
||||
* im Zeichenfenster an.
|
||||
*/
|
||||
public final void redraw() {
|
||||
if( state == Options.AppState.PAUSED ) {
|
||||
if( state == Options.AppState.PAUSED
|
||||
|| state == Options.AppState.TERMINATED ) {
|
||||
draw();
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt den aktuellen Inhalt der {@link Zeichenleinwand}
|
||||
* im Zeichenfenster an, ohne vorher {@link #draw()} aufzurufen.
|
||||
*/
|
||||
public final void render() {
|
||||
canvas.render();
|
||||
// canvas.invalidate();
|
||||
// frame.repaint();
|
||||
@@ -546,6 +538,11 @@ public class Zeichenmaschine extends Constants {
|
||||
}
|
||||
|
||||
public final void exitNow() {
|
||||
// Do nothing, when already quitting
|
||||
if( state == Options.AppState.QUITING ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( running ) {
|
||||
running = false;
|
||||
terminateImediately = true;
|
||||
@@ -584,13 +581,16 @@ public class Zeichenmaschine extends Constants {
|
||||
* @see System#exit(int)
|
||||
*/
|
||||
public final void quit( boolean exit ) {
|
||||
frame.setVisible(false);
|
||||
canvas.dispose();
|
||||
frame.dispose();
|
||||
state = Options.AppState.QUITING;
|
||||
TaskRunner.invokeLater(() -> {
|
||||
frame.setVisible(false);
|
||||
canvas.dispose();
|
||||
frame.dispose();
|
||||
|
||||
if( exit ) {
|
||||
System.exit(0);
|
||||
}
|
||||
if( exit ) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -601,11 +601,11 @@ public class Zeichenmaschine extends Constants {
|
||||
* @see Zeichenleinwand#setSize(int, int)
|
||||
*/
|
||||
public final void setSize( int width, int height ) {
|
||||
frame.setSize(width, height);
|
||||
frame.setCanvasSize(width, height);
|
||||
|
||||
java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
|
||||
canvasWidth = (int) canvasBounds.getWidth();
|
||||
canvasHeight = (int) canvasBounds.getHeight();
|
||||
canvasWidth = canvasBounds.width;
|
||||
canvasHeight = canvasBounds.height;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1100,6 +1100,9 @@ public class Zeichenmaschine extends Constants {
|
||||
}
|
||||
|
||||
if( isPaused() || isTerminated() ) {
|
||||
if( MouseEvent.class.isInstance(evt) ) {
|
||||
saveMousePosition((MouseEvent)evt);
|
||||
}
|
||||
dispatchEvents();
|
||||
}
|
||||
}
|
||||
@@ -1327,7 +1330,7 @@ public class Zeichenmaschine extends Constants {
|
||||
long overslept = 0L;
|
||||
// Interne Zähler für tick und runtime
|
||||
int _tick = 0;
|
||||
long _runtime = 0;
|
||||
long _runtime;
|
||||
// Öffentliche Zähler für Unterklassen
|
||||
tick = 0;
|
||||
runtime = 0;
|
||||
@@ -1390,7 +1393,7 @@ public class Zeichenmaschine extends Constants {
|
||||
}
|
||||
|
||||
// Display the current buffer content
|
||||
if( canvas != null ) {
|
||||
if( canvas != null && frame.isDisplayable() ) {
|
||||
canvas.render();
|
||||
// canvas.invalidate();
|
||||
// frame.repaint();
|
||||
@@ -1458,7 +1461,7 @@ public class Zeichenmaschine extends Constants {
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
class DelayedTask implements Delayed {
|
||||
static class DelayedTask implements Delayed {
|
||||
|
||||
long startTime; // in ms
|
||||
|
||||
@@ -1547,6 +1550,7 @@ public class Zeichenmaschine extends Constants {
|
||||
|
||||
}
|
||||
|
||||
// TODO: (ngb) exception handling when update/draw throws ex
|
||||
class UpdateThreadExecutor extends ThreadPoolExecutor {
|
||||
|
||||
private Thread updateThread;
|
||||
@@ -1555,7 +1559,18 @@ public class Zeichenmaschine extends Constants {
|
||||
|
||||
public UpdateThreadExecutor() {
|
||||
super(1, 1, 0L,
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new ThreadFactory() {
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
@Override
|
||||
public Thread newThread( Runnable r ) {
|
||||
Thread t = new Thread(mainThread.getThreadGroup(), r,
|
||||
"updateThread-" + threadNumber.getAndIncrement(),
|
||||
0);
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
updateState = Options.AppState.IDLE;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,13 @@ import java.awt.RadialGradientPaint;
|
||||
/**
|
||||
* Eine Ebene, die nur aus einer Farbe (oder einem Farbverlauf) besteht.
|
||||
* <p>
|
||||
* Ein {@code ColorLayer} ist eine der drei Standardebenen der
|
||||
* {@link schule.ngb.zm.Zeichenmaschine}.
|
||||
* <p>
|
||||
* Die Farbe der Ebene kann beliebig gesetzt werden und kann gut als
|
||||
* Hintergundfarbe für eine Szene dienen, oder als halbtransparente "Abdeckung",
|
||||
* wenn ein {@code ColorLayer} über den anderen Ebenen eingefügt wird.
|
||||
* Hintergrundfarbe für eine Szene dienen, oder als halbtransparente
|
||||
* "Abdeckung", wenn ein {@code ColorLayer} über den anderen Ebenen eingefügt
|
||||
* wird.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class ColorLayer extends Layer {
|
||||
|
||||
@@ -8,55 +8,102 @@ import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ein Layer um {@link Drawable} Objekte zu zeichnen.
|
||||
* <p>
|
||||
* Objekte, die das {@code Drawable} Interface implementieren, können der Ebene
|
||||
* hinzugefügt werden. Die Ebene sorgt dafür, dass alle {@code Drawable}s einmal
|
||||
* pro Frame über ihre {@link Drawable#draw(Graphics2D)} Methode gezeichnet.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class DrawableLayer extends Layer {
|
||||
|
||||
protected List<Drawable> drawables = new LinkedList<>();
|
||||
/**
|
||||
* Liste der {@link Drawable}s.
|
||||
*/
|
||||
protected final List<Drawable> drawables;
|
||||
|
||||
/**
|
||||
* Ob die Ebene bei jedem Aufruf von {@link #draw(Graphics2D)} geleert
|
||||
* werden soll.
|
||||
*/
|
||||
protected boolean clearBeforeDraw = true;
|
||||
|
||||
/**
|
||||
* Erstellt eine Ebene in der Standardgröße.
|
||||
*/
|
||||
public DrawableLayer() {
|
||||
drawables = new LinkedList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Ebene mit der angegebenen Größe.
|
||||
*
|
||||
* @param width Die Breite der Ebene.
|
||||
* @param height Die Höhe der Ebene.
|
||||
*/
|
||||
public DrawableLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
drawables = new LinkedList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt alle angegebenen {@code Drawable}s der Ebene hinzu.
|
||||
*
|
||||
* @param drawables Die {@code Drawable} Objekte.
|
||||
*/
|
||||
public void add( Drawable... drawables ) {
|
||||
synchronized( drawables ) {
|
||||
for( Drawable d : drawables ) {
|
||||
this.drawables.add(d);
|
||||
}
|
||||
synchronized( this.drawables ) {
|
||||
Collections.addAll(this.drawables, drawables);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt eine Liste aller {@code Drawable} Objekte dieser Ebene zurück.
|
||||
*
|
||||
* @return Die Liste der {@code Drawable} Objekte.
|
||||
*/
|
||||
public java.util.List<Drawable> getDrawables() {
|
||||
return drawables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ob die Ebene bei jedem Frame automatisch gelöscht wird.
|
||||
*
|
||||
* @return {@code true}, wenn die Ebene vorm Zeichnen gelöscht wird,
|
||||
* {@code false} sonst.
|
||||
*/
|
||||
public boolean isClearBeforeDraw() {
|
||||
return clearBeforeDraw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stellt ein, ob die Ebene vorm Zeichnen gelöscht werden soll.
|
||||
*
|
||||
* @param pClearBeforeDraw Ob die Ebene vorm Zeichnen gelöscht werden
|
||||
* soll.
|
||||
*/
|
||||
public void setClearBeforeDraw( boolean pClearBeforeDraw ) {
|
||||
this.clearBeforeDraw = pClearBeforeDraw;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
if( clearBeforeDraw ) {
|
||||
clear();
|
||||
}
|
||||
|
||||
synchronized( drawables ) {
|
||||
List<Drawable> it = List.copyOf(drawables);
|
||||
for( Drawable d : it ) {
|
||||
if( d.isVisible() ) {
|
||||
d.draw(drawing);
|
||||
}
|
||||
List<Drawable> it = List.copyOf(drawables);
|
||||
for( Drawable d : it ) {
|
||||
if( d.isVisible() ) {
|
||||
d.draw(drawing);
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(pGraphics);
|
||||
super.draw(graphics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,161 +1,424 @@
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.Fillable;
|
||||
import schule.ngb.zm.Strokeable;
|
||||
import schule.ngb.zm.shapes.Text;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
import java.util.Stack;
|
||||
|
||||
public class DrawingLayer extends Layer {
|
||||
|
||||
protected schule.ngb.zm.Color fillColor = DEFAULT_FILLCOLOR;
|
||||
|
||||
protected schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||
|
||||
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
private Options.Direction default_anchor = CENTER;
|
||||
/**
|
||||
* Eine Ebene auf der direkt gezeichnet werden kann.
|
||||
* <p>
|
||||
* Ein {@code DrawingLayer} ist eine der drei Standardebenen der
|
||||
* {@link schule.ngb.zm.Zeichenmaschine}.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
||||
|
||||
/**
|
||||
* Wiederverwendbarer Speicher für eine Linie.
|
||||
*/
|
||||
protected Line2D.Double line = new Line2D.Double();
|
||||
|
||||
/**
|
||||
* Wiederverwendbarer Speicher für eine Ellipse.
|
||||
*/
|
||||
protected Ellipse2D.Double ellipse = new Ellipse2D.Double();
|
||||
|
||||
/**
|
||||
* Wiederverwendbarer Speicher für ein Rechteck.
|
||||
*/
|
||||
protected Rectangle2D.Double rect = new Rectangle2D.Double();
|
||||
|
||||
/**
|
||||
* Wiederverwendbarer Speicher für einen Kreisbogen.
|
||||
*/
|
||||
protected Arc2D.Double arc = new Arc2D.Double();
|
||||
|
||||
/**
|
||||
* Wiederverwendbarer Speicher für einen Pfad.
|
||||
*/
|
||||
protected Path2D.Double path = new Path2D.Double();
|
||||
|
||||
/**
|
||||
* Ob ein individueller Pfad gestartet wurde.
|
||||
*/
|
||||
private boolean pathStarted = false;
|
||||
|
||||
private Stack<AffineTransform> transformStack = new Stack<>();
|
||||
/**
|
||||
* Stapel für zwischengespeicherte Transformationsmatrizen.
|
||||
*/
|
||||
private final Stack<AffineTransform> transformStack;
|
||||
// private FontMetrics fontMetrics;
|
||||
|
||||
private FontMetrics fontMetrics = null;
|
||||
/**
|
||||
* Eine Shape, an die die Methoden für Farben und Konturlinien delegiert
|
||||
* werden.
|
||||
*/
|
||||
private schule.ngb.zm.shapes.Text shapeDelegate;
|
||||
|
||||
private final Stack<schule.ngb.zm.shapes.Text> styleStack;
|
||||
|
||||
/**
|
||||
* Erstellt eine Ebene in der Standardgröße.
|
||||
*/
|
||||
public DrawingLayer() {
|
||||
super();
|
||||
transformStack = new Stack<>();
|
||||
transformStack.push(new AffineTransform());
|
||||
fontMetrics = drawing.getFontMetrics();
|
||||
// fontMetrics = drawing.getFontMetrics();
|
||||
|
||||
styleStack = new Stack<>();
|
||||
resetStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Ebene mit der angegebenen Größe.
|
||||
*
|
||||
* @param width Die Breite der Ebene.
|
||||
* @param height Die Höhe der Ebene.
|
||||
*/
|
||||
public DrawingLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
transformStack = new Stack<>();
|
||||
transformStack.push(new AffineTransform());
|
||||
fontMetrics = drawing.getFontMetrics();
|
||||
// fontMetrics = drawing.getFontMetrics();
|
||||
|
||||
styleStack = new Stack<>();
|
||||
resetStyle();
|
||||
}
|
||||
|
||||
public schule.ngb.zm.Color getColor() {
|
||||
return fillColor;
|
||||
/**
|
||||
* Gibt die aktuelle Füllfarbe zurück.
|
||||
*
|
||||
* @return Die aktuelle Füllfarbe.
|
||||
*/
|
||||
public Color getFillColor() {
|
||||
return shapeDelegate.getFillColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die angegebene Farbe.
|
||||
*
|
||||
* @param color Die neue Füllfarbe oder {@code null}.
|
||||
* @see Color
|
||||
*/
|
||||
public void setFillColor( Color color ) {
|
||||
shapeDelegate.setFillColor(color);
|
||||
drawing.setPaint(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die angegebene Farbe und setzt die Transparenz
|
||||
* auf den angegebenen Wert. 0 is komplett durchsichtig und 255 komplett
|
||||
* deckend.
|
||||
*
|
||||
* @param color Die neue Füllfarbe oder {@code null}.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(Color, int)
|
||||
*/
|
||||
public void setFillColor( Color color, int alpha ) {
|
||||
setFillColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität. 0
|
||||
* entspricht schwarz, 255 entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @see Color#Color(int)
|
||||
*/
|
||||
public void setFillColor( int gray ) {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( schule.ngb.zm.Color color ) {
|
||||
fillColor = color;
|
||||
drawing.setColor(color.getJavaColor());
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
fillColor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität und
|
||||
* dem angegebenen Transparenzwert. Der Grauwert 0 entspricht schwarz, 255
|
||||
* entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(int, int)
|
||||
*/
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
setFillColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||
* Blauanteilen.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @see Color#Color(int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
setFillColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||
* Blauanteilen und dem angegebenen Transparenzwert.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
||||
* @see Color#Color(int, int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new schule.ngb.zm.Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public schule.ngb.zm.Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
/**
|
||||
* Entfernt die Füllung der Form.
|
||||
*/
|
||||
public void noFill() {
|
||||
shapeDelegate.noFill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf den Standardwert zurück.
|
||||
*
|
||||
* @see schule.ngb.zm.Constants#DEFAULT_FILLCOLOR
|
||||
*/
|
||||
public void resetFill() {
|
||||
shapeDelegate.resetFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFill( Paint fill ) {
|
||||
shapeDelegate.setFill(fill);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Paint getFill() {
|
||||
return shapeDelegate.getFill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen linearen Farbverlauf, der am Punkt
|
||||
* ({@code fromX}, {@code fromY}) mit der Farbe {@code from} startet und am
|
||||
* Punkt (({@code toX}, {@code toY}) mit der Farbe {@code to} endet.
|
||||
*
|
||||
* @param fromX x-Koordinate des Startpunktes.
|
||||
* @param fromY y-Koordinate des Startpunktes.
|
||||
* @param from Farbe am Startpunkt.
|
||||
* @param toX x-Koordinate des Endpunktes.
|
||||
* @param toY y-Koordinate des Endpunktes.
|
||||
* @param to Farbe am Endpunkt.
|
||||
*/
|
||||
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||
shapeDelegate.setGradient(fromX, fromY, from, toX, toY, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, mit dem
|
||||
* Zentrum im Punkt ({@code centerX}, {@code centerY}) und dem angegebenen
|
||||
* Radius. Der Verlauf starte im Zentrum mit der Farbe {@code from} und
|
||||
* endet am Rand des durch den Radius beschriebenen Kreises mit der Farbe
|
||||
* {@code to}.
|
||||
*
|
||||
* @param centerX x-Koordinate des Kreismittelpunktes.
|
||||
* @param centerY y-Koordinate des Kreismittelpunktes.
|
||||
* @param radius Radius des Kreises.
|
||||
* @param from Farbe im Zentrum des Kreises.
|
||||
* @param to Farbe am Rand des Kreises.
|
||||
*/
|
||||
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||
shapeDelegate.setGradient(centerX, centerY, radius, from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||
Point2D.Double anchorPoint = schule.ngb.zm.shapes.Shape.getAnchorPoint(getWidth(), getHeight(), dir);
|
||||
Point2D.Double inversePoint = schule.ngb.zm.shapes.Shape.getAnchorPoint(getWidth(), getHeight(), dir.inverse());
|
||||
shapeDelegate.setGradient(inversePoint.x, inversePoint.y, from, anchorPoint.x, anchorPoint.y, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( Color from, Color to ) {
|
||||
shapeDelegate.setGradient(getWidth() / 2.0, getHeight() / 2.0, min(getWidth(), getHeight()) / 2.0, from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt den Farbverlauf von der Form.
|
||||
*/
|
||||
public void noGradient() {
|
||||
shapeDelegate.noGradient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStroke( Stroke stroke ) {
|
||||
shapeDelegate.setStroke(stroke);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Farbe der Konturlinie zurück.
|
||||
*
|
||||
* @return Die Konturfarbe oder {@code null}.
|
||||
*/
|
||||
public Color getStrokeColor() {
|
||||
return shapeDelegate.getStrokeColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe.
|
||||
*
|
||||
* @param color Die neue Farbe der Konturlinie.
|
||||
* @see Color
|
||||
*/
|
||||
public void setStrokeColor( Color color ) {
|
||||
shapeDelegate.setStrokeColor(color);
|
||||
drawing.setPaint(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe und setzt die
|
||||
* Transparenz auf den angegebenen Wert. 0 is komplett durchsichtig und 255
|
||||
* komplett deckend.
|
||||
*
|
||||
* @param color Die neue Farbe der Konturlinie oder {@code null}.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(Color, int)
|
||||
*/
|
||||
public void setStrokeColor( Color color, int alpha ) {
|
||||
setStrokeColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf einen Grauwert mit der angegebenen
|
||||
* Intensität. 0 entspricht schwarz, 255 entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @see Color#Color(int)
|
||||
*/
|
||||
public void setStrokeColor( int gray ) {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( schule.ngb.zm.Color color ) {
|
||||
strokeColor = color;
|
||||
drawing.setColor(color.getJavaColor());
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
strokeColor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf einen Grauwert mit der angegebenen
|
||||
* Intensität und dem angegebenen Transparenzwert. Der Grauwert 0 entspricht
|
||||
* schwarz, 255 entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(int, int)
|
||||
*/
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
setStrokeColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die Farbe mit den angegebenen Rot-,
|
||||
* Grün- und Blauanteilen.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @see Color#Color(int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
setStrokeColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die Farbe mit den angegebenen Rot-,
|
||||
* Grün- und Blauanteilen und dem angegebenen Transparenzwert.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
||||
* @see Color#Color(int, int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
setStrokeColor(new schule.ngb.zm.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;
|
||||
}
|
||||
/**
|
||||
* Entfernt die Kontur der Form.
|
||||
*/
|
||||
public void noStroke() {
|
||||
shapeDelegate.noStroke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die Standardwerte zurück.
|
||||
*
|
||||
* @see schule.ngb.zm.Constants#DEFAULT_STROKECOLOR
|
||||
* @see schule.ngb.zm.Constants#DEFAULT_STROKEWEIGHT
|
||||
* @see schule.ngb.zm.Constants#SOLID
|
||||
*/
|
||||
public void resetStroke() {
|
||||
setStrokeColor(DEFAULT_STROKECOLOR);
|
||||
setStrokeWeight(DEFAULT_STROKEWEIGHT);
|
||||
setStrokeType(SOLID);
|
||||
shapeDelegate.resetStroke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Dicke der Konturlinie zurück.
|
||||
*
|
||||
* @return Die aktuelle Dicke der Linie.
|
||||
*/
|
||||
public double getStrokeWeight() {
|
||||
return shapeDelegate.getStrokeWeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Dicke der Konturlinie. Die Dicke muss größer 0 sein. Wird 0
|
||||
* übergeben, dann wird keine Kontur mehr angezeigt.
|
||||
*
|
||||
* @param weight Die Dicke der Konturlinie.
|
||||
*/
|
||||
public void setStrokeWeight( double weight ) {
|
||||
shapeDelegate.setStrokeWeight(weight);
|
||||
drawing.setStroke(shapeDelegate.getStroke());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Art der Konturlinie zurück.
|
||||
*
|
||||
* @return Die aktuelle Art der Konturlinie.
|
||||
* @see Options.StrokeType
|
||||
*/
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return shapeDelegate.getStrokeType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
||||
* {@link #DOTTED} und {@link #SOLID}.
|
||||
*
|
||||
* @param type Eine der möglichen Konturarten.
|
||||
* @see Options.StrokeType
|
||||
*/
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
shapeDelegate.setStrokeType(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stroke getStroke() {
|
||||
return shapeDelegate.getStroke();
|
||||
}
|
||||
|
||||
public void setAnchor( Options.Direction anchor ) {
|
||||
default_anchor = anchor;
|
||||
shapeDelegate.setAnchor(anchor);
|
||||
}
|
||||
|
||||
public void clear( int gray ) {
|
||||
@@ -187,80 +450,122 @@ public class DrawingLayer extends Layer {
|
||||
}
|
||||
|
||||
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);
|
||||
buffer.setRGB((int)x, (int)y, fillColor.getRGBA());
|
||||
buffer.setRGB((int) x, (int) y, shapeDelegate.getFontColor().getRGBA());
|
||||
}
|
||||
|
||||
public void square( double x, double y, double w ) {
|
||||
rect(x, y, w, w);
|
||||
rect(x, y, w, w, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
public void square( double x, double y, double w, Options.Direction anchor ) {
|
||||
rect(x, y, w, w, anchor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet ein Rechteck in die Zeichenebene.
|
||||
*
|
||||
* @param x x-Koordinate des Rechtecks.
|
||||
* @param y y-Koordinate des Rechtecks.
|
||||
* @param w Breite des Rechtecks.
|
||||
* @param h Höhe des Rechtecks.
|
||||
*/
|
||||
public void rect( double x, double y, double w, double h ) {
|
||||
rect(x, y, w, h, default_anchor);
|
||||
rect(x, y, w, h, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet ein Rechteck mit in die Zeichenebene.
|
||||
*
|
||||
* @param x x-Koordinate des Rechtecks.
|
||||
* @param y y-Koordinate des Rechtecks.
|
||||
* @param w Breite des Rechtecks.
|
||||
* @param h Höhe des Rechtecks.
|
||||
* @param anchor Ankerpunkt de Rechtecks.
|
||||
*/
|
||||
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);
|
||||
Point2D.Double anchorPoint = this.getOriginPoint(x, y, w, h, anchor);
|
||||
rect.setRect(anchorPoint.getX(), anchorPoint.getY(), w, h);
|
||||
fillShape(rect);
|
||||
drawShape(rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet ein Rechteck mit abgerundeten Ecken in die Zeichenebene.
|
||||
* <p>
|
||||
* Jede Ecke wird als Viertelkreis eines Kreises mit dem Radius
|
||||
* {@code radius} gezeichnet.
|
||||
*
|
||||
* @param x x-Koordinate des Rechtecks.
|
||||
* @param y y-Koordinate des Rechtecks.
|
||||
* @param w Breite des Rechtecks.
|
||||
* @param h Höhe des Rechtecks.
|
||||
* @param radius Radius der Eckenkreise.
|
||||
*/
|
||||
public void roundedRect( double x, double y, double w, double h, double radius ) {
|
||||
roundedRect(x, y, w, h, radius, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet ein Rechteck mit abgerundeten Ecken in die Zeichenebene.
|
||||
* <p>
|
||||
* Jede Ecke wird als Viertelkreis eines Kreises mit dem Radius
|
||||
* {@code radius} gezeichnet.
|
||||
*
|
||||
* @param x x-Koordinate des Rechtecks.
|
||||
* @param y y-Koordinate des Rechtecks.
|
||||
* @param w Breite des Rechtecks.
|
||||
* @param h Höhe des Rechtecks.
|
||||
* @param radius Radius der Eckenkreise.
|
||||
* @param anchor Ankerpunkt de Rechtecks.
|
||||
*/
|
||||
public void roundedRect( double x, double y, double w, double h, double radius, Options.Direction anchor ) {
|
||||
Point2D.Double anchorPoint = this.getOriginPoint(x, y, w, h, anchor);
|
||||
Shape rect = new RoundRectangle2D.Double(anchorPoint.getX(), anchorPoint.getY(), w, h, radius, radius);
|
||||
fillShape(rect);
|
||||
drawShape(rect);
|
||||
}
|
||||
|
||||
public void point( double x, double y ) {
|
||||
circle(x - 1, y - 1, 2);
|
||||
ellipse(x - 1, y - 1, 2, 2, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
public void circle( double x, double y, double r ) {
|
||||
ellipse(x, y, r+r, r+r, default_anchor);
|
||||
ellipse(x, y, r + r, r + r, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
public void circle( double x, double y, double r, Options.Direction anchor ) {
|
||||
ellipse(x, y, r+r, r+r, 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);
|
||||
ellipse(x, y, w, h, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
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);
|
||||
Point2D.Double anchorPoint = getOriginPoint(x, y, w, h, anchor);
|
||||
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);
|
||||
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
|
||||
);*/
|
||||
Point2D.Double anchorPoint = getOriginPoint(x, y, w, h, CENTER);
|
||||
arc.setArc(
|
||||
anchorPoint.x, anchorPoint.y,
|
||||
w, h,
|
||||
//Math.toRadians(angle1), Math.toRadians(angle2 - angle1),
|
||||
angle1, angle2-angle1,
|
||||
angle1, angle2 - angle1,
|
||||
Arc2D.OPEN
|
||||
);
|
||||
|
||||
@@ -270,16 +575,9 @@ public class DrawingLayer extends Layer {
|
||||
public void pie( double x, double y, double r, double angle1, double angle2 ) {
|
||||
while( angle2 < angle1 ) angle2 += 360.0;
|
||||
|
||||
double d = r+r;
|
||||
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
|
||||
);*/
|
||||
Point2D.Double anchorPoint = getOriginPoint(x, y, d, d, CENTER);
|
||||
arc.setArc(
|
||||
anchorPoint.x, anchorPoint.y,
|
||||
d, d,
|
||||
@@ -313,12 +611,12 @@ public class DrawingLayer extends Layer {
|
||||
}
|
||||
|
||||
public void rhombus( double x, double y, double width, double height ) {
|
||||
rhombus(x, y, width, height, default_anchor);
|
||||
rhombus(x, y, width, height, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
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);
|
||||
Point2D.Double anchorPoint = getOriginPoint(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);
|
||||
}
|
||||
|
||||
@@ -364,8 +662,21 @@ public class DrawingLayer extends Layer {
|
||||
drawShape(path);
|
||||
}
|
||||
|
||||
// TODO: General shape drawing
|
||||
/*
|
||||
protected void fillShape( Shape shape ) {
|
||||
if( shapeDelegate.getFill() != null ) {
|
||||
drawing.setPaint(shapeDelegate.getFill());
|
||||
drawing.fill(shape);
|
||||
}
|
||||
}
|
||||
|
||||
protected void drawShape( Shape shape ) {
|
||||
if( shapeDelegate.hasStroke() ) {
|
||||
drawing.setColor(shapeDelegate.getStrokeColor().getJavaColor());
|
||||
drawing.setStroke(shapeDelegate.getStroke());
|
||||
drawing.draw(shape);
|
||||
}
|
||||
}
|
||||
|
||||
public void beginShape() {
|
||||
path.reset();
|
||||
pathStarted = false;
|
||||
@@ -388,10 +699,9 @@ public class DrawingLayer extends Layer {
|
||||
fillShape(path);
|
||||
drawShape(path);
|
||||
}
|
||||
*/
|
||||
|
||||
public void image( String imageSource, double x, double y ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, default_anchor);
|
||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
public void image( String imageSource, double x, double y, Options.Direction anchor ) {
|
||||
@@ -399,7 +709,7 @@ public class DrawingLayer extends Layer {
|
||||
}
|
||||
|
||||
public void image( String imageSource, double x, double y, double scale ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, scale, default_anchor);
|
||||
image(ImageLoader.loadImage(imageSource), x, y, scale, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
public void image( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
|
||||
@@ -407,120 +717,110 @@ public class DrawingLayer extends Layer {
|
||||
}
|
||||
|
||||
public void image( Image image, double x, double y ) {
|
||||
image(image, x, y, 1.0, default_anchor);
|
||||
image(image, x, y, 1.0, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
public void image( Image image, double x, double y, double scale ) {
|
||||
image(image, x, y, scale, default_anchor);
|
||||
image(image, x, y, scale, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
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);
|
||||
Point2D.Double anchorPoint = getOriginPoint(x, y, neww, newh, anchor);
|
||||
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) neww, (int) newh, null);
|
||||
}
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return shapeDelegate.getFont();
|
||||
}
|
||||
|
||||
public void setFont( Font newFont ) {
|
||||
shapeDelegate.setFont(newFont);
|
||||
// fontMetrics = drawing.getFontMetrics();
|
||||
}
|
||||
|
||||
public void setFont( String fontName ) {
|
||||
shapeDelegate.setFont(fontName);
|
||||
}
|
||||
|
||||
public void setFont( String fontName, int size ) {
|
||||
shapeDelegate.setFont(fontName, size);
|
||||
}
|
||||
|
||||
public void setFont( String fontName, int size, int style ) {
|
||||
shapeDelegate.setFont(fontName, size, style);
|
||||
}
|
||||
|
||||
public double getFontSize() {
|
||||
return shapeDelegate.getFontSize();
|
||||
}
|
||||
|
||||
public void setFontSize( double size ) {
|
||||
shapeDelegate.setFontSize(size);
|
||||
}
|
||||
|
||||
public void text( String text, double x, double y ) {
|
||||
text(text, x, y, default_anchor);
|
||||
text(text, x, y, shapeDelegate.getAnchor());
|
||||
}
|
||||
|
||||
public void text( String text, double x, double y, Options.Direction anchor ) {
|
||||
drawing.setFont(shapeDelegate.getFont());
|
||||
FontMetrics fm = drawing.getFontMetrics();
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y + fm.getAscent(), fm.stringWidth(text), fm.getHeight(), anchor);
|
||||
Point2D.Double anchorPoint = getOriginPoint(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));
|
||||
/**
|
||||
* Berechnet den Ursprung einer zu zeichnenden Form, wenn der angegebene
|
||||
* Ankerpunkt zugrunde gelegt wird.
|
||||
* <p>
|
||||
* Der Ursprung einer Form liegt immer oben links. Eine Form an den
|
||||
* Koordinaten {@code (300, 300)} und den Abmessungen {@code (100, 50)}
|
||||
* würde für den Anker {@link Options.Direction#NORTHWEST} also den Ursprung
|
||||
* {@code (300, 300)} haben. Für den Anker {@link Options.Direction#CENTER}
|
||||
* wäre der Ursprung zu den Koordinaten um die hälfte der Abmessungen nach
|
||||
* links und oben verschoben bei {@code (250, 275)}. Beim Zeichnen liegen
|
||||
* die Koordinaten {@code (300, 300)} dann in der Mitte der Form.
|
||||
*
|
||||
* @param x x-Koordinate des umgebenden Rechtecks.
|
||||
* @param y y-Koordinate des umgebenden Rechtecks.
|
||||
* @param w Breite des umgebenden Rechtecks.
|
||||
* @param h Höhe des umgebenden Rechtecks.
|
||||
* @param anchor Zu verendender Anker.
|
||||
* @return Der Ursprung des umgebenden Rechtecks.
|
||||
*/
|
||||
protected Point2D.Double getOriginPoint( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||
Point2D.Double ap = schule.ngb.zm.shapes.Shape.getAnchorPoint(w, h, anchor);
|
||||
ap.x = x - ap.x;
|
||||
ap.y = y - ap.y;
|
||||
return ap;
|
||||
}
|
||||
|
||||
public void setFont( String fontName ) {
|
||||
Font font = new Font(fontName, drawing.getFont().getStyle(), drawing.getFont().getSize());
|
||||
setFont(font);
|
||||
public void pushStyle() {
|
||||
styleStack.push(shapeDelegate);
|
||||
shapeDelegate = new Text(shapeDelegate);
|
||||
}
|
||||
|
||||
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);
|
||||
public void popStyle() {
|
||||
if( styleStack.isEmpty() ) {
|
||||
resetStyle();
|
||||
} else {
|
||||
shapeDelegate = styleStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
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 resetStyle() {
|
||||
Text newDelegate = new Text(0, 0, "");
|
||||
newDelegate.setFillColor(DEFAULT_FILLCOLOR);
|
||||
newDelegate.setStrokeColor(DEFAULT_STROKECOLOR);
|
||||
if( shapeDelegate != null ) {
|
||||
newDelegate.setAnchor(shapeDelegate.getAnchor());
|
||||
}
|
||||
shapeDelegate = newDelegate;
|
||||
}
|
||||
|
||||
public void translate( double dx, double dy ) {
|
||||
@@ -535,6 +835,10 @@ public class DrawingLayer extends Layer {
|
||||
drawing.rotate(Math.toRadians(pAngle));
|
||||
}
|
||||
|
||||
public void rotate( double pAngle, double centerX, double centerY ) {
|
||||
drawing.rotate(Math.toRadians(pAngle), centerX, centerY);
|
||||
}
|
||||
|
||||
public void shear( double dx, double dy ) {
|
||||
drawing.shear(dx, dy);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
public class ImageLayer extends Layer {
|
||||
|
||||
protected Image image;
|
||||
@@ -16,20 +17,20 @@ public class ImageLayer extends Layer {
|
||||
|
||||
protected boolean redraw = true;
|
||||
|
||||
public ImageLayer(String source) {
|
||||
public ImageLayer( String source ) {
|
||||
image = ImageLoader.loadImage(source);
|
||||
}
|
||||
|
||||
public ImageLayer(Image image) {
|
||||
public ImageLayer( Image image ) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public ImageLayer(int width, int height, Image image) {
|
||||
public ImageLayer( int width, int height, Image image ) {
|
||||
super(width, height);
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
public void setImage( Image image ) {
|
||||
this.image = image;
|
||||
redraw = true;
|
||||
}
|
||||
@@ -38,7 +39,7 @@ public class ImageLayer extends Layer {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(double pX) {
|
||||
public void setX( double pX ) {
|
||||
this.x = pX;
|
||||
redraw = true;
|
||||
}
|
||||
@@ -47,7 +48,7 @@ public class ImageLayer extends Layer {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(double pY) {
|
||||
public void setY( double pY ) {
|
||||
this.y = pY;
|
||||
redraw = true;
|
||||
}
|
||||
@@ -59,8 +60,8 @@ public class ImageLayer extends Layer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D graphics) {
|
||||
if (redraw && visible) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
if( redraw && visible ) {
|
||||
drawing.drawImage(image, (int) x, (int) y, null);
|
||||
redraw = false;
|
||||
}
|
||||
|
||||
@@ -4,42 +4,46 @@ import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import java.awt.Stroke;
|
||||
import java.util.LinkedList;
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
public final class Shape2DLayer extends Layer {
|
||||
|
||||
protected schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||
private schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||
|
||||
protected schule.ngb.zm.Color fillColor = DEFAULT_FILLCOLOR;
|
||||
private schule.ngb.zm.Color fillColor = DEFAULT_FILLCOLOR;
|
||||
|
||||
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||
private double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
private Options.StrokeType strokeType = SOLID;
|
||||
|
||||
private LinkedList<java.awt.Shape> shapes;
|
||||
private final LinkedList<java.awt.Shape> shapes;
|
||||
|
||||
private boolean instantDraw = false;
|
||||
|
||||
public Shape2DLayer() {
|
||||
super();
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
shapes = new LinkedList<>();
|
||||
}
|
||||
|
||||
public Shape2DLayer( boolean instantDraw ) {
|
||||
super();
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
shapes = new LinkedList<>();
|
||||
this.instantDraw = instantDraw;
|
||||
}
|
||||
|
||||
public Shape2DLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
shapes = new LinkedList<>();
|
||||
}
|
||||
|
||||
public Shape2DLayer( int width, int height, boolean instantDraw ) {
|
||||
super(width, height);
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
shapes = new LinkedList<>();
|
||||
this.instantDraw = instantDraw;
|
||||
}
|
||||
|
||||
@@ -106,7 +110,7 @@ public final class Shape2DLayer extends Layer {
|
||||
drawing.setStroke(createStroke());
|
||||
}
|
||||
|
||||
protected Stroke createStroke() {
|
||||
private Stroke createStroke() {
|
||||
switch( strokeType ) {
|
||||
case DOTTED:
|
||||
return new BasicStroke(
|
||||
@@ -153,7 +157,7 @@ public final class Shape2DLayer extends Layer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
if( !instantDraw ) {
|
||||
for( Shape shape : shapes ) {
|
||||
drawing.setColor(fillColor.getJavaColor());
|
||||
@@ -164,7 +168,7 @@ public final class Shape2DLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(pGraphics);
|
||||
super.draw(graphics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,13 +10,20 @@ import java.awt.Graphics2D;
|
||||
import java.util.*;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
/**
|
||||
* Ein Layer um {@link Shape} Objekte zu zeichnen.
|
||||
* <p>
|
||||
* Ein {@code ShapesLayer} ist eine der drei Standardebenen der
|
||||
* {@link schule.ngb.zm.Zeichenmaschine}.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class ShapesLayer extends Layer {
|
||||
|
||||
protected boolean clearBeforeDraw = true;
|
||||
|
||||
private List<Shape> shapes;
|
||||
private final List<Shape> shapes;
|
||||
|
||||
private List<Animation<? extends Shape>> animations;
|
||||
private final List<Animation<? extends Shape>> animations;
|
||||
|
||||
public ShapesLayer() {
|
||||
super();
|
||||
@@ -37,7 +44,7 @@ public class ShapesLayer extends Layer {
|
||||
public <ST extends Shape> ST getShape( Class<ST> shapeClass ) {
|
||||
for( Shape s : shapes ) {
|
||||
if( shapeClass.isInstance(s) ) {
|
||||
return (ST) s;
|
||||
return shapeClass.cast(s);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -51,7 +58,7 @@ public class ShapesLayer extends Layer {
|
||||
List<ST> result = new LinkedList<>();
|
||||
for( Shape s : shapes ) {
|
||||
if( shapeClass.isInstance(s) ) {
|
||||
result.add((ST) s);
|
||||
result.add(shapeClass.cast(s));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -59,17 +66,13 @@ public class ShapesLayer extends Layer {
|
||||
|
||||
public void add( Shape... shapes ) {
|
||||
synchronized( this.shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.add(s);
|
||||
}
|
||||
Collections.addAll(this.shapes, shapes);
|
||||
}
|
||||
}
|
||||
|
||||
public void add( Collection<Shape> shapes ) {
|
||||
synchronized( this.shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.add(s);
|
||||
}
|
||||
this.shapes.addAll(shapes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +86,7 @@ public class ShapesLayer extends Layer {
|
||||
|
||||
public void remove( Collection<Shape> shapes ) {
|
||||
synchronized( this.shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.remove(s);
|
||||
}
|
||||
this.shapes.removeAll(shapes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,9 +117,9 @@ public class ShapesLayer extends Layer {
|
||||
anim.start();
|
||||
}
|
||||
|
||||
|
||||
public void play( Animation<? extends Shape>... anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
@SafeVarargs
|
||||
public final void play( Animation<? extends Shape>... anims ) {
|
||||
for( Animation<? extends Shape> anim : anims ) {
|
||||
this.animations.add(anim);
|
||||
anim.start();
|
||||
}
|
||||
@@ -141,13 +142,13 @@ public class ShapesLayer extends Layer {
|
||||
anim.update(delta);
|
||||
|
||||
if( !anim.isActive() ) {
|
||||
animations.remove(anim);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
if( clearBeforeDraw ) {
|
||||
clear();
|
||||
}
|
||||
@@ -161,7 +162,7 @@ public class ShapesLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(pGraphics);
|
||||
super.draw(graphics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import schule.ngb.zm.*;
|
||||
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 schule.ngb.zm.Fillable;
|
||||
import schule.ngb.zm.Strokeable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class TurtleLayer extends Layer {
|
||||
@SuppressWarnings( "unused" )
|
||||
public class TurtleLayer extends Layer implements Strokeable, Fillable {
|
||||
|
||||
// Rotating by the clock
|
||||
public static final int H1 = 30;
|
||||
@@ -39,7 +40,7 @@ public class TurtleLayer extends Layer {
|
||||
|
||||
public static final int H12 = 360;
|
||||
|
||||
private static Stack<Color> turtleColors;
|
||||
private final static Stack<Color> turtleColors;
|
||||
|
||||
static {
|
||||
turtleColors = new Stack<>();
|
||||
@@ -51,9 +52,9 @@ public class TurtleLayer extends Layer {
|
||||
turtleColors.add(Color.BLUE);
|
||||
}
|
||||
|
||||
private Turtle mainTurtle = null;
|
||||
private final Turtle mainTurtle;
|
||||
|
||||
private ArrayList<Turtle> turtles = new ArrayList<Turtle>(6);
|
||||
private final List<Turtle> turtles = new ArrayList<>(6);
|
||||
|
||||
public TurtleLayer() {
|
||||
super();
|
||||
@@ -117,7 +118,20 @@ public class TurtleLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
// Begin of delegate methods (auto-generated)
|
||||
// begin of delegate methods (auto-generated)
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return mainTurtle.isVisible();
|
||||
}
|
||||
|
||||
public void beginPath() {
|
||||
mainTurtle.beginPath();
|
||||
}
|
||||
|
||||
public void closePath() {
|
||||
mainTurtle.closePath();
|
||||
}
|
||||
|
||||
public void fill() {
|
||||
mainTurtle.fill();
|
||||
@@ -175,100 +189,196 @@ public class TurtleLayer extends Layer {
|
||||
mainTurtle.moveTo(x, y);
|
||||
}
|
||||
|
||||
public Color getFillColor() {
|
||||
return mainTurtle.getFillColor();
|
||||
@Override
|
||||
public void setFill( Paint fill ) {
|
||||
mainTurtle.setFill(fill);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Paint getFill() {
|
||||
return mainTurtle.getFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
@Override
|
||||
public Color getFillColor() {
|
||||
return mainTurtle.getFillColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeWeight( double weight ) {
|
||||
mainTurtle.setStrokeWeight(weight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return mainTurtle.getStrokeType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
mainTurtle.setStrokeType(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||
mainTurtle.setGradient(from, to, dir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( Color from, Color to ) {
|
||||
mainTurtle.setGradient(from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFill() {
|
||||
return mainTurtle.hasFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFillColor() {
|
||||
return mainTurtle.hasFillColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasGradient() {
|
||||
return mainTurtle.hasGradient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( Color color, int alpha ) {
|
||||
mainTurtle.setFillColor(color, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( int gray ) {
|
||||
mainTurtle.setFillColor(gray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
mainTurtle.setFillColor(gray, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
mainTurtle.setFillColor(red, green, blue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
mainTurtle.setFillColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noFill() {
|
||||
mainTurtle.noFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetFill() {
|
||||
mainTurtle.resetFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultipleGradientPaint getGradient() {
|
||||
return mainTurtle.getGradient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||
mainTurtle.setGradient(fromX, fromY, from, toX, toY, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||
mainTurtle.setGradient(centerX, centerY, radius, from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noGradient() {
|
||||
mainTurtle.noGradient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStroke( Stroke stroke ) {
|
||||
mainTurtle.setStroke(stroke);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stroke getStroke() {
|
||||
return mainTurtle.getStroke();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStroke() {
|
||||
return mainTurtle.hasStroke();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getStrokeColor() {
|
||||
return mainTurtle.getStrokeColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( Color color, int alpha ) {
|
||||
mainTurtle.setStrokeColor(color, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( int gray ) {
|
||||
mainTurtle.setStrokeColor(gray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
mainTurtle.setStrokeColor(gray, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
mainTurtle.setStrokeColor(red, green, blue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
mainTurtle.setStrokeColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noStroke() {
|
||||
mainTurtle.noStroke();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetStroke() {
|
||||
mainTurtle.resetStroke();
|
||||
}
|
||||
|
||||
public void addPosToPath() {
|
||||
mainTurtle.addPosToPath();
|
||||
@Override
|
||||
public double getStrokeWeight() {
|
||||
return mainTurtle.getStrokeWeight();
|
||||
}
|
||||
|
||||
public void closePath() {
|
||||
mainTurtle.closePath();
|
||||
}
|
||||
|
||||
// End of delegate methods (auto-generated)
|
||||
// end of delegate methods (auto-generated)
|
||||
|
||||
|
||||
public class Turtle extends FilledShape {
|
||||
/**
|
||||
* Die Turtle der Zeichenmaschine.
|
||||
*/
|
||||
public class Turtle extends BasicDrawable {
|
||||
|
||||
private static final int STD_SIZE = 12;
|
||||
private static final int DEFAULT_SIZE = 12;
|
||||
|
||||
boolean penDown = true;
|
||||
|
||||
@@ -282,7 +392,31 @@ public class TurtleLayer extends Layer {
|
||||
|
||||
boolean pathOpen = false;
|
||||
|
||||
Turtle() {}
|
||||
/**
|
||||
* Path-Objekt für die Darstellung der Turtle.
|
||||
*/
|
||||
Path2D.Double turtlePath;
|
||||
|
||||
Turtle() {
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
public void beginPath() {
|
||||
pathOpen = false;
|
||||
addPosToPath();
|
||||
}
|
||||
|
||||
public void closePath() {
|
||||
if( pathOpen ) {
|
||||
addPosToPath();
|
||||
path.closePath();
|
||||
path.trimToSize();
|
||||
pathOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void addPosToPath() {
|
||||
if( !pathOpen ) {
|
||||
@@ -294,53 +428,39 @@ public class TurtleLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
if( hasFill() ) {
|
||||
drawing.setPaint(getFill());
|
||||
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);
|
||||
if( turtlePath == null ) {
|
||||
turtlePath = new Path2D.Double();
|
||||
path.moveTo(DEFAULT_SIZE, 0);
|
||||
path.lineTo(-DEFAULT_SIZE, -DEFAULT_SIZE / 2.0);
|
||||
path.lineTo(-DEFAULT_SIZE, DEFAULT_SIZE / 2.0);
|
||||
path.lineTo(DEFAULT_SIZE, 0);
|
||||
}
|
||||
|
||||
AffineTransform verzerrung = new AffineTransform();
|
||||
verzerrung.translate(position.x, position.y);
|
||||
verzerrung.rotate(Math.toRadians(direction.angle()));
|
||||
|
||||
Shape shape = verzerrung.createTransformedShape(path);
|
||||
java.awt.Shape shape = verzerrung.createTransformedShape(turtlePath);
|
||||
|
||||
if( strokeColor != null ) {
|
||||
if( hasStroke() ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(DEFAULT_STROKECOLOR.getJavaColor());
|
||||
}
|
||||
graphics.fill(shape);
|
||||
graphics.setColor(Color.BLACK.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
graphics.draw(shape);
|
||||
}
|
||||
|
||||
@@ -354,9 +474,9 @@ public class TurtleLayer extends Layer {
|
||||
Vector positionStart = position.copy();
|
||||
position.add(Vector.setLength(direction, dist));
|
||||
|
||||
if( penDown && strokeColor != null ) {
|
||||
if( penDown && hasStroke() ) {
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.setStroke(createStroke());
|
||||
drawing.setStroke(getStroke());
|
||||
drawing.drawLine((int) positionStart.x, (int) positionStart.y, (int) position.x, (int) position.y);
|
||||
}
|
||||
}
|
||||
@@ -407,9 +527,9 @@ public class TurtleLayer extends Layer {
|
||||
position.x = x;
|
||||
position.y = y;
|
||||
|
||||
if( penDown && strokeColor != null ) {
|
||||
if( penDown && hasStroke() ) {
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.setStroke(createStroke());
|
||||
drawing.setStroke(getStroke());
|
||||
drawing.drawLine((int) x, (int) y, (int) position.x, (int) position.y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
import java.awt.GradientPaint;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Paint;
|
||||
import java.awt.RadialGradientPaint;
|
||||
|
||||
/**
|
||||
* Basisklasse für Formen, die eine Füllung besitzen können.
|
||||
* <p>
|
||||
* Formen mit einer Füllung können auch immer eine Konturlinie besitzen.
|
||||
*/
|
||||
public abstract class FilledShape extends StrokedShape {
|
||||
|
||||
/**
|
||||
* Die aktuelle Füllfarbe der Form oder {@code null}, wenn die Form nicht
|
||||
* gefüllt werden soll.
|
||||
*/
|
||||
protected Color fillColor = DEFAULT_FILLCOLOR;
|
||||
|
||||
/**
|
||||
* Der aktuelle Farbverlauf der Form oder {@code null}, wenn die Form keinen
|
||||
* Farbverlauf besitzt.
|
||||
*/
|
||||
protected Paint fill = null;
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Füllfarbe der Form zurück.
|
||||
*
|
||||
* @return Die aktuelle Füllfarbe oder {@code null}.
|
||||
*/
|
||||
public Color getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die angegebene Farbe.
|
||||
*
|
||||
* @param color Die neue Füllfarbe oder {@code null}.
|
||||
* @see Color
|
||||
*/
|
||||
public void setFillColor( Color color ) {
|
||||
fillColor = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die angegebene Farbe und setzt die Transparenz
|
||||
* auf den angegebenen Wert. 0 is komplett durchsichtig und 255 komplett
|
||||
* deckend.
|
||||
*
|
||||
* @param color Die neue Füllfarbe oder {@code null}.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(Color, int)
|
||||
*/
|
||||
public void setFillColor( Color color, int alpha ) {
|
||||
setFillColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität. 0
|
||||
* entspricht schwarz, 255 entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @see Color#Color(int)
|
||||
*/
|
||||
public void setFillColor( int gray ) {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität und
|
||||
* dem angegebenen Transparenzwert. Der Grauwert 0 entspricht schwarz, 255
|
||||
* entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(int, int)
|
||||
*/
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
setFillColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||
* Blauanteilen.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @see Color#Color(int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
setFillColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||
* Blauanteilen und dem angegebenen Transparenzwert.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
||||
* @see Color#Color(int, int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt die Füllung der Form.
|
||||
*/
|
||||
public void noFill() {
|
||||
setFillColor(null);
|
||||
noGradient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf den Standardwert zurück.
|
||||
*
|
||||
* @see schule.ngb.zm.Constants#DEFAULT_FILLCOLOR
|
||||
*/
|
||||
public void resetFill() {
|
||||
setFillColor(DEFAULT_FILLCOLOR);
|
||||
noGradient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen linearen Farbverlauf, der am Punkt
|
||||
* ({@code fromX}, {@code fromY}) mit der Farbe {@code from} startet und am
|
||||
* Punkt (({@code toX}, {@code toY}) mit der Farbe {@code to} endet.
|
||||
*
|
||||
* @param fromX x-Koordinate des Startpunktes.
|
||||
* @param fromY y-Koordinate des Startpunktes.
|
||||
* @param from Farbe am Startpunkt.
|
||||
* @param toX x-Koordinate des Endpunktes.
|
||||
* @param toY y-Koordinate des Endpunktes.
|
||||
* @param to Farbe am Endpunkt.
|
||||
*/
|
||||
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||
setFillColor(from);
|
||||
fill = new GradientPaint(
|
||||
(float) fromX, (float) fromY, from.getJavaColor(),
|
||||
(float) toX, (float) toY, to.getJavaColor()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, mit dem
|
||||
* Zentrum im Punkt ({@code centerX}, {@code centerY}) und dem angegebenen
|
||||
* Radius. Der Verlauf starte im Zentrum mit der Farbe {@code from} und
|
||||
* endet am Rand des durch den Radius beschriebenen Kreises mit der Farbe
|
||||
* {@code to}.
|
||||
*
|
||||
* @param centerX x-Koordinate des Kreismittelpunktes.
|
||||
* @param centerY y-Koordinate des Kreismittelpunktes.
|
||||
* @param radius Radius des Kreises.
|
||||
* @param from Farbe im Zentrum des Kreises.
|
||||
* @param to Farbe am Rand des Kreises.
|
||||
*/
|
||||
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||
setFillColor(from);
|
||||
fill = new RadialGradientPaint(
|
||||
(float) centerX, (float) centerY, (float) radius,
|
||||
new float[]{0f, 1f},
|
||||
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()});
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt den Farbverlauf von der Form.
|
||||
*/
|
||||
public void noGradient() {
|
||||
fill = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit der aktuellen
|
||||
* Füllung auf den Grafik-Kontext zu zeichnen. Die Methode verändert
|
||||
* gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt sie nicht
|
||||
* auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
||||
*
|
||||
* @param shape Die zu zeichnende Java-AWT Form
|
||||
* @param graphics Das Grafikobjekt.
|
||||
*/
|
||||
protected void fillShape( java.awt.Shape shape, Graphics2D graphics ) {
|
||||
if( fill != null ) {
|
||||
graphics.setPaint(fill);
|
||||
graphics.fill(shape);
|
||||
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
graphics.setColor(fillColor.getJavaColor());
|
||||
graphics.fill(shape);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
@@ -14,7 +13,7 @@ import java.awt.geom.Point2D;
|
||||
* <p>
|
||||
* Neben den abstrakten Methoden implementieren Unterklassen mindestens zwei
|
||||
* Konstruktoren. Einen Konstruktor, der die Form mit vom Nutzer gegebenen
|
||||
* Parametern initialisiert und einen, der die Werten einer anderen Form
|
||||
* Parametern initialisiert und einen, der die Werte einer anderen Form
|
||||
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
|
||||
* beispielsweise so implementiert:
|
||||
*
|
||||
@@ -34,7 +33,7 @@ import java.awt.geom.Point2D;
|
||||
* eine {@link #equals(Object)} Methode.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public abstract class Shape extends FilledShape {
|
||||
public abstract class Shape extends BasicDrawable {
|
||||
|
||||
/**
|
||||
* x-Koordinate der Form.
|
||||
@@ -56,11 +55,6 @@ public abstract class Shape extends FilledShape {
|
||||
*/
|
||||
protected double scale = 1.0;
|
||||
|
||||
/**
|
||||
* Ob die Form angezeigt werden soll.
|
||||
*/
|
||||
protected boolean visible = true;
|
||||
|
||||
/**
|
||||
* Ankerpunkt der Form.
|
||||
*/
|
||||
@@ -84,38 +78,6 @@ public abstract class Shape extends FilledShape {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ob die Form angezeigt wird oder nicht.
|
||||
*
|
||||
* @return {@code true}, wenn die From angezeigt werden soll, {@code false}
|
||||
* sonst.
|
||||
*/
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Versteckt die Form.
|
||||
*/
|
||||
public void hide() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt die Form an.
|
||||
*/
|
||||
public void show() {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Versteckt die Form, wenn sie derzeit angezeigt wird und zeigt sie
|
||||
* andernfalls an.
|
||||
*/
|
||||
public void toggle() {
|
||||
visible = !visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die x-Koordinate der Form zurück.
|
||||
*
|
||||
@@ -180,6 +142,25 @@ public abstract class Shape extends FilledShape {
|
||||
*/
|
||||
public abstract double getHeight();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
||||
Point2D apDir = getAbsAnchorPoint(dir);
|
||||
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
||||
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
||||
Point2D ap = getAbsAnchorPoint(CENTER);
|
||||
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Rotation in Grad zurück.
|
||||
*
|
||||
@@ -191,6 +172,7 @@ public abstract class Shape extends FilledShape {
|
||||
|
||||
/**
|
||||
* Dreht die Form um den angegebenen Winkel um ihren Ankerpunkt.
|
||||
*
|
||||
* @param angle Drehwinkel in Grad.
|
||||
*/
|
||||
public void rotate( double angle ) {
|
||||
@@ -227,6 +209,7 @@ public abstract class Shape extends FilledShape {
|
||||
public double getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public void scale( double factor ) {
|
||||
scale = factor;
|
||||
}
|
||||
@@ -235,17 +218,6 @@ public abstract class Shape extends FilledShape {
|
||||
scale(scale * factor);
|
||||
}
|
||||
|
||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
||||
Point2D apDir = getAbsAnchorPoint(dir);
|
||||
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
||||
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
||||
}
|
||||
|
||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
||||
Point2D ap = getAbsAnchorPoint(CENTER);
|
||||
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
||||
}
|
||||
|
||||
public Options.Direction getAnchor() {
|
||||
return anchor;
|
||||
}
|
||||
@@ -268,28 +240,25 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkt basierend
|
||||
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
|
||||
* auf der angegebenen Breite und Höhe des umschließenden Rechtecks.
|
||||
* <p>
|
||||
* Die Koordinaten des Ankerpunkt werden relativ zur oberen linken Ecke des
|
||||
* Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
||||
* Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke
|
||||
* des Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
||||
* bestimmt.
|
||||
*
|
||||
* @param width Breite des umschließdenden Rechtecks.
|
||||
* @param height Höhe des umschließdenden Rechtecks.
|
||||
* @param width Breite des umschließenden Rechtecks.
|
||||
* @param height Höhe des umschließenden Rechtecks.
|
||||
* @param anchor Gesuchter Ankerpunkt.
|
||||
* @return Ein {@link Point2D} mit den relativen Koordinaten.
|
||||
*/
|
||||
protected static Point2D.Double getAnchorPoint( double width, double height, Options.Direction anchor ) {
|
||||
public 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(
|
||||
return new Point2D.Double(
|
||||
wHalf + wHalf * anchor.x,
|
||||
hHalf + hHalf * anchor.y
|
||||
);
|
||||
|
||||
return anchorPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,12 +272,10 @@ public abstract class Shape extends FilledShape {
|
||||
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
||||
|
||||
// anchor == CENTER
|
||||
Point2D.Double anchorPoint = new Point2D.Double(
|
||||
return new Point2D.Double(
|
||||
wHalf * (anchor.x - this.anchor.x),
|
||||
hHalf * (anchor.y - this.anchor.y)
|
||||
);
|
||||
|
||||
return anchorPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -352,8 +319,8 @@ public abstract class Shape extends FilledShape {
|
||||
setStrokeWeight(shape.getStrokeWeight());
|
||||
setStrokeType(shape.getStrokeType());
|
||||
visible = shape.isVisible();
|
||||
rotation = shape.rotation;
|
||||
scale(shape.scale);
|
||||
rotation = shape.getRotation();
|
||||
scale(shape.getScale());
|
||||
setAnchor(shape.getAnchor());
|
||||
}
|
||||
}
|
||||
@@ -384,19 +351,10 @@ public abstract class Shape extends FilledShape {
|
||||
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den
|
||||
* {@link Graphics2D Grafikkontext} zu zeichnen.
|
||||
* <p>
|
||||
* Da die AWT-Shape bei jedem Zeichnen (also mindestens einmal pro Frame)
|
||||
* benötigt wird, wird das aktuelle Shape-Objekt in {@link #awtShape}
|
||||
* zwischengespeichert. Bei Änderungen der Objekteigenschaften muss daher
|
||||
* intern {@link #invalidate()} aufgerufen werden, damit beim nächsten
|
||||
* Aufruf von {@link #draw(Graphics2D)} die Shape mit einem Aufurf von
|
||||
* {@code getShape()} neu erzeugt wird. Unterklassen können aber auch die
|
||||
* zwischengespeicherte Shape direkt modifizieren, um eine Neugenerierung zu
|
||||
* vermeiden.
|
||||
* <p>
|
||||
* Wenn diese Form nicht durch eine AWT-Shape dargstellt wird, kann die
|
||||
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, kann die
|
||||
* Methode {@code null} zurückgeben.
|
||||
*
|
||||
* @return Eine Java-AWT {@code Shape} die diess Form repräsentiert oder
|
||||
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
|
||||
* {@code null}.
|
||||
*/
|
||||
public abstract java.awt.Shape getShape();
|
||||
@@ -560,7 +518,7 @@ public abstract class Shape extends FilledShape {
|
||||
shape = transform.createTransformedShape(shape);
|
||||
}
|
||||
|
||||
Color currentColor = graphics.getColor();
|
||||
java.awt.Color currentColor = graphics.getColor();
|
||||
fillShape(shape, graphics);
|
||||
strokeShape(shape, graphics);
|
||||
graphics.setColor(currentColor);
|
||||
@@ -573,8 +531,8 @@ public abstract class Shape extends FilledShape {
|
||||
* verglichen. Unterklassen überschreiben die Methode, um weitere
|
||||
* Eigenschaften zu berücksichtigen.
|
||||
* <p>
|
||||
* Die Eigenschaften von {@link FilledShape} und {@link StrokedShape} werden
|
||||
* nicht verglichen.
|
||||
* Die Eigenschaften, die durch {@link Fillable} und {@link Strokeable}
|
||||
* impliziert werden, werden nicht verglichen.
|
||||
*
|
||||
* @param o Ein anderes Objekt.
|
||||
* @return
|
||||
@@ -590,4 +548,43 @@ public abstract class Shape extends FilledShape {
|
||||
Double.compare(pShape.scale, scale) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit den aktuellen
|
||||
* Kontureigenschaften auf den Grafik-Kontext zu zeichnen. Die Methode
|
||||
* verändert gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt
|
||||
* sie nicht auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
||||
*
|
||||
* @param shape Die zu zeichnende Java-AWT Form
|
||||
* @param graphics Das Grafikobjekt.
|
||||
*/
|
||||
protected void strokeShape( java.awt.Shape shape, Graphics2D graphics ) {
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(getStroke());
|
||||
graphics.draw(shape);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit der aktuellen
|
||||
* Füllung auf den Grafik-Kontext zu zeichnen. Die Methode verändert
|
||||
* gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt sie nicht
|
||||
* auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
||||
*
|
||||
* @param shape Die zu zeichnende Java-AWT Form
|
||||
* @param graphics Das Grafikobjekt.
|
||||
*/
|
||||
protected void fillShape( java.awt.Shape shape, Graphics2D graphics ) {
|
||||
if( fill != null ) {
|
||||
graphics.setPaint(fill);
|
||||
graphics.fill(shape);
|
||||
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
graphics.setColor(fillColor.getJavaColor());
|
||||
graphics.fill(shape);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -35,7 +36,7 @@ import java.util.List;
|
||||
* {@link #arrangeInGrid(int, Options.Direction, double, int)} und
|
||||
* {@link #align(Options.Direction)} verwendet werden, die jeweils die Position
|
||||
* der Formen in der Gruppe ändern und nicht die Position der Gruppe selbst (so
|
||||
* wie z.B. {@link #alignTo(Shape, Options.Direction)}.
|
||||
* wie z.B. {@link #alignTo(Shape, Options.Direction)}).
|
||||
*/
|
||||
public class ShapeGroup extends Shape {
|
||||
|
||||
@@ -43,7 +44,7 @@ public class ShapeGroup extends Shape {
|
||||
|
||||
public static final int ARRANGE_COLS = 1;
|
||||
|
||||
private List<Shape> shapes;
|
||||
private final List<Shape> shapes;
|
||||
|
||||
private double groupWidth = -1.0;
|
||||
|
||||
@@ -51,7 +52,7 @@ public class ShapeGroup extends Shape {
|
||||
|
||||
public ShapeGroup() {
|
||||
super();
|
||||
shapes = new ArrayList<>(10);
|
||||
shapes = Collections.synchronizedList(new ArrayList<>(10));
|
||||
}
|
||||
|
||||
public ShapeGroup( double x, double y ) {
|
||||
@@ -62,9 +63,7 @@ public class ShapeGroup extends Shape {
|
||||
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);
|
||||
}
|
||||
Collections.addAll(this.shapes, shapes);
|
||||
}
|
||||
|
||||
public Shape copy() {
|
||||
@@ -99,9 +98,11 @@ public class ShapeGroup extends Shape {
|
||||
|
||||
public <ShapeType extends Shape> List<ShapeType> getShapes( Class<ShapeType> typeClass ) {
|
||||
LinkedList<ShapeType> list = new LinkedList<>();
|
||||
for( Shape s : shapes ) {
|
||||
if( typeClass.isInstance(s) ) {
|
||||
list.add((ShapeType) s);
|
||||
synchronized( shapes ) {
|
||||
for( Shape shape : shapes ) {
|
||||
if( typeClass.isInstance(shape) ) {
|
||||
list.add(typeClass.cast(shape));
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
@@ -170,19 +171,15 @@ public class ShapeGroup extends Shape {
|
||||
int rows, cols;
|
||||
if( mode == ARRANGE_ROWS ) {
|
||||
rows = n;
|
||||
cols = (int) ceil(shapes.size() / n);
|
||||
cols = (int) ceil(shapes.size() / (double)n);
|
||||
} else {
|
||||
cols = n;
|
||||
rows = (int) ceil(shapes.size() / n);
|
||||
rows = (int) ceil(shapes.size() / (double)n);
|
||||
}
|
||||
|
||||
// Calculate grid cell size
|
||||
double maxHeight = shapes.stream().mapToDouble(
|
||||
( s ) -> s.getHeight()
|
||||
).reduce(0.0, Double::max);
|
||||
double maxWidth = shapes.stream().mapToDouble(
|
||||
( s ) -> s.getWidth()
|
||||
).reduce(0.0, Double::max);
|
||||
double maxHeight = shapes.stream().mapToDouble(Shape::getHeight).reduce(0.0, Double::max);
|
||||
double maxWidth = shapes.stream().mapToDouble(Shape::getWidth).reduce(0.0, Double::max);
|
||||
double halfHeight = maxHeight * .5;
|
||||
double halfWidth = maxWidth * .5;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ public class Text extends Shape {
|
||||
public Text( double x, double y, String text ) {
|
||||
this(x, y, text, new Font(Font.SANS_SERIF, Font.PLAIN, DEFAULT_FONTSIZE));
|
||||
}
|
||||
|
||||
public Text( double x, double y, String text, String fontname ) {
|
||||
super(x, y);
|
||||
Font userfont = FontLoader.loadFont(fontname);
|
||||
@@ -60,29 +61,58 @@ public class Text extends Shape {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setFont( String fontname ) {
|
||||
Font newFont = FontLoader.loadFont(fontname);
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public void setFont( Font newFont ) {
|
||||
//font = newFont.deriveFont(font.getStyle(), font.getSize2D());
|
||||
font = new Font(newFont.getFontName(), newFont.getStyle(), newFont.getSize());
|
||||
//font = newFont;
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
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());
|
||||
public void setFont( String fontName, double fontSize ) {
|
||||
Font newFont = FontLoader.loadFont(fontName);
|
||||
if( newFont != null ) {
|
||||
setFont(newFont);
|
||||
}
|
||||
font = newFont.deriveFont((float) fontSize);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return font;
|
||||
public void setFont( String fontName, double fontSize, int style ) {
|
||||
Font newFont = FontLoader.loadFont(fontName);
|
||||
if( newFont != null ) {
|
||||
setFont(newFont);
|
||||
}
|
||||
font = newFont.deriveFont(style, (float) fontSize);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public void setFontsize( double size ) {
|
||||
public double getFontSize() {
|
||||
return font.getSize2D();
|
||||
}
|
||||
|
||||
public void setFontSize( double size ) {
|
||||
font = font.deriveFont((float) size);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public double getFontsize() {
|
||||
return font.getSize2D();
|
||||
public int getFontStyle() {
|
||||
return font.getStyle();
|
||||
}
|
||||
|
||||
public void setFontStyle( int fontStyle ) {
|
||||
font = font.deriveFont(fontStyle);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
@@ -151,19 +181,19 @@ public class Text extends Shape {
|
||||
|
||||
@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();
|
||||
}
|
||||
super.copyFrom(shape);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
setFontsize(font.getSize2D() * factor);
|
||||
setFontSize(font.getSize2D() * factor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,7 +221,7 @@ public class Text extends Shape {
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
graphics.drawRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
|
||||
@@ -288,7 +288,7 @@ public class BarChart extends Rectangle {
|
||||
barY += barHeight + gap;
|
||||
}
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
if( inverted ) {
|
||||
graphics.drawLine((int) width, 0, (int) width, (int) height);
|
||||
} else {
|
||||
@@ -360,7 +360,7 @@ public class BarChart extends Rectangle {
|
||||
}
|
||||
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
if( inverted ) {
|
||||
graphics.drawLine(0, 0, (int) width, 0);
|
||||
} else {
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ChartAxes extends Rectangle {
|
||||
}
|
||||
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
graphics.drawLine(0, (int)(height), (int)((xMax-xMin) * xUnit), (int)(height));
|
||||
graphics.drawLine(0, (int)(height), 0, (int)(height - (yMax-yMin) * yUnit));
|
||||
if( showArrows ) {
|
||||
|
||||
@@ -91,7 +91,7 @@ public class LineChart extends Rectangle {
|
||||
for( ChartValue lcv : val ) {
|
||||
if( drawLines && lastLcv != null ) {
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ public class PieChart extends Circle {
|
||||
}
|
||||
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
graphics.drawOval(0, 0, (int) (radius * 2), (int) (radius * 2));
|
||||
|
||||
graphics.setTransform(originalTransform);
|
||||
|
||||
@@ -4,33 +4,101 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Eine Hilfsklasse um Dinge zu zählen.
|
||||
* <p>
|
||||
* Im einfachsten Fall kann der Zähler als geteilte Zählvariable genutzt werden,
|
||||
* die mit {@link #inc()} und {@link #dec()} aus verschiedenen Objekten oder
|
||||
* Methoden verändert werden kann.
|
||||
* <p>
|
||||
* Der Zähler kann aber auch Objekte zählen, indem die Instanzen an
|
||||
* {@link #count(Object)} übergeben werden. Am Ende kann mit {@link #getCount()}
|
||||
* die Anzahl der Objkete abgerufen werden.
|
||||
* <p>
|
||||
* Handelt es sich bei den Objekten um Zahlen, dann merkt sich ein Zähler auch
|
||||
* das Maximum, das Minimum, die Summe und den Durchschnitt der gezählten
|
||||
* Werte.
|
||||
* <p>
|
||||
* Ein Zähler kann auch komplette Arrays oder Listen von Zahlen zählen und die
|
||||
* obigen Statistiken auswerten.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public final class Counter {
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen {@code Counter}, der alle Integer im angegebenen
|
||||
* Array gezählt hat.
|
||||
*
|
||||
* @param values Die zu zählenden Werte.
|
||||
* @return Ein neuer {@code Counter}.
|
||||
*/
|
||||
public static Counter fromArray( int[] values ) {
|
||||
return new Counter().countAll(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen {@code Counter}, der alle Doubles im angegebenen
|
||||
* Array gezählt hat.
|
||||
*
|
||||
* @param values Die zu zählenden Werte.
|
||||
* @return Ein neuer {@code Counter}.
|
||||
*/
|
||||
public static Counter fromArray( double[] values ) {
|
||||
return new Counter().countAll(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen {@code Counter}, der alle Zahlen im angegebenen
|
||||
* Array gezählt hat.
|
||||
*
|
||||
* @param values Die zu zählenden Werte.
|
||||
* @return Ein neuer {@code Counter}.
|
||||
*/
|
||||
public static Counter fromArray( Number[] values ) {
|
||||
return new Counter().countAll(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen {@code Counter}, der alle Zahlen in der angegebenen
|
||||
* Liste gezählt hat.
|
||||
*
|
||||
* @param values Die zu zählenden Werte.
|
||||
* @return Ein neuer {@code Counter}.
|
||||
*/
|
||||
public static Counter fromList( List<Number> values ) {
|
||||
return new Counter().countAll(values);
|
||||
}
|
||||
|
||||
private AtomicInteger count = new AtomicInteger(0);
|
||||
/**
|
||||
* Aktuelle Anzahl gezählter Werte.
|
||||
*/
|
||||
private final AtomicInteger count = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* Statistiken zu den gezählten Werten.
|
||||
*/
|
||||
private double min = Double.NaN, max = Double.NaN, sum = Double.NaN;
|
||||
|
||||
public void Counter() {
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen, leeren {@code Counter}.
|
||||
*/
|
||||
public Counter() {
|
||||
}
|
||||
|
||||
public void Counter( int initial ) {
|
||||
/**
|
||||
* Ertstellt einen neuen {@code Counter}, der mit dem angegebenen Wert
|
||||
* initialisiert ist.
|
||||
*
|
||||
* @param initial Wert des Zählers zu Beginn.
|
||||
*/
|
||||
public Counter( int initial ) {
|
||||
count.set(initial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Anzahl zurück.
|
||||
* @return Die aktuelle Anzahl.
|
||||
*/
|
||||
public int getCount() {
|
||||
return count.get();
|
||||
}
|
||||
@@ -61,6 +129,8 @@ public final class Counter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Counter setCount( int count ) {
|
||||
this.count.set(count);
|
||||
return this;
|
||||
@@ -94,10 +164,10 @@ public final class Counter {
|
||||
inc();
|
||||
// Update stats
|
||||
synchronized( count ) {
|
||||
sum = Double.isNaN(sum) ? value : sum + value;
|
||||
sum = Double.isNaN(sum) ? value : sum + value;
|
||||
if( Double.isNaN(max) || max < value )
|
||||
max = value;
|
||||
if( Double.isNaN(min) ||min > value )
|
||||
if( Double.isNaN(min) || min > value )
|
||||
min = value;
|
||||
}
|
||||
return this;
|
||||
@@ -120,7 +190,15 @@ public final class Counter {
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public synchronized Counter countAll( double[] values ) {
|
||||
for( double value: values ) {
|
||||
for( double value : values ) {
|
||||
count(value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public synchronized Counter countAll( int[] values ) {
|
||||
for( double value : values ) {
|
||||
count(value);
|
||||
}
|
||||
return this;
|
||||
@@ -128,7 +206,7 @@ public final class Counter {
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public synchronized Counter countAll( Number[] values ) {
|
||||
for( Number value: values ) {
|
||||
for( Number value : values ) {
|
||||
count(value);
|
||||
}
|
||||
return this;
|
||||
@@ -136,7 +214,7 @@ public final class Counter {
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public synchronized Counter countAll( Collection<Number> values ) {
|
||||
for( Number value: values ) {
|
||||
for( Number value : values ) {
|
||||
count(value);
|
||||
}
|
||||
return this;
|
||||
|
||||
190
src/main/java/schule/ngb/zm/util/Faker.java
Normal file
190
src/main/java/schule/ngb/zm/util/Faker.java
Normal file
@@ -0,0 +1,190 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Zeichenmaschine;
|
||||
import schule.ngb.zm.util.io.FileLoader;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.font.LineMetrics;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Hilfsklasse, um zufällige Beispieldaten zu erzeugen.
|
||||
* <p>
|
||||
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen,
|
||||
* unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalterbilder.
|
||||
*/
|
||||
public final class Faker {
|
||||
|
||||
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
|
||||
|
||||
/**
|
||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
|
||||
*
|
||||
* @param n Anzahl der Beispieldaten.
|
||||
* @return Ein Array mit den Beispieldaten.
|
||||
*/
|
||||
public static String[][] fakeUsers( int n ) {
|
||||
String[][] data = loadMockfile("users");
|
||||
String[][] result = new String[n][];
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
result[i] = Constants.choice(data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Vornamen.
|
||||
*
|
||||
* @param n Anzahl der Beispieldaten.
|
||||
* @return Ein Array mit den Beispieldaten.
|
||||
*/
|
||||
public static String[] fakeNames( int n ) {
|
||||
String[][] data = loadMockfile("users");
|
||||
String[] result = new String[n];
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
String[] row = Constants.choice(data);
|
||||
result[i] = row[0];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen (Vor- und
|
||||
* Nachname).
|
||||
*
|
||||
* @param n Anzahl der Beispieldaten.
|
||||
* @return Ein Array mit den Beispieldaten.
|
||||
*/
|
||||
public static String[] fakeFullnames( int n ) {
|
||||
String[][] data = loadMockfile("users");
|
||||
String[] result = new String[n];
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
String[] row = Constants.choice(data);
|
||||
result[i] = row[0] + " " + row[1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
|
||||
*
|
||||
* @param n Anzahl der Beispieldaten.
|
||||
* @return Ein Array mit den Beispieldaten.
|
||||
*/
|
||||
public static String[] fakeUsernames( int n ) {
|
||||
String[][] data = loadMockfile("users");
|
||||
String[] result = new String[n];
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
String[] row = Constants.choice(data);
|
||||
result[i] = row[3];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Passwörter.
|
||||
*
|
||||
* @param n Anzahl der Beispieldaten.
|
||||
* @return Ein Array mit den Beispieldaten.
|
||||
*/
|
||||
public static String[] fakePasswords( int n ) {
|
||||
String[][] data = loadMockfile("users");
|
||||
String[] result = new String[n];
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
String[] row = Constants.choice(data);
|
||||
result[i] = row[4];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger E-Mail-Adressen.
|
||||
*
|
||||
* @param n Anzahl der Beispieldaten.
|
||||
* @return Ein Array mit den Beispieldaten.
|
||||
*/
|
||||
public static String[] fakeEmails( int n ) {
|
||||
String[][] data = loadMockfile("users");
|
||||
String[] result = new String[n];
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
String[] row = Constants.choice(data);
|
||||
result[i] = row[5];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Erzeugt ein Platzhalter-Bild in der angegebenen Größe.
|
||||
* <p>
|
||||
* Das Bild ist ein aus dem Internet geladenes, zufälliges Motiv, dass unter
|
||||
* einer freien Lizenz (Creative Commons) steht.
|
||||
*
|
||||
* @param width Breite des Bildes.
|
||||
* @param height Höhe des Bildes.
|
||||
* @return Ein zufälliges Bild in der angegebenen Größe.
|
||||
*/
|
||||
public static BufferedImage fakeImage( int width, int height ) {
|
||||
return fakeImage(width, height, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt ein Platzhalter-Bild in der angegebenen Größe.
|
||||
* <p>
|
||||
* Wenn {@code fromWeb} auf {@code true} gesetzt ist, wird ein zufälliges
|
||||
* Motiv, das unter einer freien Lizenz (Creative Commons) steht, geladen.
|
||||
* Bei {@code false} wird das Bild lokal generiert.
|
||||
*
|
||||
* @param width Breite des Bildes.
|
||||
* @param height Höhe des Bildes.
|
||||
* @param fromWeb Bei {@code true} wird das Bild aus dem Internet geladen,
|
||||
* bei {@code false} wird das Bild lokal erzeugt.
|
||||
* @return Ein zufälliges Bild in der angegebenen Größe.
|
||||
*/
|
||||
public static BufferedImage fakeImage( int width, int height, boolean fromWeb ) {
|
||||
if( !fromWeb ) {
|
||||
BufferedImage img = ImageLoader.createImage(width, height);
|
||||
Graphics2D graphics = (Graphics2D) img.getGraphics().create();
|
||||
|
||||
String text = width + " x " + height;
|
||||
|
||||
Color clr = Constants.randomNiceColor();
|
||||
|
||||
graphics.setBackground(clr.getJavaColor());
|
||||
graphics.clearRect(0, 0, width, height);
|
||||
|
||||
graphics.setColor(clr.textcolor().getJavaColor());
|
||||
graphics.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, (int) ((width + height) * 0.05)));
|
||||
FontMetrics fontMerics = graphics.getFontMetrics();
|
||||
LineMetrics lineMetrics = fontMerics.getLineMetrics(text, graphics);
|
||||
graphics.drawString(text,
|
||||
(int) ((width - fontMerics.stringWidth(text)) / 2),
|
||||
(int) (height / 2 - lineMetrics.getDescent() + lineMetrics.getAscent() / 2)
|
||||
);
|
||||
graphics.dispose();
|
||||
|
||||
return img;
|
||||
} else {
|
||||
return ImageLoader.loadImage(String.format(FAKE_IMG_URL, width, height), false);
|
||||
}
|
||||
}
|
||||
|
||||
public static String fakeText( int words, int paragraphs ) {
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String[][] loadMockfile( String name ) {
|
||||
return FileLoader.loadCsv("schule/ngb/zm/util/mock-" + name + ".csv", true);
|
||||
}
|
||||
|
||||
private Faker() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,8 @@ import static java.util.logging.Level.*;
|
||||
* 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
|
||||
* Die API orientiert sich lose an <a
|
||||
* href="https://logging.apache.org/log4j/2.x/">Log4j</a> und vereinfacht die
|
||||
* Nutzung der Java logging API für die häufigsten Anwendungsfälle.
|
||||
*/
|
||||
public final class Log {
|
||||
@@ -133,23 +134,13 @@ public final class Log {
|
||||
|
||||
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()) {
|
||||
@Override
|
||||
public synchronized void publish(final LogRecord record) {
|
||||
public synchronized void publish( final LogRecord record ) {
|
||||
super.publish(record);
|
||||
flush();
|
||||
}
|
||||
});
|
||||
// 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;
|
||||
@@ -208,26 +199,15 @@ public final class Log {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
for( StackTraceElement stackTraceElement : trace ) {
|
||||
if( !stackTraceElement.getClassName().equals(Log.class.getName()) ) {
|
||||
clazz = stackTraceElement.getClassName();
|
||||
method = stackTraceElement.getMethodName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -350,9 +330,9 @@ public final class Log {
|
||||
ZonedDateTime zdt = ZonedDateTime.ofInstant(
|
||||
record.getInstant(), ZoneId.systemDefault());
|
||||
String source;
|
||||
if (record.getSourceClassName() != null) {
|
||||
if( record.getSourceClassName() != null ) {
|
||||
source = record.getSourceClassName();
|
||||
if (record.getSourceMethodName() != null) {
|
||||
if( record.getSourceMethodName() != null ) {
|
||||
source += " " + record.getSourceMethodName();
|
||||
}
|
||||
} else {
|
||||
@@ -360,7 +340,7 @@ public final class Log {
|
||||
}
|
||||
String message = formatMessage(record);
|
||||
String throwable = "";
|
||||
if (record.getThrown() != null) {
|
||||
if( record.getThrown() != null ) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
pw.println();
|
||||
@@ -368,7 +348,7 @@ public final class Log {
|
||||
pw.close();
|
||||
throwable = sw.toString();
|
||||
}
|
||||
return String.format(DEFAULT_LOG_FORMAT,
|
||||
return String.format(DEFAULT_DEBUG_FORMAT,
|
||||
zdt,
|
||||
source,
|
||||
record.getLoggerName(),
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Random;
|
||||
* href="https://adrianb.io/2014/08/09/perlinnoise.html">Beschreibung von
|
||||
* FLAFLA2</a> implementiert.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Noise {
|
||||
|
||||
private static final int N = 256;
|
||||
@@ -42,7 +43,7 @@ public class Noise {
|
||||
/**
|
||||
* Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator.
|
||||
*
|
||||
* @param rand
|
||||
* @param rand Ein Zufallsgenerator-Objekt.
|
||||
*/
|
||||
public Noise( Random rand ) {
|
||||
init(rand);
|
||||
|
||||
@@ -2,77 +2,189 @@ package schule.ngb.zm.util;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Eine Hilfsklasse zur Zeitmessung im Nanosekundenbereich.
|
||||
* <p>
|
||||
* Die Klasse kann zum Beispiel genutzt werden, um die Laufzeit eines
|
||||
* Quelltextes zu messen. Wie eine echte Stoppuhr läuft der {@code Timer}
|
||||
* weiter, wenn nach einem {@link #stop()} wieder {@link #start()} aufgerufen
|
||||
* wird. Soll die Zeitmessung wieder bei null beginnen, muss vorher
|
||||
* {@link #reset()} genutzt werden.
|
||||
* <p>
|
||||
* Die gemessene Zeit kann in {@link #getMillis() Millisekunden} oder
|
||||
* {@link #getSeconds() Sekunden} abgerufen werden. Wird eine noch größere
|
||||
* Genauigkeit benötigt, können mit {@link #getTime(TimeUnit)} beliebige
|
||||
* Zeiteinheiten (zum Beispiel {@link TimeUnit#NANOSECONDS Nanosekunden})
|
||||
* abgerufen werden.
|
||||
* <p>
|
||||
* Die Zeit kann auch bei laufender Uhr abgefragt werden. In dem Fall wird die
|
||||
* bis zu diesem Zeitpunkt gemessene Zeit zurückgegeben.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public final class Timer {
|
||||
|
||||
/**
|
||||
* Die Basiseinheit für die Zeitmessung.
|
||||
*/
|
||||
private final TimeUnit baseUnit;
|
||||
|
||||
/**
|
||||
* Ob die Zeitmessung gerade läuft.
|
||||
*/
|
||||
private boolean running = false;
|
||||
|
||||
private long starttime = -1;
|
||||
/**
|
||||
* Startzeit der Zeitmessung. -1, wenn noch keine Messung gestartet wurde.
|
||||
*/
|
||||
private long start = -1;
|
||||
|
||||
/**
|
||||
* Zeit, die bisher bei allen Zeitmessungen, die gestoppt wurden, insgesamt
|
||||
* vergangen ist.
|
||||
*/
|
||||
private long elapsed = 0;
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen {@code Timer} mit Millisekunden als Basiseinheit.
|
||||
*/
|
||||
public Timer() {
|
||||
this(TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen {@code Timer}, der die angegebene Einheit als Basiseinheit
|
||||
* für {@link #getTime()} benutzt.
|
||||
* <p>
|
||||
* Um eine Zeitmessung in Nanosekunden durchzuführen, kann der {@code Timer}
|
||||
* beispielsweise so instanziiert werden:
|
||||
* <pre><code>
|
||||
* Timer clock = new Timer(TimeUnit.NANOSECONDS);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param baseUnit Die Basiseinheit für die Zeitmessung.
|
||||
*/
|
||||
public Timer( TimeUnit baseUnit ) {
|
||||
this.baseUnit = baseUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ob die Zeitmessung gerade läuft.
|
||||
*
|
||||
* @return {@code true}, wenn die Zeitmessung mit {@link #start()} gestartet
|
||||
* wurde.
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
/**
|
||||
* Startet die Zeitmessung.
|
||||
* <p>
|
||||
* Wenn zuvor schon eine Zeitmessung gestartet wurde, wird die neue Messung
|
||||
* zur Summe aller Messungen hinzuaddiert. Soll die Messung bei null
|
||||
* starten, muss vorher {@link #reset()} verwendet werden:
|
||||
*
|
||||
* <pre><code>
|
||||
* // Timer auf null stellen und sofort starten
|
||||
* timer.reset().start();
|
||||
* </code></pre>
|
||||
*
|
||||
* @return Dieser {@code Timer} selbst (method chaining).
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Timer start() {
|
||||
starttime = System.nanoTime();
|
||||
start = System.nanoTime();
|
||||
running = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stoppt den {@code Timer}, wenn er derzeit läuft. Die gemessene Zeit wird
|
||||
* zur Summe aller gemessenen Zeiten hinzuaddiert.
|
||||
*
|
||||
* @return Dieser {@code Timer} selbst (method chaining).
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Timer stop() {
|
||||
running = false;
|
||||
elapsed += System.nanoTime() - starttime;
|
||||
|
||||
if( running ) {
|
||||
running = false;
|
||||
elapsed += System.nanoTime() - start;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den {@code Timer} auf den Startzustand und löscht alle bisher
|
||||
* gemessenen Zeiten. Falls die Zeitmessung gerade läuft, wird sie nicht
|
||||
* gestoppt, sondern läuft vom Zeitpunkt des Aufrufs weiter.
|
||||
*
|
||||
* @return Dieser {@code Timer} selbst (method chaining).
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Timer reset() {
|
||||
running = false;
|
||||
starttime = -1;
|
||||
elapsed = 0;
|
||||
start = -1;
|
||||
|
||||
if( running ) {
|
||||
start = System.nanoTime();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Zeit in der Basiseinheit zurück.
|
||||
*
|
||||
* @return Die bisher insgesamt gemessene Zeit.
|
||||
*/
|
||||
public long getTime() {
|
||||
return getTime(baseUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Zeit in der angegebenen Einheit zurück.
|
||||
* <p>
|
||||
* Größere Zeiteinheiten werden gerundet und verlieren daher an Genauigkeit.
|
||||
* Eine Zeitmessung von 999 Millisekunden wird als 0 Sekunden
|
||||
* zurückgegeben.
|
||||
* <p>
|
||||
* Um genauere Ergebnisse zu erhalten, kann mit {@link #getMillis()} und
|
||||
* {@link #getSeconds()} die gemessene Zeit in Minuten beziehungsweise
|
||||
* Sekunden als Kommazahl abgefragt werden.
|
||||
*
|
||||
* @param unit Zeiteinheit
|
||||
* @return Die bisher insgesamt gemessene Zeit in der gewählten Zeiteinheit.
|
||||
*/
|
||||
public long getTime( TimeUnit unit ) {
|
||||
if( running ) {
|
||||
return unit.convert(System.nanoTime() - starttime + elapsed, TimeUnit.NANOSECONDS);
|
||||
return unit.convert(System.nanoTime() - start + elapsed, TimeUnit.NANOSECONDS);
|
||||
} else {
|
||||
return unit.convert(elapsed, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die gemessene Zeit in Millisekunden (gerundet) zurück.
|
||||
*
|
||||
* @return Die gemessene Zeit in ms.
|
||||
*/
|
||||
public int getMillis() {
|
||||
if( running ) {
|
||||
return (int) ((System.nanoTime() - starttime + elapsed) / 1000000);
|
||||
return (int) ((System.nanoTime() - start + elapsed) / 1000000);
|
||||
} else {
|
||||
return (int) (elapsed / 1000000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die gemessene Zeit in Sekunden zurück.
|
||||
*
|
||||
* @return Die gemessene Zeit in s.
|
||||
*/
|
||||
public double getSeconds() {
|
||||
if( running ) {
|
||||
return (System.nanoTime() - starttime + elapsed) / 1000000000.0;
|
||||
return (System.nanoTime() - start + elapsed) / 1000000000.0;
|
||||
} else {
|
||||
return elapsed / 1000000000.0;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public class Validator {
|
||||
|
||||
public static final <T> T requireNotNull( T obj ) {
|
||||
@@ -19,7 +19,7 @@ public class Validator {
|
||||
}
|
||||
|
||||
public static final String requireNotEmpty( String str ) {
|
||||
return requireNotEmpty(str, (Supplier<String>)null);
|
||||
return requireNotEmpty(str, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final String requireNotEmpty( String str, CharSequence msg ) {
|
||||
@@ -33,7 +33,7 @@ public class Validator {
|
||||
}
|
||||
|
||||
public static final int requirePositive( int i ) {
|
||||
return requirePositive(i, (Supplier<String>)null);
|
||||
return requirePositive(i, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final int requirePositive( int i, CharSequence msg ) {
|
||||
@@ -47,7 +47,7 @@ public class Validator {
|
||||
}
|
||||
|
||||
public static final int requireNotNegative( int i ) {
|
||||
return requireNotNegative(i, (Supplier<String>)null);
|
||||
return requireNotNegative(i, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final int requireNotNegative( int i, CharSequence msg ) {
|
||||
@@ -61,7 +61,7 @@ public class Validator {
|
||||
}
|
||||
|
||||
public static final int requireInRange( int i, int min, int max ) {
|
||||
return requireInRange(i, min, max, (Supplier<String>)null);
|
||||
return requireInRange(i, min, max, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final int requireInRange( int i, int min, int max, CharSequence msg ) {
|
||||
@@ -75,7 +75,7 @@ public class Validator {
|
||||
}
|
||||
|
||||
public static final double requirePositive( double i ) {
|
||||
return requirePositive(i, (Supplier<String>)null);
|
||||
return requirePositive(i, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final double requirePositive( double i, CharSequence msg ) {
|
||||
@@ -89,7 +89,7 @@ public class Validator {
|
||||
}
|
||||
|
||||
public static final double requireNotNegative( double i ) {
|
||||
return requireNotNegative(i, (Supplier<String>)null);
|
||||
return requireNotNegative(i, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final double requireNotNegative( double i, CharSequence msg ) {
|
||||
@@ -103,7 +103,7 @@ public class Validator {
|
||||
}
|
||||
|
||||
public static final double requireInRange( double i, double min, double max ) {
|
||||
return requireInRange(i, min, max, (Supplier<String>)null);
|
||||
return requireInRange(i, min, max, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final double requireInRange( double i, double min, double max, CharSequence msg ) {
|
||||
@@ -138,7 +138,7 @@ public class Validator {
|
||||
|
||||
|
||||
public static final <T> T[] requireNotEmpty( T[] arr ) {
|
||||
return requireNotEmpty(arr, (Supplier<String>)null);
|
||||
return requireNotEmpty(arr, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence msg ) {
|
||||
@@ -147,12 +147,12 @@ public class Validator {
|
||||
|
||||
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
||||
if( arr.length == 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be empty") : msg.get());
|
||||
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static final <T> T[] requireSize( T[] arr, int size ) {
|
||||
return requireSize(arr, size, (Supplier<String>)null);
|
||||
return requireSize(arr, size, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final <T> T[] requireSize( T[] arr, int size, CharSequence msg ) {
|
||||
@@ -161,21 +161,21 @@ public class Validator {
|
||||
|
||||
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());
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array must have <%d> elements (<%d> provided)", size, arr.length) : msg.get());
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static final <T> T[] requireValid( T[] arr ) {
|
||||
return requireValid(arr, (Supplier<String>)null);
|
||||
return requireValid(arr, (Supplier<String>) null);
|
||||
}
|
||||
|
||||
public static final<T> T[] requireValid( T[] arr, CharSequence msg ) {
|
||||
public static final <T> T[] requireValid( T[] arr, CharSequence msg ) {
|
||||
return requireValid(arr, msg::toString);
|
||||
}
|
||||
|
||||
public static final <T> T[] requireValid( T[] arr, Supplier<String> msg ) {
|
||||
if( arr == null || arr.length > 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be null or empty") : msg.get());
|
||||
throw new IllegalArgumentException(msg == null ? "Parameter array may not be null or empty" : msg.get());
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import java.util.function.BiConsumer;
|
||||
|
||||
public class EventDispatcher<E, L extends Listener<E>> {
|
||||
|
||||
private CopyOnWriteArraySet<L> listeners;
|
||||
private final CopyOnWriteArraySet<L> listeners;
|
||||
|
||||
private ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
||||
private final ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
||||
|
||||
public EventDispatcher() {
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
@@ -49,9 +49,7 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
||||
|
||||
if( eventRegistered(eventKey) ) {
|
||||
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
||||
listeners.forEach(( listener ) -> {
|
||||
dispatcher.accept(event, listener);
|
||||
});
|
||||
listeners.forEach(( listener ) -> dispatcher.accept(event, listener));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,4 @@ package schule.ngb.zm.util.events;
|
||||
|
||||
public interface Listener<E> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@ package schule.ngb.zm.util.io;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class FileLoader {
|
||||
|
||||
@@ -22,19 +25,31 @@ public final class FileLoader {
|
||||
|
||||
public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
|
||||
|
||||
|
||||
public static List<String> loadLines( String source ) {
|
||||
return loadLines(source, UTF8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt die angegebene Datei Zeilenweise in ein Array.
|
||||
*
|
||||
* @param source
|
||||
* @param charset
|
||||
* @return
|
||||
*/
|
||||
public static List<String> loadLines( String source, Charset charset ) {
|
||||
try {
|
||||
return Files.readAllLines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset);
|
||||
} catch( IOException | URISyntaxException ex ) {
|
||||
LOG.error(ex, "Error while loading lines from source <%s>", source);
|
||||
}
|
||||
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
return Collections.EMPTY_LIST;
|
||||
String line;
|
||||
while( (line = reader.readLine()) != null ) {
|
||||
result.add(line);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch( IOException ex ) {
|
||||
LOG.error(ex, "Error while loading lines from source <%s>", source);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public static String loadText( String source ) {
|
||||
@@ -42,63 +57,108 @@ public final class FileLoader {
|
||||
}
|
||||
|
||||
public static String loadText( String source, Charset charset ) {
|
||||
try {
|
||||
return Files.readString(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset);
|
||||
} catch( IOException | URISyntaxException ex ) {
|
||||
LOG.error(ex, "Error while loading text from source <%s>", source);
|
||||
}
|
||||
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
return "";
|
||||
String line;
|
||||
while( (line = reader.readLine()) != null ) {
|
||||
result.append(line).append('\n');
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
} catch( IOException ex ) {
|
||||
LOG.error(ex, "Error while loading string from source <%s>", source);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt die Daten aus einer CSV Datei in ein zweidimensionales
|
||||
* String-Array.
|
||||
* <p>
|
||||
* Der Aufruf entspricht dem Aufruf von
|
||||
* <pre><code>
|
||||
* FileLoader.loadCsv(source, ',', skipFirst, UTF8);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param source Die Quelle der CSV-Daten.
|
||||
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||
* @return Ein Array mit den Daten als {@code String}s.
|
||||
* @see #loadCsv(String, char, boolean, Charset)
|
||||
*/
|
||||
public static String[][] loadCsv( String source, boolean skipFirst ) {
|
||||
return loadCsv(source, ',', skipFirst, UTF8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt die Daten aus einer CSV Datei in ein zweidimensionales
|
||||
* String-Array.
|
||||
* <p>
|
||||
* Die Methode ist nicht in der Lage, komplexe CSV-Dateien zu verarbeiten.
|
||||
* Insbesondere können Inhalte, die das Trennzeichen {@code separator}
|
||||
* enthalten, nicht korrekt erkannt werden. Das Trennzeichen wird unabhängig
|
||||
* vom Kontext immer als Zelltrenner erkannt. (Im Normalfall kann das
|
||||
* Trennzeichen durch die Verwendung doppelter Anführungszeichen in der Art
|
||||
* {@code Inhalt,"Inhalt, der Komma enthält",Inhalt} maskiert werden.)
|
||||
* <p>
|
||||
* Es wird auch keine erweiterte Inhaltserkennung ausgeführt, sondern alle
|
||||
* Inhalte als {@code String} gelesen. Die weitere Verarbeitung mit den
|
||||
* passenden Parser-Methoden (beispielsweise
|
||||
* {@link Double#parseDouble(String)}) obligt dem Nutzer.
|
||||
*
|
||||
* @param source Die Quelle der CSV-Daten.
|
||||
* @param separator Das verwendete Trennzeichen.
|
||||
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||
* @param charset Die zu verwendende Zeichenkodierung.
|
||||
* @return Ein Array mit den Daten als {@code String}s.
|
||||
*/
|
||||
public static String[][] loadCsv( String source, char separator, boolean skipFirst, Charset charset ) {
|
||||
try {
|
||||
int n = skipFirst ? 1 : 0;
|
||||
return Files
|
||||
.lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset)
|
||||
.skip(n)
|
||||
.map(
|
||||
( line ) -> line.split(Character.toString(separator))
|
||||
).toArray(String[][]::new);
|
||||
} catch( IOException | URISyntaxException ex ) {
|
||||
LOG.error(ex, "Error while loading csv source <%s>", source);
|
||||
}
|
||||
|
||||
return new String[0][0];
|
||||
int n = skipFirst ? 1 : 0;
|
||||
List<String> lines = loadLines(source, charset);
|
||||
return lines.stream().skip(n).map(
|
||||
//( line ) -> line.split(Character.toString(separator))
|
||||
( line ) -> line.split("\\s*" + separator + "\\s*")
|
||||
).toArray(String[][]::new);
|
||||
}
|
||||
|
||||
public static double[][] loadValues( String source, char separator, boolean skipFirst ) {
|
||||
return loadValues(source, separator, skipFirst, UTF8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt Double-Werte aus einer CSV Datei in ein zweidimensionales Array.
|
||||
* <p>
|
||||
* Die gelesenen Strings werden mit {@link Double#parseDouble(String)} in
|
||||
* {@code double} umgeformt. Es leigt in der Verantwortung des Nutzers
|
||||
* sicherzustellen, dass die CSV-Datei auch nur Zahlen enthält, die korrekt
|
||||
* in {@code double} umgewandelt werden können. Zellen für die die
|
||||
* Umwandlung fehlschlägt werden mit 0.0 befüllt.
|
||||
* <p>
|
||||
* Die Methode unterliegt denselben Einschränkungen wie
|
||||
* {@link #loadCsv(String, char, boolean, Charset)}.
|
||||
*
|
||||
* @param source Die Quelle der CSV-Daten.
|
||||
* @param separator Das verwendete Trennzeichen.
|
||||
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||
* @param charset Die zu verwendende Zeichenkodierung.
|
||||
* @return Ein Array mit den Daten als {@code String}s.
|
||||
*/
|
||||
public static double[][] loadValues( String source, char separator, boolean skipFirst, Charset charset ) {
|
||||
try {
|
||||
int n = skipFirst ? 1 : 0;
|
||||
return Files
|
||||
.lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset)
|
||||
.skip(n)
|
||||
.map(
|
||||
( line ) -> Arrays
|
||||
.stream(line.split(Character.toString(separator)))
|
||||
.mapToDouble(
|
||||
( value ) -> {
|
||||
try {
|
||||
return Double.parseDouble(value);
|
||||
} catch( NumberFormatException nfe ) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
).toArray()
|
||||
).toArray(double[][]::new);
|
||||
} catch( IOException | URISyntaxException ex ) {
|
||||
LOG.error(ex, "Error while loading double values from csv source <%s>", source);
|
||||
}
|
||||
|
||||
return new double[0][0];
|
||||
int n = skipFirst ? 1 : 0;
|
||||
List<String> lines = loadLines(source, charset);
|
||||
return lines.stream().skip(n).map(
|
||||
( line ) -> Arrays
|
||||
.stream(line.split(Character.toString(separator)))
|
||||
.mapToDouble(
|
||||
( value ) -> {
|
||||
try {
|
||||
return Double.parseDouble(value);
|
||||
} catch( NumberFormatException nfe ) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
).toArray()
|
||||
).toArray(double[][]::new);
|
||||
}
|
||||
|
||||
public FileLoader() {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package schule.ngb.zm.util.io;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -18,23 +20,29 @@ public class FontLoader {
|
||||
/**
|
||||
* Lädt eine Schrift aus einer Datei.
|
||||
* <p>
|
||||
* Die Schrift wird unter ihrem Dateinamen in den Schriftenspeicher geladen
|
||||
* und kann danach in der Zeichenmaschine benutzt werden.
|
||||
*
|
||||
* Ein Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
||||
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
||||
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
||||
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das
|
||||
* Ergebnis {@code null}.
|
||||
* <p>
|
||||
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher
|
||||
* geladen und kann danach in der Zeichenmaschine benutzt werden.
|
||||
* <p>
|
||||
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
||||
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
||||
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
|
||||
* verwendet werden.
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
* @return Die erste geladene Schrift oder {@code null}.
|
||||
* @see #loadFont(String, String)
|
||||
*/
|
||||
public static Font loadFont( String source ) {
|
||||
String name = source;
|
||||
// Dateipfad entfernen
|
||||
int lastIndex = source.lastIndexOf(File.separatorChar);
|
||||
if( lastIndex > -1 ) {
|
||||
source.substring(lastIndex + 1);
|
||||
source = source.substring(lastIndex + 1);
|
||||
}
|
||||
// Dateiendung entfernen
|
||||
lastIndex = name.lastIndexOf('.');
|
||||
@@ -45,10 +53,8 @@ public class FontLoader {
|
||||
}
|
||||
|
||||
public static Font loadFont( String name, String source ) {
|
||||
Objects.requireNonNull(source, "Font source may not be null");
|
||||
if( source.length() == 0 ) {
|
||||
throw new IllegalArgumentException("Font source may not be empty.");
|
||||
}
|
||||
Validator.requireNotNull(source,"Font source may not be null");
|
||||
Validator.requireNotEmpty(source, "Font source may not be empty.");
|
||||
|
||||
if( fontCache.containsKey(name) ) {
|
||||
LOG.trace("Retrieved font <%s> from font cache.", name);
|
||||
@@ -57,7 +63,7 @@ public class FontLoader {
|
||||
|
||||
// Look for System fonts
|
||||
Font font = Font.decode(source);
|
||||
if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
||||
if( source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
||||
fontCache.put(name, font);
|
||||
fontCache.put(source, font);
|
||||
LOG.debug("Loaded system font for <%s>.", source);
|
||||
@@ -85,6 +91,38 @@ public class FontLoader {
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt eine Schrift aus einer Datei.
|
||||
* <p>
|
||||
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
||||
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
||||
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das
|
||||
* Ergebnis {@code null}.
|
||||
* <p>
|
||||
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher
|
||||
* geladen und kann danach in der Zeichenmaschine benutzt werden.
|
||||
* <p>
|
||||
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
||||
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
||||
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
|
||||
* verwendet werden.
|
||||
*
|
||||
* @param name
|
||||
* @param sources
|
||||
* @return Die erste geladene Schrift oder {@code null}.
|
||||
* @see #loadFont(String, String)
|
||||
*/
|
||||
public static Font loadFonts( String name, String... sources ) {
|
||||
for( String fontSource: sources ) {
|
||||
// TODO: Ignore exceptions in this case and throw own at end?
|
||||
Font font = loadFont(name, fontSource);
|
||||
if( font != null ) {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private FontLoader() {
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ public final class ImageLoader {
|
||||
|
||||
public static boolean caching = true;
|
||||
|
||||
private static Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
||||
|
||||
private static SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
||||
private static final SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
||||
|
||||
/**
|
||||
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
||||
@@ -48,12 +48,12 @@ public final class ImageLoader {
|
||||
|
||||
/**
|
||||
* Lädt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
||||
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden
|
||||
* konnte. Ist ein Bild mit der angegebenen Quelle im Cache, wird das
|
||||
* gespeicherte Bild zurückgegeben. Dies kann mit {@code caching = false}
|
||||
* verhindert werden.
|
||||
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden konnte.
|
||||
* Ist ein Bild mit der angegebenen Quelle im Cache, wird das gespeicherte
|
||||
* Bild zurückgegeben. Dies kann mit {@code caching = false} verhindert
|
||||
* werden.
|
||||
* <p>
|
||||
* Wurde das Chaching global deaktiviert, kann mit <code>caching =
|
||||
* Wurde das Caching global deaktiviert, kann mit <code>caching =
|
||||
* true</code> das Bild trotzdem aus dem Cache geladen werden, wenn es
|
||||
* vorhanden ist.
|
||||
*
|
||||
@@ -113,9 +113,9 @@ public final class ImageLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Speiechert das angegebene Bild unter dem angegebenen Namen im Cache.
|
||||
* Speichert das angegebene Bild unter dem angegebenen Namen im Cache.
|
||||
* <p>
|
||||
* Exisitert zu {@code name} schon ein Bild im Zwischenspeicher
|
||||
* Existiert zu {@code name} schon ein Bild im Zwischenspeicher
|
||||
* ({@code ImageLoader.isCached(name) == true}), dann wird dieses
|
||||
* überschrieben.
|
||||
* <p>
|
||||
@@ -149,9 +149,7 @@ public final class ImageLoader {
|
||||
* @param name Name des Bildes im Cache.
|
||||
*/
|
||||
public static void invalidateCache( final String name ) {
|
||||
if( imageCache.containsKey(name) ) {
|
||||
imageCache.remove(name);
|
||||
}
|
||||
imageCache.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,7 +209,7 @@ public final class ImageLoader {
|
||||
* 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
|
||||
* verweisen alle auf dasselbe Objekt. Änderungen schlagen 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:
|
||||
@@ -302,7 +300,7 @@ public final class ImageLoader {
|
||||
* @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.
|
||||
* wenn die Datei schon existiert.
|
||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||
* {@code null} ist.
|
||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||
@@ -335,7 +333,7 @@ public final class ImageLoader {
|
||||
* @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.
|
||||
* wenn die Datei schon existiert.
|
||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||
* {@code null} ist.
|
||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||
|
||||
@@ -5,13 +5,66 @@ import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Helferklasse, um {@link InputStream}s für Resourcen zu erhalten.
|
||||
*/
|
||||
public class ResourceStreamProvider {
|
||||
|
||||
|
||||
/**
|
||||
* 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 einer URL zu
|
||||
* einer bestehenden Resource.
|
||||
*/
|
||||
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||
Validator.requireNotNull(source, "Resource source may not be null");
|
||||
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
||||
|
||||
// Ist source ein valider Dateipfad?
|
||||
File file = new File(source);
|
||||
if( file.isFile() ) {
|
||||
return file.toURI().toURL();
|
||||
}
|
||||
// Ist source im Classpath vorhanden?
|
||||
URL url = Zeichenmaschine.class.getClassLoader().getResource(source);
|
||||
if( url != null ) {
|
||||
return url;
|
||||
}
|
||||
// Dann versuchen aus source direkt eine URL zu machen.
|
||||
return new URL(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht eine zur angegebenen Quelle passende Resource und öffnet einen
|
||||
* passenden {@link InputStream}. Die konkrete Art des Streams hängt davon
|
||||
@@ -45,134 +98,41 @@ public class ResourceStreamProvider {
|
||||
* @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
|
||||
* @throws IOException Geworfen beim Öffnen des Streams zu
|
||||
* einer bestehenden Resource oder falls
|
||||
* keine passende Resource gefunden wurde.
|
||||
*/
|
||||
public static InputStream getInputStream( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||
Validator.requireNotNull(source, "Resource source may not be null");
|
||||
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
||||
|
||||
InputStream in = null;
|
||||
|
||||
// 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
|
||||
if( in == null ) {
|
||||
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;
|
||||
}
|
||||
|
||||
public static InputStream getInputStream( File file ) throws IOException {
|
||||
Validator.requireNotNull(file, "Provided file can't be null.");
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
public static InputStream getInputStream( URL url ) throws IOException {
|
||||
Validator.requireNotNull(url, "Provided URL can't be null.");
|
||||
return url.openStream();
|
||||
return getResourceURL(source).openStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
Validator.requireNotNull(source, "Resource source may not be null");
|
||||
Validator.requireNotEmpty(source, "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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ver
|
||||
* @param source
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static OutputStream getOutputStream( String source ) throws IOException {
|
||||
Validator.requireNotNull(source, "Resource source may not be null");
|
||||
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
||||
URL url = getResourceURL(source);
|
||||
return getOutputStream(new File(url.getPath()));
|
||||
try {
|
||||
return Files.newOutputStream(Path.of(getResourceURL(source).toURI()));
|
||||
} catch( URISyntaxException ex ) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static OutputStream getOutputStream( File file ) throws IOException {
|
||||
Validator.requireNotNull(file, "Provided file can't be null.");
|
||||
return new FileOutputStream(file);
|
||||
public static BufferedReader getReader( String source ) throws IOException {
|
||||
return getReader(source, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static Reader getReader( String source ) throws IOException {
|
||||
return new InputStreamReader(getInputStream(source));
|
||||
public static BufferedReader getReader( String source, Charset charset ) throws IOException {
|
||||
return new BufferedReader(new InputStreamReader(getInputStream(source), charset.newDecoder()));
|
||||
}
|
||||
|
||||
public static Writer getWriter( String source ) throws IOException {
|
||||
return new OutputStreamWriter(getOutputStream(source));
|
||||
public static BufferedWriter getWriter( String source ) throws IOException {
|
||||
return getWriter(source, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static BufferedWriter getWriter( String source, Charset charset ) throws IOException {
|
||||
return new BufferedWriter(new OutputStreamWriter(getOutputStream(source), charset.newEncoder()));
|
||||
}
|
||||
|
||||
private ResourceStreamProvider() {
|
||||
|
||||
@@ -5,35 +5,18 @@ import schule.ngb.zm.Zeichenmaschine;
|
||||
|
||||
public abstract class FrameSynchronizedTask extends Task {
|
||||
|
||||
private static Thread mainThread;
|
||||
|
||||
private static final Thread getMainThread() {
|
||||
if( mainThread == null ) {
|
||||
mainThread = Thread.currentThread();
|
||||
if( !mainThread.getName().equals(Constants.APP_NAME) ) {
|
||||
// Need to search for main Zeichenthread ...
|
||||
}
|
||||
}
|
||||
return mainThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
initialize();
|
||||
running = true;
|
||||
|
||||
Object lock = Zeichenmaschine.globalSyncLock;
|
||||
final Object lock = Zeichenmaschine.globalSyncLock;
|
||||
|
||||
// start of thread in ms
|
||||
final long start = System.currentTimeMillis();
|
||||
// current time in ns
|
||||
long beforeTime = System.nanoTime();
|
||||
// store for deltas
|
||||
long overslept = 0L;
|
||||
// internal counters for tick and runtime
|
||||
int _tick = 0;
|
||||
|
||||
double delta = 0.0;
|
||||
// internal counters for tick and delta time
|
||||
int _tick;
|
||||
double delta;
|
||||
|
||||
while( running ) {
|
||||
// delta in seconds
|
||||
@@ -53,10 +36,7 @@ public abstract class FrameSynchronizedTask extends Task {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
running = false;
|
||||
done = true;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public abstract class RateLimitedTask extends Task {
|
||||
// store for deltas
|
||||
long overslept = 0L;
|
||||
// delta in ms
|
||||
double delta = 0;
|
||||
double delta;
|
||||
|
||||
running = true;
|
||||
while( running ) {
|
||||
@@ -47,10 +47,7 @@ public abstract class RateLimitedTask extends Task {
|
||||
// Did we sleep to long?
|
||||
overslept = (System.nanoTime() - afterTime) - sleep;
|
||||
}
|
||||
|
||||
running = false;
|
||||
done = true;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@ import java.util.concurrent.*;
|
||||
*/
|
||||
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() {
|
||||
|
||||
3001
src/main/resources/schule/ngb/zm/util/mock-users.csv
Normal file
3001
src/main/resources/schule/ngb/zm/util/mock-users.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,9 @@ package schule.ngb.zm;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ConstantsTest {
|
||||
@@ -169,4 +172,30 @@ class ConstantsTest {
|
||||
assertTrue(0.0 <= d && 1.0 >= d, "Noise should be in Range 0 to 1. Was <" + d + ">.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void choice() {
|
||||
int[] values = IntStream.range(0,1000).toArray();
|
||||
int[] choosen = Constants.choice(values, 1, false);
|
||||
assertEquals(1, choosen.length);
|
||||
assertTrue(Arrays.stream(values).anyMatch((i) -> i == choosen[0]));
|
||||
|
||||
int[] choosen4 = Constants.choice(values, 2000, false);
|
||||
assertEquals(2000, choosen4.length);
|
||||
|
||||
int[] choosen2 = Constants.choice(values, 400, false);
|
||||
assertEquals(400, choosen2.length);
|
||||
assertTrue(Arrays.stream(choosen2).allMatch((i) -> Arrays.binarySearch(values, i)>=0 ));
|
||||
|
||||
int[] choosen3 = Constants.choice(values, 999, true);
|
||||
assertEquals(999, choosen3.length);
|
||||
assertTrue(Arrays.stream(choosen3).allMatch((i) -> Arrays.binarySearch(values, i)>=0 ));
|
||||
for( int i = 0; i < choosen3.length; i++ ) {
|
||||
for( int j = i+1; j < choosen3.length; j++ ) {
|
||||
assertNotEquals(choosen3[i], choosen3[j], "Item "+i+" also found at index "+j);
|
||||
}
|
||||
}
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> Constants.choice(values, 2000, true));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.shapes.*;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
public class TestInput extends Zeichenmaschine {
|
||||
|
||||
public static void main( String[] args ) {
|
||||
@@ -23,7 +21,7 @@ public class TestInput extends Zeichenmaschine {
|
||||
shapes.getShape(1).setFillColor(PINK);
|
||||
|
||||
text = new Text(10, 42, "");
|
||||
text.setFontsize(32);
|
||||
text.setFontSize(32);
|
||||
text.setAnchor(NORTHWEST);
|
||||
shapes.add(text);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user