Closes #8 and closes #9

This commit is contained in:
ngb 2022-01-06 16:56:11 +01:00
parent 9d78686470
commit bfc3c5b157
8 changed files with 206 additions and 40 deletions

View File

@ -11,6 +11,6 @@ Mit der Zechenmaschine können Schüler:innen ihre Programme von Anfang an mit
Visualisierungen und Interaktionen ausstatten, ohne sich mit der GUI Programmierung Visualisierungen und Interaktionen ausstatten, ohne sich mit der GUI Programmierung
mit Java Swing auseinandersetzen zu müssen. mit Java Swing auseinandersetzen zu müssen.
Die Bibliothek wurde sich stark on [Processing](https://processing.org), Die Bibliothek wurde stark on [Processing](https://processing.org),
[Engine Alpha](https://engine-alpha.org) und [TigerJython](https://www.tigerjython.ch/de) [Engine Alpha](https://engine-alpha.org) und [TigerJython](https://www.tigerjython.ch/de)
inspiriert. inspiriert.

View File

@ -9,6 +9,12 @@ public class ColorLayer extends Layer {
clear(); clear();
} }
public ColorLayer( int width, int height, Color color ) {
super(width, height);
this.background = color;
clear();
}
@Override @Override
public void setSize( int width, int height ) { public void setSize( int width, int height ) {
super.setSize(width, height); super.setSize(width, height);

View File

@ -8,8 +8,8 @@ public class Constants {
public static final String APP_NAME = "Zeichenmaschine"; public static final String APP_NAME = "Zeichenmaschine";
public static final int APP_VERSION_MAJ = 0; public static final int APP_VERSION_MAJ = 0;
public static final int APP_VERSION_MIN = 1; public static final int APP_VERSION_MIN = 0;
public static final int APP_VERSION_REV = 5; public static final int APP_VERSION_REV = 8;
public static final String APP_VERSION = APP_VERSION_MAJ + "." + APP_VERSION_MIN + "." + APP_VERSION_REV; public static final String APP_VERSION = APP_VERSION_MAJ + "." + APP_VERSION_MIN + "." + APP_VERSION_REV;

View File

@ -16,11 +16,6 @@ public class ImageLayer extends Layer {
protected boolean redraw = true; protected boolean redraw = true;
public ImageLayer() {
}
public ImageLayer( String source ) { public ImageLayer( String source ) {
image = ImageLoader.loadImage(source); image = ImageLoader.loadImage(source);
} }

View File

@ -30,9 +30,9 @@ public class Zeichenleinwand extends Canvas {
// Liste der Ebenen initialisieren und die Standardebenen einfügen // Liste der Ebenen initialisieren und die Standardebenen einfügen
layers = new LinkedList<>(); layers = new LinkedList<>();
synchronized( layers ) { synchronized( layers ) {
layers.add(new ColorLayer(Constants.STD_BACKGROUND)); layers.add(new ColorLayer(width, height, Constants.STD_BACKGROUND));
layers.add(new DrawingLayer()); layers.add(new DrawingLayer(width, height));
layers.add(new ShapesLayer()); layers.add(new ShapesLayer(width, height));
} }
} }
@ -160,6 +160,16 @@ public class Zeichenleinwand extends Canvas {
render(); render();
} }
public void draw( Graphics graphics ) {
Graphics2D g2d = (Graphics2D) graphics.create();
synchronized( layers ) {
for( Layer layer : layers ) {
layer.draw(g2d);
}
}
g2d.dispose();
}
public void render() { public void render() {
if( getBufferStrategy() == null ) { if( getBufferStrategy() == null ) {
allocateBuffer(); allocateBuffer();
@ -192,16 +202,6 @@ public class Zeichenleinwand extends Canvas {
// Repeat the rendering if the drawing buffer was lost // Repeat the rendering if the drawing buffer was lost
} while( strategy.contentsLost() ); } while( strategy.contentsLost() );
} }
/*
Graphics2D g2d = (Graphics2D) g.create();
for( Layer layer : layers ) {
layer.draw(g2d);
}
g2d.dispose();
*/
} }
} }
} }

View File

