Verbesserter Vollbildmodus und Trennung GUI / Controller

This commit is contained in:
ngb
2022-07-27 13:54:55 +02:00
parent e2e6f8c291
commit 687d7d35b7
3 changed files with 140 additions and 91 deletions

View File

@@ -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);
@@ -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);
this.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();
this.requestFocus();
fullscreen = true;
} else if( !pEnable && fullscreen ) {
fullscreen = false;
canvas.removeKeyListener(fullscreenExitListener);
displayDevice.setFullScreenWindow(null);
dispose();
setUndecorated(false);
setResizable(false);
this.removeKeyListener(fullscreenExitListener);
canvas.setSize(canvasPreferredWidth, canvasPreferredHeight);
this.pack();
// frame.setUndecorated(false);
setVisible(true);
pack();
//canvas.requestFocus();
this.requestFocus();
fullscreen = false;
}
// Resume rendering
canvas.resumeRendering();
}
}

View File

@@ -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.*;
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;
/**
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
@@ -24,10 +24,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 LinkedList<Layer> layers;
private boolean rendering = false, suspended = false;
/**
* Erstellt eine neue Zeichenleinwand mit einer festen Größe.
@@ -48,6 +52,20 @@ public class Zeichenleinwand extends Canvas {
}
}
public boolean isRendering() {
return rendering;
}
public void suspendRendering() throws InterruptedException {
synchronized( renderLock ) {
suspended = true;
}
}
public void resumeRendering() {
suspended = false;
}
/**
* Ändert die Größe der Zeichenleinwand auf die angegebene Größe in Pixeln.
* <p>
@@ -88,11 +106,11 @@ public class Zeichenleinwand extends Canvas {
/**
* 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 ) {
@@ -249,11 +267,13 @@ public class Zeichenleinwand extends Canvas {
* Zeigt den aktuellen Inhalt der Zeichenleinwand an.
*/
public void render() {
if( !suspended && isDisplayable() ) {
if( getBufferStrategy() == null ) {
allocateBuffer();
}
if( isDisplayable() ) {
synchronized( renderLock ) {
rendering = true;
BufferStrategy strategy = this.getBufferStrategy();
if( strategy != null ) {
do {
@@ -281,6 +301,8 @@ public class Zeichenleinwand extends Canvas {
// Repeat the rendering if the drawing buffer was lost
} while( strategy.contentsLost() );
}
rendering = false;
}
}
}

View File

@@ -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,7 +75,7 @@ public class Zeichenmaschine extends Constants {
/**
* Das Zeichenfenster der Zeichenmaschine
*/
private Zeichenfenster frame;
protected Zeichenfenster frame;
// Aktueller Zustand der Zeichenmaschine.
@@ -312,8 +276,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 ) {
@@ -392,10 +363,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();
}
@@ -546,6 +525,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,6 +568,7 @@ public class Zeichenmaschine extends Constants {
* @see System#exit(int)
*/
public final void quit( boolean exit ) {
state = Options.AppState.QUITING;
frame.setVisible(false);
canvas.dispose();
frame.dispose();
@@ -1390,7 +1375,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();
@@ -1547,6 +1532,7 @@ public class Zeichenmaschine extends Constants {
}
// TODO: (ngb) exception handling when update/draw throws ex
class UpdateThreadExecutor extends ThreadPoolExecutor {
private Thread updateThread;
@@ -1555,7 +1541,18 @@ public class Zeichenmaschine extends Constants {
public UpdateThreadExecutor() {
super(1, 1, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
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;
}