Verantwortlichkeiten für Layout und Aufgaben klarer getrennt

This commit is contained in:
ngb
2022-07-26 08:59:30 +02:00
parent 68c88ec9ca
commit a228b21c84
3 changed files with 99 additions and 88 deletions

View File

@@ -14,6 +14,14 @@ import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
/**
* Ein Zeichenfenster ist das Programmfenster für die Zeichenmaschine.
* <p>
* Das Zeichenfenster implementiert hilfreiche Funktionen, um ein
* Programmfenster mit einer Zeichenleinwand als zentrales Element zu erstellen.
* Ein Zeichenfenster kann auch ohne eine Zeichenmaschine verwendet werden, um
* eigene Programmabläufe zu implementieren.
*/
public class Zeichenfenster extends JFrame { public class Zeichenfenster extends JFrame {
public static final void setLookAndFeel() { public static final void setLookAndFeel() {
@@ -46,24 +54,22 @@ public class Zeichenfenster extends JFrame {
} }
/** /**
* Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich * Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich dem
* dem Aktuellen sein, wenn das Fenster verschoben wurde). * Aktuellen sein, wenn das Fenster verschoben wurde).
*/ */
private GraphicsDevice displayDevice; private GraphicsDevice displayDevice;
private int canvasWidth, canvasHeight;
/** /**
* Höhe und Breite der Zeichenmaschine, bevor sie mit * Bevorzugte Abmessungen der Zeichenleinwand. Für das Zeichenfenster hat
* {@link #setFullscreen(boolean)} in den Vollbild-Modus versetzt wurde. * es Priorität die Leinwand auf dieser Größe zu halten. Gegebenenfalls unter
* Wird verwendet, um die Fenstergröße wiederherzustellen, sobald der * Missachtung anderer Größenvorgaben. Allerdings kann das Fenster keine
* Vollbild-Modus verlassen wird. * Garantie für die Größe der Leinwand übernehmen.
*/ */
private int initialWidth, initialHeight; private int canvasPreferredWidth, canvasPreferredHeight;
/** /**
* Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)} * Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)} in
* in den Vollbildmodus versetzt wurde. * den Vollbildmodus versetzt wurde.
*/ */
private boolean fullscreen = false; private boolean fullscreen = false;
@@ -85,15 +91,32 @@ public class Zeichenfenster extends JFrame {
private Zeichenleinwand canvas; private Zeichenleinwand canvas;
public Zeichenfenster( int width, int height, String title ) { public Zeichenfenster( int width, int height, String title ) {
this(width, height, title, getGraphicsDevice()); this(new Zeichenleinwand(width, height), title, getGraphicsDevice());
} }
public Zeichenfenster( int width, int height, String title, GraphicsDevice displayDevice ) { public Zeichenfenster( int width, int height, String title, GraphicsDevice displayDevice ) {
this(new Zeichenleinwand(width, height), title, displayDevice);
}
public Zeichenfenster( Zeichenleinwand canvas, String title ) {
this(canvas, title, getGraphicsDevice());
}
public Zeichenfenster( Zeichenleinwand canvas, String title, GraphicsDevice displayDevice ) {
super(Validator.requireNotNull(displayDevice).getDefaultConfiguration()); super(Validator.requireNotNull(displayDevice).getDefaultConfiguration());
this.displayDevice = displayDevice; this.displayDevice = displayDevice;
Validator.requireNotNull(canvas, "Every Zeichenfenster needs a Zeichenleinwand, but got <null>.");
this.canvasPreferredWidth = canvas.getWidth();
this.canvasPreferredHeight = canvas.getHeight();
//this.add(canvas, BorderLayout.CENTER);
this.add(canvas);
this.canvas = canvas;
// Konfiguration des Frames // Konfiguration des Frames
this.setTitle(title); this.setTitle(title == null ? "Zeichenfenster " + Constants.APP_VERSION: title);
// Kann vom Aufrufenden überschrieben werden
this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
// Das Icon des Fensters ändern // Das Icon des Fensters ändern
@@ -118,68 +141,51 @@ public class Zeichenfenster extends JFrame {
// Dock Icon in macOS konnte nicht gesetzt werden :( // Dock Icon in macOS konnte nicht gesetzt werden :(
LOG.warn("Could not set dock icon: %s", macex.getMessage()); LOG.warn("Could not set dock icon: %s", macex.getMessage());
} }
// Fenster zusammenbauen, auf dem Bildschirm positionieren ...
this.canvasWidth = width;
this.canvasHeight = height;
// Fenster zusammenbauen, auf dem Bildschirm zentrieren ...
this.pack(); this.pack();
this.setResizable(false); this.setResizable(false);
this.centerFrame(); this.setLocationByPlatform(true);
// this.centerFrame();
} }
public void addCanvas( Zeichenleinwand canvas ) { public GraphicsDevice getDisplayDevice() {
this.canvas = canvas; return displayDevice;
this.add(canvas, 0);
this.canvasWidth = canvas.getWidth();
this.canvasHeight = canvas.getHeight();
this.pack();
} }
public java.awt.Rectangle getScreenBounds() { public Rectangle getScreenBounds() {
return displayDevice.getDefaultConfiguration().getBounds(); return displayDevice.getDefaultConfiguration().getBounds();
} }
public java.awt.Rectangle getCanvasBounds() { public Zeichenleinwand getCanvas() {
java.awt.Insets insets = getInsets(); return canvas;
return new java.awt.Rectangle(insets.top, insets.left, canvasWidth, canvasHeight);
} }
public Rectangle getCanvasBounds() {
return canvas.getBounds();
}
/** /**
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm. * Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
*/ */
public final void centerFrame() { public final void centerFrame() {
java.awt.Rectangle bounds = getScreenBounds(); java.awt.Rectangle screenBounds = getScreenBounds();
java.awt.Insets insets = getInsets(); java.awt.Rectangle frameBounds = getBounds();
this.setLocation( this.setLocation(
(int) (bounds.x + (bounds.width - canvasWidth) / 2.0 - insets.left), (int) (screenBounds.x + (screenBounds.width - frameBounds.width) / 2.0),
(int) (bounds.y + (bounds.height - canvasHeight) / 2.0- insets.top) (int) (screenBounds.y + (screenBounds.height - frameBounds.height) / 2.0)
); );
} }
@Override public void setCanvasSize( int newWidth, int newHeight ) {
public void setSize( int newWidth, int newHeight ) { // TODO: (ngb) Put constains on max/min frame/canvas size
java.awt.Rectangle bounds = getScreenBounds();
java.awt.Insets insets = this.getInsets();
if( fullscreen ) { if( fullscreen ) {
initialWidth = Math.min(newWidth, bounds.width - insets.left - insets.right); canvasPreferredWidth = newWidth;
initialHeight = Math.min(newHeight, bounds.height - insets.top - insets.bottom); canvasPreferredHeight = newHeight;
setFullscreen(false); setFullscreen(false);
} else { } else {
canvasWidth = Math.min(newWidth, bounds.width - insets.left - insets.right); canvas.setSize(newWidth, newHeight);
canvasHeight = Math.min(newHeight, bounds.height - insets.top - insets.bottom); canvasPreferredWidth = canvas.getWidth();
canvasPreferredHeight = canvas.getHeight();
if( canvas != null ) { this.pack();
canvas.setSize(canvasWidth, canvasHeight);
}
/*super.setSize(
width + insets.left + insets.right,
height + insets.top + insets.bottom
);*/
super.pack();
} }
} }
@@ -200,16 +206,13 @@ public class Zeichenfenster extends JFrame {
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html // See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
if( displayDevice.isFullScreenSupported() ) { if( displayDevice.isFullScreenSupported() ) {
if( pEnable && !fullscreen ) { if( pEnable && !fullscreen ) {
// Store width / height
initialWidth = getWidth();
initialHeight = getHeight();
// frame.setUndecorated(true); // frame.setUndecorated(true);
this.setResizable(false); // Should be set anyway this.setResizable(false); // Should be set anyway
displayDevice.setFullScreenWindow(this); displayDevice.setFullScreenWindow(this);
java.awt.Rectangle bounds = getScreenBounds(); java.awt.Rectangle bounds = getScreenBounds();
this.setSize(bounds.width, bounds.height); // 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 // Register ESC to exit fullscreen
canvas.addKeyListener(fullscreenExitListener); canvas.addKeyListener(fullscreenExitListener);
@@ -219,7 +222,7 @@ public class Zeichenfenster extends JFrame {
canvas.removeKeyListener(fullscreenExitListener); canvas.removeKeyListener(fullscreenExitListener);
displayDevice.setFullScreenWindow(null); displayDevice.setFullScreenWindow(null);
this.setSize(initialWidth, initialHeight); canvas.setSize(canvasPreferredWidth, canvasPreferredHeight);
this.pack(); this.pack();
// frame.setUndecorated(false); // frame.setUndecorated(false);
} }

View File

@@ -1,16 +1,16 @@
package schule.ngb.zm; package schule.ngb.zm;
import schule.ngb.zm.layers.ColorLayer; import schule.ngb.zm.layers.ColorLayer;
import schule.ngb.zm.layers.ShapesLayer;
import schule.ngb.zm.util.Log; import schule.ngb.zm.util.Log;
import java.awt.Canvas; import java.awt.*;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy; import java.awt.image.BufferStrategy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/** /**
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus * Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
@@ -37,8 +37,8 @@ public class Zeichenleinwand extends Canvas {
*/ */
public Zeichenleinwand( int width, int height ) { public Zeichenleinwand( int width, int height ) {
super.setSize(width, height); super.setSize(width, height);
this.setPreferredSize(this.getSize()); this.setPreferredSize(getSize());
this.setMinimumSize(this.getSize()); this.setMinimumSize(getSize());
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor()); this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
// Liste der Ebenen initialisieren und die Standardebenen einfügen // Liste der Ebenen initialisieren und die Standardebenen einfügen
@@ -60,8 +60,8 @@ public class Zeichenleinwand extends Canvas {
@Override @Override
public void setSize( int width, int height ) { public void setSize( int width, int height ) {
super.setSize(width, height); super.setSize(width, height);
this.setPreferredSize(this.getSize()); this.setPreferredSize(getSize());
this.setMinimumSize(this.getSize()); this.setMinimumSize(getSize());
synchronized( layers ) { synchronized( layers ) {
for( Layer layer : layers ) { for( Layer layer : layers ) {

View File

@@ -275,21 +275,21 @@ public class Zeichenmaschine extends Constants {
public Zeichenmaschine( int width, int height, String title, boolean run_once ) { public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
LOG.info("Starting " + APP_NAME + " " + APP_VERSION); LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
// Erstellen des Zeichenfensters
frame = createFrame(width, height, title);
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
this.canvasWidth = width;
this.canvasHeight = height;
java.awt.Rectangle displayBounds = frame.getScreenBounds();
this.screenWidth = (int) displayBounds.getWidth();
this.screenHeight = (int) displayBounds.getHeight();
// Erstellen der Leinwand // Erstellen der Leinwand
canvas = new Zeichenleinwand(width, height); canvas = new Zeichenleinwand(width, height);
frame.addCanvas(canvas);
// Die drei Standardebenen merken, für den einfachen Zugriff aus unterklassen. // Erstellen des Zeichenfensters
frame = createFrame(canvas, title);
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
this.canvasWidth = canvasBounds.width;
this.canvasHeight = canvasBounds.height;
java.awt.Rectangle displayBounds = frame.getScreenBounds();
this.screenWidth = displayBounds.width;
this.screenHeight = displayBounds.height;
// Die drei Standardebenen merken, für den einfachen Zugriff aus Unterklassen.
background = getBackgroundLayer(); background = getBackgroundLayer();
drawing = getDrawingLayer(); drawing = getDrawingLayer();
shapes = getShapesLayer(); shapes = getShapesLayer();
@@ -333,7 +333,7 @@ public class Zeichenmaschine extends Constants {
} }
}); });
// Fesnter anzeigen // Fenster anzeigen
frame.setVisible(true); frame.setVisible(true);
// Nach dem Anzeigen kann die Pufferstrategie erstellt werden. // Nach dem Anzeigen kann die Pufferstrategie erstellt werden.
@@ -357,14 +357,22 @@ public class Zeichenmaschine extends Constants {
* *
* @param title * @param title
*/ */
// TODO: Implement in conjunction with Zeichenfenster private final Zeichenfenster createFrame( Zeichenleinwand c, String title ) {
private final Zeichenfenster createFrame( int width, int height, String title ) { while( frame == null ) {
// Setzen des Look&Feel try {
// TODO: (ngb) Ist das überhaupt notwendig oder eh schon der Default? TaskRunner.invokeLater(new Runnable() {
Zeichenfenster.setLookAndFeel(); @Override
public void run() {
Zeichenfenster frame = new Zeichenfenster(width, height, title); Zeichenfenster.setLookAndFeel();
frame = new Zeichenfenster(canvas, title);
}
}).get();
} catch( InterruptedException e ) {
} catch( ExecutionException e ) {
LOG.error(e, "Error initializing application frame: %s", e.getMessage());
throw new RuntimeException(e);
}
}
return frame; return frame;
} }