@ -1,15 +1,21 @@
package schule.ngb.zm; package schule.ngb.zm;
import schule.ngb.zm.shapes.ShapesLayer; import schule.ngb.zm.shapes.ShapesLayer;
import schule.ngb.zm.util.ImageLoader;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.MouseInputListener; import javax.swing.event.MouseInputListener;
import java.awt.*; import java.awt.*;
import java.awt.Color;
import java.awt.event.*; import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import java.io.File;
import java.io.IOException;
/** /**
* Hauptklasse der Zeichenmaschine. * Hauptklasse der Zeichenmaschine.
* * <p>
* Projekte der Zeichenmaschine sollten als Unterklasse implementiert werden. * Projekte der Zeichenmaschine sollten als Unterklasse implementiert werden.
* Die Klasse übernimmt die Initialisierung eines Programmfensters und der * Die Klasse übernimmt die Initialisierung eines Programmfensters und der
* nötigen Komponenten. * nötigen Komponenten.
@ -25,6 +31,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
/* /*
* Attributes to be accessed by subclasses. * Attributes to be accessed by subclasses.
*/ */
protected Zeichenleinwand canvas; protected Zeichenleinwand canvas;
protected ColorLayer background; protected ColorLayer background;
@ -41,18 +48,24 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
protected double mouseX = 0.0, mouseY = 0.0, pmouseX = 0.0, pmouseY = 0.0; protected double mouseX = 0.0, mouseY = 0.0, pmouseX = 0.0, pmouseY = 0.0;
protected int width = STD_WIDTH, height = STD_HEIGHT; protected int width, height;
protected int screenWidth, screenHeight;
/*
* Interne Attribute zur Steuerung der Zeichenmaschine.
*/
private Object mouseLock = new Object(); private Object mouseLock = new Object();
private Object keyboardLock = new Object(); private Object keyboardLock = new Object();
/*
* Interne Attribute zur Steuerung der Zeichenamschine.
*/
//
private JFrame frame; private JFrame frame;
private GraphicsEnvironment environment;
private GraphicsDevice displayDevice;
private boolean running = false, isDrawing = false, isUpdating = false; private boolean running = false, isDrawing = false, isUpdating = false;
private int framesPerSecond; private int framesPerSecond;
@ -61,6 +74,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
private boolean quitAfterTeardown = false, initialized = false; private boolean quitAfterTeardown = false, initialized = false;
public Zeichenmaschine() { public Zeichenmaschine() {
this(APP_NAME + " " + APP_VERSION); this(APP_NAME + " " + APP_VERSION);
} }
@ -73,20 +87,37 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
try { try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch( Exception e ) { } catch( Exception e ) {
System.err.println("Error setting the look and feel."); System.err.println("Error setting the look and feel: " + e.getMessage());
} }
GraphicsEnvironment environment =
GraphicsEnvironment.getLocalGraphicsEnvironment(); // Looking for the screen currently holding the mouse pointer
GraphicsDevice displayDevice = environment.getDefaultScreenDevice(); // that will be used as the screen device for this Zeichenmaschine
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = environment.getScreenDevices();
for( GraphicsDevice gd: devices ) {
if( gd.getDefaultConfiguration().getBounds().contains(mouseLoc) ) {
displayDevice = gd;
break;
}
}
if( displayDevice == null ) {
displayDevice = environment.getDefaultScreenDevice();
}
this.width = width;
this.height = height;
java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds();
this.screenWidth = (int)displayBounds.getWidth();
this.screenHeight = (int)displayBounds.getHeight();
frame = new JFrame(displayDevice.getDefaultConfiguration()); frame = new JFrame(displayDevice.getDefaultConfiguration());
frame.setTitle(title); frame.setTitle(title);
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
this.width = width;
this.height = height;
canvas = new Zeichenleinwand(width, height); canvas = new Zeichenleinwand(width, height);
frame.add(canvas); frame.add(canvas);
@ -96,6 +127,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
drawing = getDrawingLayer(); drawing = getDrawingLayer();
shapes = getShapesLayer(); shapes = getShapesLayer();
// TODO: When to call settings?
settings(); settings();
canvas.addMouseListener(this); canvas.addMouseListener(this);
@ -114,9 +146,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
frame.pack(); frame.pack();
frame.setResizable(false); frame.setResizable(false);
// TODO: Center on current display (not main display by default) centerFrame();
// TODO: Position at current BlueJ windows if IN_BLUEJ
frame.setLocationRelativeTo(null);
frame.setVisible(true); frame.setVisible(true);
canvas.allocateBuffer(); canvas.allocateBuffer();
@ -148,6 +178,19 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
} }
} }
public final void centerFrame() {
// TODO: Center on current display (not main display by default)
// TODO: Position at current BlueJ windows if IN_BLUEJ
//frame.setLocationRelativeTo(null);
//frame.setLocationRelativeTo(displayDevice.getFullScreenWindow());
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)
);
}
public final void redraw() { public final void redraw() {
canvas.render(); canvas.render();
//canvas.invalidate(); //canvas.invalidate();
@ -242,6 +285,64 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
framesPerSecond = pFramesPerSecond; framesPerSecond = pFramesPerSecond;
} }
public void saveImage() {
JFileChooser jfc = new JFileChooser();
jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
jfc.setMultiSelectionEnabled(false);
int status = jfc.showSaveDialog(frame);
if( status == JFileChooser.APPROVE_OPTION ) {
File outfile = jfc.getSelectedFile();
if( outfile.isDirectory() ) {
outfile = new File(outfile.getAbsolutePath() + File.separator + "zeichenmaschine.png");
}
saveImage(outfile.getAbsolutePath());
}
}
public void saveImage( String filepath ) {
BufferedImage img = new BufferedImage(canvas.getWidth(),canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setColor(STD_BACKGROUND.getColor());
g.fillRect(0, 0, img.getWidth(), img.getHeight());
canvas.draw(g);
g.dispose();
try {
ImageLoader.saveImage(img, new File(filepath), true);
} catch ( IOException ex ) {
ex.printStackTrace();
}
}
public ImageLayer snapshot() {
BufferedImage img = new BufferedImage(canvas.getWidth(),canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setColor(STD_BACKGROUND.getColor());
g.fillRect(0, 0, img.getWidth(), img.getHeight());
canvas.draw(g);
g.dispose();
/*
float factor = 0.8f;
float base = 255f * (1f - factor);
RescaleOp op = new RescaleOp(factor, base, null);
BufferedImage filteredImage
= new BufferedImage(img.getWidth(), img.getHeight(), img.getType());
op.filter(img, filteredImage);
*/
ImageLayer imgLayer = new ImageLayer(img);
if( canvas.getLayer(0) instanceof ColorLayer ) {
canvas.addLayer(1, imgLayer);
} else {
canvas.addLayer(0, imgLayer);
}
return imgLayer;
}
/* /*
* Methoden, die von Unterklassen überschrieben werden können / sollen. * Methoden, die von Unterklassen überschrieben werden können / sollen.
*/ */
@ -366,6 +467,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
pmouseX = mouseX; pmouseX = mouseX;
pmouseY = mouseY; pmouseY = mouseY;
// TODO: Seems not right ...
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation(); java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
java.awt.Point compLoc = canvas.getLocationOnScreen(); java.awt.Point compLoc = canvas.getLocationOnScreen();
mouseX = mouseLoc.x - compLoc.x; mouseX = mouseLoc.x - compLoc.x;

View File

@ -121,7 +121,7 @@ public abstract class Shape extends FilledShape {
* Bestimmt den Ankerpunkt der Form relativ zur oberen linken Ecke und * Bestimmt den Ankerpunkt der Form relativ zur oberen linken Ecke und
* abhängig vom gesetzten {@link #setAnchor(Options.Direction) Anker}. * abhängig vom gesetzten {@link #setAnchor(Options.Direction) Anker}.
*/ */
public Point2D.Double getAnchorPoint() { public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
Point2D.Double anchorpoint = new Point2D.Double(0, 0); Point2D.Double anchorpoint = new Point2D.Double(0, 0);
double bHalf = getWidth() * .5, hHalf = getHeight() * .5; double bHalf = getWidth() * .5, hHalf = getHeight() * .5;
@ -203,18 +203,36 @@ public abstract class Shape extends FilledShape {
this.rotation = angle % 360; this.rotation = angle % 360;
} }
public void rotate( Point2D center, double angle ) {
rotate(center.getX(), center.getY(), angle);
}
public void rotate( double x, double y, double angle ) {
this.rotation += angle % 360;
// Rotate x/y position
double x1 = this.x-x, y1 = this.y-y;
double rad = Math.toRadians(angle);
double x2 = x1 * Math.cos(rad) - y1 * Math.sin(rad);
double y2 = x1 * Math.sin(rad) + y1 * Math.cos(rad);
this.x = x2 + x;
this.y = y2 + y;
}
/*public void shear( double dx, double dy ) { /*public void shear( double dx, double dy ) {
verzerrung.shear(dx, dy); verzerrung.shear(dx, dy);
}*/ }*/
public AffineTransform getTransform() { public AffineTransform getTransform() {
Point2D.Double anchor = getAnchorPoint(); Point2D.Double anchorPoint = getAnchorPoint(this.anchor);
AffineTransform transform = new AffineTransform(); AffineTransform transform = new AffineTransform();
transform.translate(x, y); transform.translate(x, y);
transform.rotate(Math.toRadians(rotation)); transform.rotate(Math.toRadians(rotation));
//transform.scale(scale, scale); //transform.scale(scale, scale);
transform.translate(-anchor.x, -anchor.y); transform.translate(-anchorPoint.x, -anchorPoint.y);
return transform; return transform;
} }

View File

@ -1,7 +1,11 @@
package schule.ngb.zm.util; package schule.ngb.zm.util;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
@ -138,4 +142,45 @@ public class ImageLoader {
cacheing = false; cacheing = false;
} }
public static void saveImage( Image image, File file ) throws IOException {
saveImage(image, file, false);
}
public static void saveImage( Image image, File file, boolean overwriteIfExists ) throws IOException {
BufferedImage outimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g = outimage.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, outimage.getWidth(), outimage.getHeight());
g.drawImage(image, 0, 0, null);
g.dispose();
saveImage(outimage, file, overwriteIfExists);
}
public static void saveImage( BufferedImage image, File file ) throws IOException {
saveImage(image, file, false);
}
public static void saveImage( BufferedImage image, File file, boolean overwriteIfExists ) throws IOException {
if( file.isFile() ) {
if( !overwriteIfExists ) {
throw new IOException("File already exists. Delete target file before saving image.");
} else if( !file.canWrite() ) {
throw new IOException("File already exists and is not writeable. Change permissions before saving again.");
}
}
String filename = file.getName();
String formatName = "png";
if( filename.lastIndexOf('.') >= 0 ) {
formatName = filename.substring(filename.lastIndexOf('.') + 1);
} else {
file = new File(file.getAbsolutePath() + ".png");
}
ImageIO.write(image, formatName, file);
}
} }