Refactorings und Dokumentation

This commit is contained in:
ngb
2022-01-08 22:02:26 +01:00
parent 812c9fe4d4
commit f1e422e98a
4 changed files with 493 additions and 108 deletions

View File

@@ -2,6 +2,7 @@ package schule.ngb.zm;
import schule.ngb.zm.util.ImageLoader;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
@@ -71,6 +72,11 @@ public class Constants {
public static final Color BROWN = Color.BROWN;
public static final Color STD_BACKGROUND = new Color(200, 200, 200);
public static final int NOBUTTON = MouseEvent.NOBUTTON;
public static final int BUTTON1 = MouseEvent.BUTTON1;
public static final int BUTTON2 = MouseEvent.BUTTON2;
public static final int BUTTON3 = MouseEvent.BUTTON3;
//@formatter:on

View File

@@ -29,7 +29,9 @@ public final class Options {
INITIALIZING,
INITIALIZED,
RUNNING,
AUSED,
UPDATING,
DRAWING,
PAUSED,
STOPPED,
TERMINATED
}

View File

@@ -2,7 +2,10 @@ package schule.ngb.zm;
import schule.ngb.zm.shapes.ShapesLayer;
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;
@@ -12,7 +15,7 @@ import java.util.LinkedList;
* mehreren Ebenen, auf denen auf verschiedene Arten gezeichnet werden kann. Die
* Ebenen lassen sich beliebig übereinander aNORTHen, ausblenden oder wieder
* löschen.
*
* <p>
* Jede Ebene besitzt eine Zeichenfläche, auf der ihre Zeichnung liegt. Diese
* zeichenflächen werden pro Frame einmal von "DOWN" nach "UP" auf diese
* Leinwand gezeichnet.
@@ -40,7 +43,7 @@ public class Zeichenleinwand extends Canvas {
* Ändert die Größe der Zeichenleinwand auf die angegebene Größe in Pixeln.
* Eine Größenänderung hat auch eine Größenänderung aller Ebenen zur Folge.
*
* @param width Neue Width der Leinwand in Pixeln.
* @param width Neue Width der Leinwand in Pixeln.
* @param height Neue Höhe der Leinwand in Pixeln.
*/
@Override
@@ -59,6 +62,7 @@ public class Zeichenleinwand extends Canvas {
/**
* Fügt der Zeichenleinwand eine Ebene hinzu, die oberhalb aller bisherigen
* Ebenen eingefügt wird.
*
* @param layer Die neue Ebene.
*/
public void addLayer( Layer layer ) {
@@ -72,7 +76,8 @@ public class Zeichenleinwand extends Canvas {
/**
* Fügt der Zeichenleinwand eine Ebene an einer bestimmten Stelle hinzu.
* @param i Index der Ebene, beginnend mit <code>0</code>.
*
* @param i Index der Ebene, beginnend mit <code>0</code>.
* @param layer Die neue Ebene.
*/
public void addLayer( int i, Layer layer ) {
@@ -84,8 +89,18 @@ public class Zeichenleinwand extends Canvas {
}
}
/**
* Gibt die Anzahl der {@link Layer Ebenen} in dieser Leinwand zurück.
*
* @return Die Anzahl der Ebenen.
*/
public int getLayerCount() {
return layers.size();
}
/**
* Gibt die Liste der bisher hinzugefügten Ebenen zurück.
*
* @return Liste der Ebenen.
*/
public java.util.List<Layer> getLayers() {
@@ -193,7 +208,7 @@ public class Zeichenleinwand extends Canvas {
} while( strategy.contentsRestored() );
// Display the buffer
if (!strategy.contentsLost()) {
if( !strategy.contentsLost() ) {
strategy.show();
Toolkit.getDefaultToolkit().sync();
@@ -204,4 +219,5 @@ public class Zeichenleinwand extends Canvas {
}
}
}
}

View File

@@ -23,6 +23,9 @@ import java.io.IOException;
*/
public class Zeichenmaschine extends Constants implements MouseInputListener, KeyListener {
/**
* Gibt an, ob die Zeichenmaschine aus BlueJ heraus gestartet wurde.
*/
public static boolean IN_BLUEJ;
static {
@@ -30,114 +33,240 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
/*
* Attributes to be accessed by subclasses.
* Objektvariablen, die von Unterklassen benutzt werden können.
*/
/**
* Die Leinwand, auf die alles gezeichnet wird. Die Leinwand enthält
* {@link Layer Ebenen}, die einzelne Zeichnungen enthalten. Die Inhalte
* aller Ebenen werden einmal pro Frame auf die Hauptleinwand übertragen.
*/
protected Zeichenleinwand canvas;
/**
* Ebene mit der Hintergrundfarbe.
*/
protected ColorLayer background;
/**
* Zeichenebene
*/
protected DrawingLayer drawing;
/**
* Formenebene
*/
protected ShapesLayer shapes;
/**
* Anzahl der Ticks (Frames), die das Programm bisher läuft.
*/
protected int tick = 0;
/**
* Die Zeit in Millisekunden, die das Programm seit seinem Start läuft.
*/
protected long runtime = 0L;
/**
* Der Zeitunterschied zum letzten Frame in Sekunden.
*/
protected double delta = 0.0;
protected double mouseX = 0.0, mouseY = 0.0, pmouseX = 0.0, pmouseY = 0.0;
/**
* Die aktuelle {@code x}-Koordinate der Maus.
*/
protected double mouseX = 0.0;
/**
* Die aktuelle {@code y}-Koordinate der Maus.
*/
protected double mouseY = 0.0;
/**
* Die letzte {@code x}-Koordinate der Maus (wird einmal pro Frame
* aktualisiert).
*/
protected double pmouseX = 0.0;
/**
* Die letzte {@code y}-Koordinate der Maus (wird einmal pro Frame
* aktualisiert).
*/
protected double pmouseY = 0.0;
/**
* Gibt an, ob ein Mausknopf derzeit gedrückt ist.
*/
protected boolean mousePressed = false;
protected int mouseButton = 0;
/**
* Der aktuell gedrückte Mausknopf. Die Mausknöpfe werden durch die Konstanten
* {@link #NOBUTTON}, {@link #BUTTON1}, {@link #BUTTON2} und {@link #BUTTON3}
* angegeben. (Sie stimmen mit den Konstanten in {@link MouseEvent} überein.
*
* @see MouseEvent
*/
protected int mouseButton = NOBUTTON;
protected char key = ' ';
protected int keyCode = 0;
/**
* Das zuletzt ausgelöste {@code MouseEvent}.
*/
protected MouseEvent mouseEvent;
/**
* Gibt an, ob derzeit eine Taste gedrückt ist.
*/
protected boolean keyPressed = false;
protected int width, height;
/**
* Das Zeichen der zuletzt gedrückten Taste.
*/
protected char key = ' ';
protected int screenWidth, screenHeight;
/**
* Der Tastencode der zuletzt gedrückten Taste. Die Keycodes können in der
* Klasse {@link KeyEvent} nachgesehen werden. (Zum Beispiel
* {@link KeyEvent#VK_ENTER}.)
*/
protected int keyCode = 0;
/**
* Das zuletzt ausgelöste {@link KeyEvent}.
*/
protected KeyEvent keyEvent;
/**
* Die Höhe der Zeichenleinwand.
*/
protected int width;
/**
* Die Breite der Zeichenleinwand.
*/
protected int height;
/**
* Die Breite des Bildschirms, auf dem das Zeichenfenster geöffnet wurde.
* <p>
* Beachte, dass sich die Breite nicht anpasst, wenn das Zeichenfenster auf
* einen anderen Bildschirm verschoben wird.
*/
protected int screenWidth;
/**
* Die Höhe des Bildschirms, auf dem das Zeichenfenster geöffnet wurde.
* <p>
* Beachte, dass sich die Höhe nicht anpasst, wenn das Zeichenfenster auf
* einen anderen Bildschirm verschoben wird.
*/
protected int screenHeight;
/*
* Interne Attribute zur Steuerung der Zeichenmaschine.
*/
//@formatter:off
// Das Zeichenfenster der Zeichenmaschine
private JFrame frame;
// Die Graphics-Objekte für das aktuelle Fenster.
private GraphicsEnvironment environment;
private GraphicsDevice displayDevice;
private boolean running = false, isDrawing = false, isUpdating = false;
// Aktueller Zustand der Zeichenmaschine.
private Options.AppState state = Options.AppState.INITIALIZING;
private boolean running = false;
// Aktuelle Frames pro Sekunde der Zeichenmaschine.
private int framesPerSecond;
// Hauptthread der Zeichenmaschine.
private Thread mainThread;
private boolean quitAfterTeardown = false, initialized = false;
// Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
// oder das Zeichenfenster weiter geöffnet bleibt.
private boolean quitAfterTeardown = false;
//@formatter:on
/**
* Erstellt eine neue Zeichenmaschine.
*/
public Zeichenmaschine() {
this(APP_NAME + " " + APP_VERSION);
}
/**
* Erstellt eine neue Zeichenmaschine mit dem angegebene Titel.
*
* @param title Der Titel, der oben im Fenster steht.
*/
public Zeichenmaschine( String title ) {
this(STD_WIDTH, STD_HEIGHT, title);
}
/**
* Erstellt eine neue zeichenmaschine mit einer Leinwand der angebenen
* Größe und dem angegebenen Titel.
*
* @param width Breite der {@link Zeichenleinwand Zeichenleinwand}.
* @param height Höhe der {@link Zeichenleinwand Zeichenleinwand}.
* @param title Der Titel, der oben im Fenster steht.
*/
public Zeichenmaschine( int width, int height, String title ) {
// Setzen des "Look&Feel"
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch( Exception e ) {
System.err.println("Error setting the look and feel: " + e.getMessage());
}
// Looking for the screen currently holding the mouse pointer
// that will be used as the screen device for this Zeichenmaschine
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
// das Zeichenfenster dort zu zentrieren.
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = environment.getScreenDevices();
for( GraphicsDevice gd: devices ) {
for( GraphicsDevice gd : devices ) {
if( gd.getDefaultConfiguration().getBounds().contains(mouseLoc) ) {
displayDevice = gd;
break;
}
}
// Keinen passenden Bildschirm gefunden. Wir nutzen den Standard.
if( displayDevice == null ) {
displayDevice = environment.getDefaultScreenDevice();
}
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
this.width = width;
this.height = height;
java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds();
this.screenWidth = (int)displayBounds.getWidth();
this.screenHeight = (int)displayBounds.getHeight();
this.screenWidth = (int) displayBounds.getWidth();
this.screenHeight = (int) displayBounds.getHeight();
// Erstellen des Zeichenfensters
frame = new JFrame(displayDevice.getDefaultConfiguration());
frame.setTitle(title);
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
// Erstellen der Leinwand
canvas = new Zeichenleinwand(width, height);
frame.add(canvas);
framesPerSecond = STD_FPS;
// Die drei Standardebenen merken, für den einfachen Zugriff aus unterklassen.
background = getBackgroundLayer();
drawing = getDrawingLayer();
shapes = getShapesLayer();
// FPS setzen
framesPerSecond = STD_FPS;
// Settings der Unterklasse aufrufen, falls das Fenster vor dem Öffnen
// verändert werden soll.
// TODO: When to call settings?
settings();
// Listener hinzufügen, um auf Maus- und Tastatureingaben zu hören.
canvas.addMouseListener(this);
canvas.addMouseMotionListener(this);
canvas.addKeyListener(this);
@@ -153,40 +282,42 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
});
// Fenster zusammenbauen, auf dem Bildschirm zentrieren ...
frame.pack();
frame.setResizable(false);
centerFrame();
// ... und anzeigen!
frame.setVisible(true);
// Nach dem Anzeigen kann die Pufferstrategie erstellt werden.
canvas.allocateBuffer();
// Erstellen des Haupt-Zeichenthreads.
running = true;
mainThread = new Zeichenthread();
mainThread.start();
//frame.requestFocusInWindow();
canvas.requestFocus();
initialized = true;
// Fertig mit der Initialisierung!
state = Options.AppState.INITIALIZED;
// Los geht's ...
mainThread.start();
}
/**
* Erstellt ein neues Zeichenfesnter mit der aktuellen Konfiguration.
*
* @param title
*/
// TODO: Implement in conjunction with Zeichenfenster
public final void createFrame( String title ) {
}
public void show() {
if( !frame.isVisible() ) {
frame.setVisible(true);
}
}
public void hide() {
if( frame.isVisible() ) {
frame.setVisible(false);
}
private final Zeichenfenster createFrame( String title ) {
return null;
}
/**
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
*/
public final void centerFrame() {
// TODO: Center on current display (not main display by default)
// TODO: Position at current BlueJ windows if IN_BLUEJ
@@ -195,11 +326,37 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
java.awt.Rectangle bounds = displayDevice.getDefaultConfiguration().getBounds();
frame.setLocation(
(int)(bounds.x + (screenWidth-frame.getWidth())/2.0),
(int)(bounds.y + (screenHeight-frame.getHeight())/2.0)
(int) (bounds.x + (screenWidth - frame.getWidth()) / 2.0),
(int) (bounds.y + (screenHeight - frame.getHeight()) / 2.0)
);
}
/**
* Zeigt das Zeichenfenster an.
*
* @see JFrame#setVisible(boolean)
*/
public void show() {
if( !frame.isVisible() ) {
frame.setVisible(true);
}
}
/**
* Versteckt das Zeichenfenster.
*
* @see JFrame#setVisible(boolean)
*/
public void hide() {
if( frame.isVisible() ) {
frame.setVisible(false);
}
}
/**
* Zeichnet die {@link Zeichenleinwand} neu und zeigt den aktuellen Inhalt
* im Zeichenfenster an.
*/
public final void redraw() {
canvas.render();
//canvas.invalidate();
@@ -208,15 +365,30 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
//show();
}
/**
* Stoppt den Zeichenthread. Nachdem der aktuelle Frame gezeichnet wurde
* wird {@link #teardown()} aufgerufen. Das Programm wird nicht beendet und
* das Zeichenfenster bleibt weiter angezeigt.
*/
public final void stop() {
running = false;
}
/**
* Beendet das Programm.
*/
public void quit() {
//quit(!IN_BLUEJ);
quit(true);
}
/**
* Beendet das Programm. Falls <var>exit</var> gleich {@code true} ist,
* wird die komplette Virtuelle Maschine beendet.
*
* @param exit Ob die VM beendet werden soll.
* @see System#exit(int)
*/
public void quit( boolean exit ) {
frame.setVisible(false);
canvas.dispose();
@@ -227,6 +399,12 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
}
/**
* Setzte die Größe der {@link Zeichenleinwand}.
*
* @param width
* @param height
*/
public final void setSize( int width, int height ) {
//frame.setSize(width, height);
@@ -235,30 +413,103 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
this.width = width;
this.height = height;
frame.pack();
}
/**
* Die Breite der {@link Zeichenleinwand}.
*
* @return Die Breite der {@link Zeichenleinwand}.
*/
public final int getWidth() {
return width;
}
/**
* Die Höhe der {@link Zeichenleinwand}.
*
* @return Die Höhe der {@link Zeichenleinwand}.
*/
public final int getHeight() {
return height;
}
public final void setTitle( String pTitel ) {
frame.setTitle(pTitel);
/**
* Setzt den Titel des Zeichenfensters.
*
* @param title Der Titel, der oben im Zeichenfenster angezeigt wird.
*/
public final void setTitle( String title ) {
frame.setTitle(title);
}
/**
* Gibt die Zeichenleinwand zurück.
*
* @return Die Zeichenleinwand.
*/
public final Zeichenleinwand getCanvas() {
return canvas;
}
/**
* Fügt der {@link Zeichenleinwand} eine weitere {@link Layer Ebene}
* hinzu.
*
* @param layer Die neue Ebene.
*/
public final void addLayer( Layer layer ) {
canvas.addLayer(layer);
layer.setSize(getWidth(), getHeight());
}
/**
* Gibt die Anzahl der {@link Layer Ebenen} in der {@link Zeichenleinwand}
* zurück.
*
* @return Die Anzahl der Ebenen.
*/
public int getLayerCount() {
return canvas.getLayerCount();
}
/**
* Gibt die {@link Layer Ebene} am angegebenen Index zurück. Gibt es keine
* Ebene mit diesem Index.
*
* @param index
* @return
*/
public final Layer getLayer( int index ) {
return canvas.getLayer(index);
}
/**
* Gibt die erste (unterste) {@link Layer Ebene} der angegebenen Klasse
* zurück.
*
* <pre>
* DrawingLayer draw = getLayer(DrawingLayer.class);
* </pre>
*
* @param layerClass
* @param <LT>
* @return
*/
public final <LT extends Layer> LT getLayer( Class<LT> layerClass ) {
return canvas.getLayer(layerClass);
}
/**
* Gibt die {@link ColorLayer Ebene} mit der Hintergrundfarbe zurück. Gibt es
* keine solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand}
* hinzugefügt.
* <p>
* In der Regel sollte dies dieselbe Ebene sein wie {@link #background}.
*
* @return Die Hintergrundebene.
*/
public final ColorLayer getBackgroundLayer() {
ColorLayer layer = canvas.getLayer(ColorLayer.class);
if( layer == null ) {
@@ -268,6 +519,15 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
return layer;
}
/**
* Gibt die Standard-{@link DrawingLayer Zeichenebene} zurück. Gibt es
* keine solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand}
* hinzugefügt.
* <p>
* In der Regel sollte dies dieselbe Ebene sein wie {@link #drawing}.
*
* @return Die Zeichenebene.
*/
public final DrawingLayer getDrawingLayer() {
DrawingLayer layer = canvas.getLayer(DrawingLayer.class);
if( layer == null ) {
@@ -277,6 +537,15 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
return layer;
}
/**
* Gibt die Standard-{@link ShapesLayer Formenebene} zurück. Gibt es
* keine solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand}
* hinzugefügt.
* <p>
* In der Regel sollte dies dieselbe Ebene sein wie {@link #shapes}.
*
* @return Die Formenebene.
*/
public final ShapesLayer getShapesLayer() {
ShapesLayer layer = canvas.getLayer(ShapesLayer.class);
if( layer == null ) {
@@ -286,14 +555,29 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
return layer;
}
/**
* Gibt die aktuellen Frames pro Sekunde zurück.
*
* @return Angepeilte Frames pro Sekunde
*/
public final int getFramesPerSecond() {
return framesPerSecond;
}
/**
* Setzt die Anzahl an Frames pro Sekunde auf einen neuen Wert.
*
* @param pFramesPerSecond Neue FPS.
*/
public final void setFramesPerSecond( int pFramesPerSecond ) {
framesPerSecond = pFramesPerSecond;
}
/**
* Speichert den aktuellen Inhalt der {@link Zeichenleinwand} in einer
* Bilddatei auf der Festplatte. Zur Auswahl der Zieldatei wird dem Nutzer
* ein {@link JFileChooser} angezeigt.
*/
public void saveImage() {
JFileChooser jfc = new JFileChooser();
jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
@@ -309,8 +593,12 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
}
/**
* Speichert den aktuellen Inhalt der {@link Zeichenleinwand} in einer
* Bilddatei im angegebenen Dateipfad auf der Festplatte.
*/
public void saveImage( String filepath ) {
BufferedImage img = new BufferedImage(canvas.getWidth(),canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
BufferedImage img = new BufferedImage(canvas.getWidth(), canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setColor(STD_BACKGROUND.getColor());
@@ -320,13 +608,21 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
try {
ImageLoader.saveImage(img, new File(filepath), true);
} catch ( IOException ex ) {
} catch( IOException ex ) {
ex.printStackTrace();
}
}
/**
* Erstellt eine Momentanaufnahme des aktuellen Inhalts der
* {@link Zeichenleinwand} und erstellt daraus eine {@link ImageLayer Bildebene}.
* Die Ebene wird automatisch der {@link Zeichenleinwand} vor dem
* {@link #background} hinzugefügt.
*
* @return Die neue Bildebene.
*/
public ImageLayer snapshot() {
BufferedImage img = new BufferedImage(canvas.getWidth(),canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
BufferedImage img = new BufferedImage(canvas.getWidth(), canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setColor(STD_BACKGROUND.getColor());
@@ -352,38 +648,22 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
return imgLayer;
}
/*
* Methoden, die von Unterklassen überschrieben werden können / sollen.
/**
* Pausiert die Schleife der Zeichenmaschine für die angegebene Anzahl an
* Millisekunden.
*
* @param ms Schlafenszeit in Millisekunden.
*/
public void settings() {
}
public void setup() {
}
public void draw() {
}
public void teardown() {
}
public void update( double delta ) {
running = false;
}
public void delay( int ms ) {
public final void delay( int ms ) {
if( ms <= 0 ) {
return;
}
long timer = 0L;
if( isDrawing ) {
// Immediately show the current drawing before waiting
// Measure the render time and subtract from the waiting ms
if( state == Options.AppState.DRAWING ) {
// Falls gerade draw() ausgeführt wird, zeigen wir den aktuellen
// Stand der Zeichnung auf der Leinwand an. Die Zeit für das
// Rendern wird gemessen und von der Wartezeit abgezogen.
timer = System.nanoTime();
canvas.render();
timer = System.nanoTime() - timer;
@@ -402,36 +682,112 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
}
/*
* Methoden, die von Unterklassen überschrieben werden können / sollen.
*/
/**
* Die Settings werden einmal beim Erstellten der Zeichenmaschine aufgerufen.
* <p>
* {@code settings()} wird nur selten benötigt, wenn das Zeichenfenster
*/
public void settings() {
// Intentionally left blank
}
/**
* Methode, die von Unterklassen überschrieben werden sollte, um die
* Zeichenmaschine vor dem Start zu konfigurieren. Hier können vorbereitende
* Befehle ausgeführt werden, die die {@link Zeichenleinwand} zu
* initialisieren, neue Objekte instanziieren und Variablen initialisieren.
*/
public void setup() {
// Intentionally left blank
}
/**
* {@code update()} wird einmal pro Frame vor {@link #draw()} aufgerufen, um
* notwendige Aktualisierungen vorzunehmen. Im Gegensatz zu {@link #draw()}
* bekommt {@code update()} zusätzlich {@link #delta} übergeben, um die
* Aktualisierungen abhängig von der echten Verzögerung zwischen zwei Frames
* zu berechnen.
* <p>
* {@code delta} wird in Sekunden angegeben. Um eine Form zum Beispiel
* um {@code 50} Pixel pro Sekunde in {@code x}-Richtung zu bewegen,
* kann so vorgegangen werden:
* <pre>
* shape.move(50*delta, 0.0);
* </pre>
*
* @param delta
*/
public void update( double delta ) {
running = false;
}
/**
* {@code draw()} wird einmal pro Frame aufgerufen. Bei einer
* {@link #getFramesPerSecond() Framerate} von {@code 60} also in etwa 60-Mal
* pro Sekunde. In der {@code draw}-Methode wird der Inhalt der Ebenen
* manipuliert und deren Inhalte gezeichnet. Am Ende des Frames werden alle
* Ebenen auf die {@link Zeichenleinwand} übertragen.
* <p>
* {@code draw()} stellt die wichtigste Methode für eine Zeichenmaschine dar,
* da hier die Zeichnung des Programms erstellt wird.
*/
public void draw() {
// Intentionally left blank
}
/**
* {@code teardown()} wird aufgerufen, sobald die Schleife des Hauptprogramms
* beendet wurde. Dies passiert entweder nach dem ersten Durchlauf (wenn keine
* eigene {@link #update(double)} erstellt wurde), nach dem Aufruf von
* {@link #stop()} oder nachdem das {@link Zeichenfenster} geschlossen wurde.
* <p>
* In {@code teardown()} kann zum Beispiel der Abschlussbildschirm eines
* Spiels oder der Abspann einer Animation angezeigt werden, oder mit
* {@link #saveImage()} die erstellte Zeichnung abgespeichert werden.
*/
public void teardown() {
// Intentionally left blank
}
/*
* Mouse handling
*/
@Override
public final void mouseClicked( MouseEvent e ) {
saveMousePosition(e);
mouseEvent = e;
mouseClicked();
}
public void mouseClicked() {
// Intentionally left blank
}
@Override
public final void mousePressed( MouseEvent e ) {
saveMousePosition(e);
mouseEvent = e;
mousePressed = true;
mouseButton = e.getButton();
mousePressed();
}
public void mousePressed() {
// Intentionally left blank
}
@Override
public final void mouseReleased( MouseEvent e ) {
saveMousePosition(e);
mouseEvent = e;
mousePressed = false;
mouseButton = NOBUTTON;
mouseReleased();
}
public void mouseReleased() {
// Intentionally left blank
}
@Override
@@ -446,30 +802,32 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
@Override
public final void mouseDragged( MouseEvent e ) {
saveMousePosition(e);
mouseEvent = e;
mouseDragged();
}
public void mouseDragged() {
// Intentionally left blank
}
@Override
public final void mouseMoved( MouseEvent e ) {
saveMousePosition(e);
mouseEvent = e;
mouseMoved();
}
public void mouseMoved() {
// Intentionally left blank
}
private void saveMousePosition( MouseEvent e ) {
pmouseX = mouseX;
pmouseY = mouseY;
private void saveMousePosition( MouseEvent event ) {
if( mouseEvent != null && event.getComponent() == canvas ) {
pmouseX = mouseX;
pmouseY = mouseY;
mouseX = e.getX();
mouseY = e.getY();
mouseX = event.getX();
mouseY = event.getY();
}
}
private void saveMousePosition() {
@@ -493,7 +851,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
public void keyTyped() {
// Intentionally left blank
}
@Override
@@ -504,7 +862,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
public void keyPressed() {
// Intentionally left blank
}
@Override
@@ -515,12 +873,13 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
public void keyReleased() {
// Intentionally left blank
}
private final void saveKeys( KeyEvent e ) {
key = e.getKeyChar();
keyCode = e.getKeyCode();
private final void saveKeys( KeyEvent event ) {
keyEvent = event;
key = event.getKeyChar();
keyCode = event.getKeyCode();
}
class Zeichenthread extends Thread {
@@ -528,7 +887,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
@Override
public final void run() {
// Wait for full initialization before start
while( !initialized ) {
while( state != Options.AppState.INITIALIZED ) {
delay(1);
}
@@ -548,12 +907,13 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
// call setup of subclass
setup();
state = Options.AppState.RUNNING;
while( running ) {
// delta in seconds
delta = (System.nanoTime() - beforeTime) / 1000000000.0;
beforeTime = System.nanoTime();
//saveMousePosition();
saveMousePosition(mouseEvent);
handleUpdate(delta);
handleDraw();
@@ -588,29 +948,30 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
tick = _tick;
runtime = _runtime;
}
state = Options.AppState.STOPPED;
teardown();
state = Options.AppState.TERMINATED;
if( quitAfterTeardown ) {
quit();
}
}
public void handleUpdate( double delta ) {
if( isUpdating ) {
return;
if( state == Options.AppState.RUNNING ) {
state = Options.AppState.UPDATING;
update(delta);
state = Options.AppState.RUNNING;
}
isUpdating = true;
update(delta);
isUpdating = false;
}
public void handleDraw() {
if( isDrawing ) {
return;
if( state == Options.AppState.RUNNING ) {
state = Options.AppState.DRAWING;
draw();
state = Options.AppState.RUNNING;
}
isDrawing = true;
draw();
isDrawing = false;
}
}