7 Commits

7 changed files with 201 additions and 16 deletions

8
.gitignore vendored
View File

@@ -48,3 +48,11 @@ gradle-app.setting
# Cache of project # Cache of project
.gradletasknamecache .gradletasknamecache
# Python mkdocs
.venv
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

0
docs/quickstart.md Normal file
View File

View File

@@ -5,10 +5,12 @@ import schule.ngb.zm.util.io.ImageLoader;
import schule.ngb.zm.util.Noise; import schule.ngb.zm.util.Noise;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.Font;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Random; import java.util.Random;
import java.util.function.DoubleUnaryOperator; import java.util.function.DoubleUnaryOperator;
@@ -65,7 +67,7 @@ public class Constants {
/** /**
* Patchversion der Zeichenmaschine. * Patchversion der Zeichenmaschine.
*/ */
public static final int APP_VERSION_REV = 31; public static final int APP_VERSION_REV = 32;
/** /**
* Version der Zeichenmaschine als Text-String. * Version der Zeichenmaschine als Text-String.
@@ -442,6 +444,21 @@ public class Constants {
*/ */
public static final double TWO_PI = Math.PI * 2.0; 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 * Globale Variablen, die von allen Klassen genutzt werden dürfen. Änderungen
* wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der * wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der
@@ -1369,8 +1386,8 @@ public class Constants {
int[] result = new int[n]; int[] result = new int[n];
int[] valuesCopy = Arrays.copyOf(values, values.length); int[] valuesCopy = Arrays.copyOf(values, values.length);
for( int i = 0; i < n; i++ ) { for( int i = 0; i < n; i++ ) {
int j = random(0, valuesCopy.length-1); int j = random(0, valuesCopy.length - 1);
int l = valuesCopy.length-1; int l = valuesCopy.length - 1;
result[i] = valuesCopy[j]; result[i] = valuesCopy[j];
valuesCopy[j] = valuesCopy[l]; valuesCopy[j] = valuesCopy[l];
@@ -1387,7 +1404,12 @@ public class Constants {
* *
* @param values Ein Array mit Werten, die zur Auswahl stehen. * @param values Ein Array mit Werten, die zur Auswahl stehen.
* @param n Anzahl der auszuwählenden Elemente. * @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. * @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 ) { public static final double[] choice( double[] values, int n, boolean unique ) {
if( unique && values.length < n ) if( unique && values.length < n )
@@ -1398,8 +1420,8 @@ public class Constants {
double[] result = new double[n]; double[] result = new double[n];
double[] valuesCopy = Arrays.copyOf(values, values.length); double[] valuesCopy = Arrays.copyOf(values, values.length);
for( int i = 0; i < n; i++ ) { for( int i = 0; i < n; i++ ) {
int j = random(0, valuesCopy.length-1); int j = random(0, valuesCopy.length - 1);
int l = valuesCopy.length-1; int l = valuesCopy.length - 1;
result[i] = valuesCopy[j]; result[i] = valuesCopy[j];
valuesCopy[j] = valuesCopy[l]; valuesCopy[j] = valuesCopy[l];
@@ -1416,8 +1438,13 @@ public class Constants {
* *
* @param values Ein Array mit Werten, die zur Auswahl stehen. * @param values Ein Array mit Werten, die zur Auswahl stehen.
* @param n Anzahl der auszuwählenden Elemente. * @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. * @param <T> Datentyp der Elemente.
* @return Ein zufälliges Element aus dem Array. * @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, boolean unique ) { public static final <T> T[] choice( T[] values, int n, boolean unique ) {
if( unique && values.length < n ) if( unique && values.length < n )
@@ -1428,7 +1455,7 @@ public class Constants {
T[] result = Arrays.copyOf(values, n); T[] result = Arrays.copyOf(values, n);
T[] valuesCopy = Arrays.copyOf(values, values.length); T[] valuesCopy = Arrays.copyOf(values, values.length);
for( int i = 0; i < n; i++ ) { for( int i = 0; i < n; i++ ) {
int last = valuesCopy.length-1; int last = valuesCopy.length - 1;
int j = random(0, last); int j = random(0, last);
result[i] = valuesCopy[j]; result[i] = valuesCopy[j];
@@ -1441,6 +1468,53 @@ public class Constants {
return 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);
}
/** /**
* Geteilte {@code Noise}-Instanz zur Erzeugung von Perlin-Noise. * Geteilte {@code Noise}-Instanz zur Erzeugung von Perlin-Noise.
*/ */

View File

@@ -130,7 +130,7 @@ public class Zeichenfenster extends JFrame {
taskbar.setIconImage(icon); taskbar.setIconImage(icon);
} else { } else {
ArrayList<Image> icons = new ArrayList<>(4); 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"))); icons.add(ImageIO.read(new File("icon_" + size + ".png")));
} }

View File

@@ -427,9 +427,18 @@ public class Zeichenmaschine extends Constants {
* im Zeichenfenster an. * im Zeichenfenster an.
*/ */
public final void redraw() { public final void redraw() {
if( state == Options.AppState.PAUSED ) { if( state == Options.AppState.PAUSED
|| state == Options.AppState.TERMINATED ) {
draw(); draw();
} }
render();
}
/**
* Zeigt den aktuellen Inhalt der {@link Zeichenleinwand}
* im Zeichenfenster an, ohne vorher {@link #draw()} aufzurufen.
*/
public final void render() {
canvas.render(); canvas.render();
// canvas.invalidate(); // canvas.invalidate();
// frame.repaint(); // frame.repaint();
@@ -1091,6 +1100,9 @@ public class Zeichenmaschine extends Constants {
} }
if( isPaused() || isTerminated() ) { if( isPaused() || isTerminated() ) {
if( MouseEvent.class.isInstance(evt) ) {
saveMousePosition((MouseEvent)evt);
}
dispatchEvents(); dispatchEvents();
} }
} }

View File

@@ -250,7 +250,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
@Override @Override
public void setGradient( Color from, Color to ) { public void setGradient( Color from, Color to ) {
shapeDelegate.setGradient(getWidth()/2.0, getHeight()/2.0, min(getWidth(), getHeight())/2.0, from, to); shapeDelegate.setGradient(getWidth() / 2.0, getHeight() / 2.0, min(getWidth(), getHeight()) / 2.0, from, to);
} }
/** /**
@@ -466,10 +466,27 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
rect(x, y, w, w, 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 ) { public void rect( double x, double y, double w, double h ) {
rect(x, y, w, h, shapeDelegate.getAnchor()); 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 ) { public void rect( double x, double y, double w, double h, Options.Direction anchor ) {
Point2D.Double anchorPoint = this.getOriginPoint(x, y, w, h, anchor); Point2D.Double anchorPoint = this.getOriginPoint(x, y, w, h, anchor);
rect.setRect(anchorPoint.getX(), anchorPoint.getY(), w, h); rect.setRect(anchorPoint.getX(), anchorPoint.getY(), w, h);
@@ -477,6 +494,42 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
drawShape(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 ) { public void point( double x, double y ) {
ellipse(x - 1, y - 1, 2, 2, shapeDelegate.getAnchor()); ellipse(x - 1, y - 1, 2, 2, shapeDelegate.getAnchor());
} }

View File

@@ -1,12 +1,14 @@
package schule.ngb.zm.util.io; package schule.ngb.zm.util.io;
import schule.ngb.zm.util.Log; import schule.ngb.zm.util.Log;
import schule.ngb.zm.util.Validator;
import java.awt.Font; import java.awt.Font;
import java.awt.FontFormatException; import java.awt.FontFormatException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -18,8 +20,13 @@ public class FontLoader {
/** /**
* Lädt eine Schrift aus einer Datei. * Lädt eine Schrift aus einer Datei.
* <p> * <p>
* Die Schrift wird unter ihrem Dateinamen in den Schriftenspeicher geladen * Die Methode kann eine Liste von Schriften bekommen und probiert diese
* und kann danach in der Zeichenmaschine benutzt werden. * 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> * <p>
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen * Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
* "Font-Name" geladen und kann danach zum Beispiel in einem * "Font-Name" geladen und kann danach zum Beispiel in einem
@@ -27,7 +34,8 @@ public class FontLoader {
* verwendet werden. * verwendet werden.
* *
* @param source * @param source
* @return * @return Die erste geladene Schrift oder {@code null}.
* @see #loadFont(String, String)
*/ */
public static Font loadFont( String source ) { public static Font loadFont( String source ) {
String name = source; String name = source;
@@ -45,10 +53,8 @@ public class FontLoader {
} }
public static Font loadFont( String name, String source ) { public static Font loadFont( String name, String source ) {
Objects.requireNonNull(source, "Font source may not be null"); Validator.requireNotNull(source,"Font source may not be null");
if( source.length() == 0 ) { Validator.requireNotEmpty(source, "Font source may not be empty.");
throw new IllegalArgumentException("Font source may not be empty.");
}
if( fontCache.containsKey(name) ) { if( fontCache.containsKey(name) ) {
LOG.trace("Retrieved font <%s> from font cache.", name); LOG.trace("Retrieved font <%s> from font cache.", name);
@@ -85,6 +91,38 @@ public class FontLoader {
return font; 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() { private FontLoader() {
} }