From a228b21c8403ab5724b54e6fa2b544a6ed736034 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Tue, 26 Jul 2022 08:59:30 +0200 Subject: [PATCH] =?UTF-8?q?Verantwortlichkeiten=20f=C3=BCr=20Layout=20und?= =?UTF-8?q?=20Aufgaben=20klarer=20getrennt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/schule/ngb/zm/Zeichenfenster.java | 121 +++++++++--------- .../java/schule/ngb/zm/Zeichenleinwand.java | 16 +-- .../java/schule/ngb/zm/Zeichenmaschine.java | 50 +++++--- 3 files changed, 99 insertions(+), 88 deletions(-) diff --git a/src/main/java/schule/ngb/zm/Zeichenfenster.java b/src/main/java/schule/ngb/zm/Zeichenfenster.java index 378e598..61c897c 100644 --- a/src/main/java/schule/ngb/zm/Zeichenfenster.java +++ b/src/main/java/schule/ngb/zm/Zeichenfenster.java @@ -14,6 +14,14 @@ import java.io.IOException; import java.net.URL; import java.util.ArrayList; +/** + * Ein Zeichenfenster ist das Programmfenster für die Zeichenmaschine. + *

+ * 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 static final void setLookAndFeel() { @@ -46,24 +54,22 @@ public class Zeichenfenster extends JFrame { } /** - * Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich - * dem Aktuellen sein, wenn das Fenster verschoben wurde). + * Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich dem + * Aktuellen sein, wenn das Fenster verschoben wurde). */ private GraphicsDevice displayDevice; - private int canvasWidth, canvasHeight; - /** - * Höhe und Breite der Zeichenmaschine, bevor sie mit - * {@link #setFullscreen(boolean)} in den Vollbild-Modus versetzt wurde. - * Wird verwendet, um die Fenstergröße wiederherzustellen, sobald der - * Vollbild-Modus verlassen wird. + * 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. */ - private int initialWidth, initialHeight; + private int canvasPreferredWidth, canvasPreferredHeight; /** - * Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)} - * in den Vollbildmodus versetzt wurde. + * Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)} in + * den Vollbildmodus versetzt wurde. */ private boolean fullscreen = false; @@ -85,15 +91,32 @@ public class Zeichenfenster extends JFrame { private Zeichenleinwand canvas; 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 ) { + 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()); this.displayDevice = displayDevice; + Validator.requireNotNull(canvas, "Every Zeichenfenster needs a Zeichenleinwand, but got ."); + + this.canvasPreferredWidth = canvas.getWidth(); + this.canvasPreferredHeight = canvas.getHeight(); + //this.add(canvas, BorderLayout.CENTER); + this.add(canvas); + this.canvas = canvas; + // 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); // Das Icon des Fensters ändern @@ -118,68 +141,51 @@ public class Zeichenfenster extends JFrame { // Dock Icon in macOS konnte nicht gesetzt werden :( LOG.warn("Could not set dock icon: %s", macex.getMessage()); } - - this.canvasWidth = width; - this.canvasHeight = height; - - // Fenster zusammenbauen, auf dem Bildschirm zentrieren ... + // Fenster zusammenbauen, auf dem Bildschirm positionieren ... this.pack(); this.setResizable(false); - this.centerFrame(); + this.setLocationByPlatform(true); + // this.centerFrame(); } - public void addCanvas( Zeichenleinwand canvas ) { - this.canvas = canvas; - this.add(canvas, 0); - - this.canvasWidth = canvas.getWidth(); - this.canvasHeight = canvas.getHeight(); - this.pack(); + public GraphicsDevice getDisplayDevice() { + return displayDevice; } - public java.awt.Rectangle getScreenBounds() { + public Rectangle getScreenBounds() { return displayDevice.getDefaultConfiguration().getBounds(); } - public java.awt.Rectangle getCanvasBounds() { - java.awt.Insets insets = getInsets(); - return new java.awt.Rectangle(insets.top, insets.left, canvasWidth, canvasHeight); + public Zeichenleinwand getCanvas() { + return canvas; } + public Rectangle getCanvasBounds() { + return canvas.getBounds(); + } /** * Zentriert das Zeichenfenster auf dem aktuellen Bildschirm. */ public final void centerFrame() { - java.awt.Rectangle bounds = getScreenBounds(); - java.awt.Insets insets = getInsets(); + java.awt.Rectangle screenBounds = getScreenBounds(); + java.awt.Rectangle frameBounds = getBounds(); this.setLocation( - (int) (bounds.x + (bounds.width - canvasWidth) / 2.0 - insets.left), - (int) (bounds.y + (bounds.height - canvasHeight) / 2.0- insets.top) + (int) (screenBounds.x + (screenBounds.width - frameBounds.width) / 2.0), + (int) (screenBounds.y + (screenBounds.height - frameBounds.height) / 2.0) ); } - @Override - public void setSize( int newWidth, int newHeight ) { - java.awt.Rectangle bounds = getScreenBounds(); - java.awt.Insets insets = this.getInsets(); - + public void setCanvasSize( int newWidth, int newHeight ) { + // TODO: (ngb) Put constains on max/min frame/canvas size if( fullscreen ) { - initialWidth = Math.min(newWidth, bounds.width - insets.left - insets.right); - initialHeight = Math.min(newHeight, bounds.height - insets.top - insets.bottom); + canvasPreferredWidth = newWidth; + canvasPreferredHeight = newHeight; setFullscreen(false); } else { - canvasWidth = Math.min(newWidth, bounds.width - insets.left - insets.right); - canvasHeight = Math.min(newHeight, bounds.height - insets.top - insets.bottom); - - if( canvas != null ) { - canvas.setSize(canvasWidth, canvasHeight); - } - - /*super.setSize( - width + insets.left + insets.right, - height + insets.top + insets.bottom - );*/ - super.pack(); + canvas.setSize(newWidth, newHeight); + canvasPreferredWidth = canvas.getWidth(); + canvasPreferredHeight = canvas.getHeight(); + this.pack(); } } @@ -200,16 +206,13 @@ public class Zeichenfenster extends JFrame { // See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html if( displayDevice.isFullScreenSupported() ) { if( pEnable && !fullscreen ) { - // Store width / height - initialWidth = getWidth(); - initialHeight = getHeight(); - // frame.setUndecorated(true); this.setResizable(false); // Should be set anyway displayDevice.setFullScreenWindow(this); 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 canvas.addKeyListener(fullscreenExitListener); @@ -219,7 +222,7 @@ public class Zeichenfenster extends JFrame { canvas.removeKeyListener(fullscreenExitListener); displayDevice.setFullScreenWindow(null); - this.setSize(initialWidth, initialHeight); + canvas.setSize(canvasPreferredWidth, canvasPreferredHeight); this.pack(); // frame.setUndecorated(false); } diff --git a/src/main/java/schule/ngb/zm/Zeichenleinwand.java b/src/main/java/schule/ngb/zm/Zeichenleinwand.java index 7855536..63c4c96 100644 --- a/src/main/java/schule/ngb/zm/Zeichenleinwand.java +++ b/src/main/java/schule/ngb/zm/Zeichenleinwand.java @@ -1,16 +1,16 @@ 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.Canvas; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Toolkit; +import java.awt.*; 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; /** * 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 ) { super.setSize(width, height); - this.setPreferredSize(this.getSize()); - this.setMinimumSize(this.getSize()); + this.setPreferredSize(getSize()); + this.setMinimumSize(getSize()); this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor()); // Liste der Ebenen initialisieren und die Standardebenen einfügen @@ -60,8 +60,8 @@ public class Zeichenleinwand extends Canvas { @Override public void setSize( int width, int height ) { super.setSize(width, height); - this.setPreferredSize(this.getSize()); - this.setMinimumSize(this.getSize()); + this.setPreferredSize(getSize()); + this.setMinimumSize(getSize()); synchronized( layers ) { for( Layer layer : layers ) { diff --git a/src/main/java/schule/ngb/zm/Zeichenmaschine.java b/src/main/java/schule/ngb/zm/Zeichenmaschine.java index b16ca5d..7f99063 100644 --- a/src/main/java/schule/ngb/zm/Zeichenmaschine.java +++ b/src/main/java/schule/ngb/zm/Zeichenmaschine.java @@ -275,21 +275,21 @@ public class Zeichenmaschine extends Constants { public Zeichenmaschine( int width, int height, String title, boolean run_once ) { 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 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(); drawing = getDrawingLayer(); shapes = getShapesLayer(); @@ -333,7 +333,7 @@ public class Zeichenmaschine extends Constants { } }); - // Fesnter anzeigen + // Fenster anzeigen frame.setVisible(true); // Nach dem Anzeigen kann die Pufferstrategie erstellt werden. @@ -357,14 +357,22 @@ public class Zeichenmaschine extends Constants { * * @param title */ - // TODO: Implement in conjunction with Zeichenfenster - private final Zeichenfenster createFrame( int width, int height, String title ) { - // Setzen des Look&Feel - // TODO: (ngb) Ist das überhaupt notwendig oder eh schon der Default? - Zeichenfenster.setLookAndFeel(); - - Zeichenfenster frame = new Zeichenfenster(width, height, title); - + private final 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); + } + }).get(); + } catch( InterruptedException e ) { + } catch( ExecutionException e ) { + LOG.error(e, "Error initializing application frame: %s", e.getMessage()); + throw new RuntimeException(e); + } + } return frame; }