diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fa17e87 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog +Alle wichtigen Änderungen an diesem Projekt werden in dieser Datei dokumentiert. + +Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.0.0/) +und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## Version 0.0.21 + +### Added +- Parameter `stop_after_draw` im Konstruktor der `Zeichenmaschine` erlaubt es beim Erstellen festzulegen, ob nach dem ersten Frame die Zeichenmaschine gestoppt wird. +- `Picture.tint(Color)` färbt ein Bild ein. +- `Picture.flip(Options.Direction)` spiegelt ein Bild entlang einer Achse (`LEFT`/`RIGHT` für horizontal, `UP`/`DOWN` für vertikal). +- Abstrakte Klasse `Zeichenobjekt` als einheitliche Oberklasse für Objekte in Projekten. Die Klasse erbt von `Constants` und implementiert `Drawabale` und `Updatable` mit leeren Methoden. +- Klasse `java.util.Validator` übernimmt intern Parametervalidierung. +- Klasse `Log` implementiert eine einfache Logging-API über `java.util.logging`. +- Klasse `TaskRunner` führt parallele Prozesse aus. +- `Zeichenmaschine#scheduleTask(Runnable, int)` führt eine Aufgabe nach einer Wartezeit im Gameloop aus. +- Neue Klasse `util.ResourceStreamProvider` sucht Resourcen und öffnet `InputStream`s. + +### Changed +- Objektvariablen der `Zeichenmaschine`, die von Unterklassen genutzt werden sollen, sind nun statisch in `Constants`. Dadurch können auch andere Klasse, die von `Constants` erben ohne Umwege auf diese Werte zugreifen (z.B. `width`/`height` der Zeichenleinwand). +- `ImageLoader` und `FontLoader` wurden überarbeitet. + - Nutzung von `Log` + - Nutzung von `ResourceStreamProvider` +- Verarbeitung von Swing `InputEvent`s in einer eigenen interne EventQueue synchron zur Framerate. diff --git a/examples/zm_aquarium/Aquarium.java b/examples/zm_aquarium/Aquarium.java deleted file mode 100644 index 1ef1e91..0000000 --- a/examples/zm_aquarium/Aquarium.java +++ /dev/null @@ -1,53 +0,0 @@ -import schule.ngb.zm.Zeichenmaschine; -import schule.ngb.zm.util.ImageLoader; - -public class Aquarium extends Zeichenmaschine { - - public static final int N_FISHES = 25; - - public static void main( String[] args ) { - new Aquarium(); - } - - private Fish[] fish; - - public Aquarium() { - super(800, 600, "Aquarium"); - } - - @Override - public void setup() { - canvas.addLayer(1, new Background()); - - fish = new Fish[N_FISHES]; - - for( int i = 1; i <= 7; i++ ) { - ImageLoader.preloadImage("fish"+i, "tiles/fish"+i+"gs.png"); - } - - for( int i = 0; i < N_FISHES; i++ ) { - fish[i] = new Fish(); - shapes.add(fish[i]); - } - } - - @Override - public void update( double delta ) { - for( int i = 0; i < fish.length; i++ ) { - fish[i].update(delta); - } - } - - @Override - public void draw() { - - } - - @Override - public void mouseClicked() { - for( int i = 0; i < fish.length; i++ ) { - fish[i].randomize(); - } - } - -} diff --git a/examples/zm_aquarium/Background.java b/examples/zm_aquarium/Background.java deleted file mode 100644 index 26cc674..0000000 --- a/examples/zm_aquarium/Background.java +++ /dev/null @@ -1,59 +0,0 @@ - -import schule.ngb.zm.Layer; - -import java.awt.Graphics2D; -import java.awt.Image; - -public class Background extends Layer { - - private static final int TILE_SIZE = 64; - - private int tile_width; - - private Image[] floor, plants; - - private Image water; - - public Background() { - super(); - } - - public Background( int width, int height ) { - super(width, height); - } - - @Override - public void setSize( int width, int height ) { - super.setSize(width, height); - generateBackground(); - } - - public void generateBackground() { - tile_width = (int)(ceil(getWidth()/(double)TILE_SIZE)); - floor = new Image[tile_width]; - plants = new Image[tile_width]; - - for ( int i = 0; i < tile_width; i++ ) { - floor[i] = loadImage("tiles/floor"+random(1,8)+".png"); - if( random(1,10) < 4 ) { - plants[i] = loadImage("tiles/plant"+random(1,14)+".png"); - } - } - - water = loadImage("tiles/water.png"); - - for ( int i = 0; i < getHeight(); i += TILE_SIZE ) { - for ( int j = 0; j < getWidth(); j += TILE_SIZE ) { - drawing.drawImage(water, j, i, null); - } - } - - for ( int i = 0; i < tile_width; i++ ) { - if( plants[i] != null ) { - drawing.drawImage(plants[i], i*TILE_SIZE, getHeight() - (2*TILE_SIZE) + 10,TILE_SIZE,TILE_SIZE, null); - } - drawing.drawImage(floor[i], i*TILE_SIZE, getHeight() - TILE_SIZE,TILE_SIZE,TILE_SIZE, null); - } - } - -} diff --git a/examples/zm_aquarium/Fish.java b/examples/zm_aquarium/Fish.java deleted file mode 100644 index 5e74e01..0000000 --- a/examples/zm_aquarium/Fish.java +++ /dev/null @@ -1,72 +0,0 @@ -import schule.ngb.zm.*; -import schule.ngb.zm.shapes.Picture; -import schule.ngb.zm.shapes.Shape; - -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; - -public class Fish extends Shape implements Updatable { - - private int speed; - - private Picture img; - - public Fish() { - randomize(); - } - - public void randomize() { - int i = random(1, 7); - speed = random(-2, 2); - img = new Picture("fish" + i); - img.setAnchor(NORTHWEST); - img.scale(random(0.8, 1.0)); - img.tint(randomNiceColor()); - //img.scale(0.5); - if( speed < 0 ) { - img.flip(LEFT); - } - img.moveTo(random(10, width-img.getWidth()), random(30, height-120)); - } - - @Override - public boolean isActive() { - return true; - } - - @Override - public void update( double delta ) { - img.move(speed, .5 * sin(tick / (speed * 10.0))); - - if( img.getX() <= 0 || img.getX()+img.getWidth() >= 800 ) { - speed *= -1; - img.flip(LEFT); - } - } - - @Override - public void draw( Graphics2D graphics, AffineTransform transform ) { - img.draw(graphics, transform); - } - - @Override - public double getWidth() { - return img.getWidth(); - } - - @Override - public double getHeight() { - return img.getHeight(); - } - - @Override - public Shape copy() { - return img.copy(); - } - - @Override - public java.awt.Shape getShape() { - return img.getShape(); - } - -} diff --git a/examples/zm_aquarium/package.bluej b/examples/zm_aquarium/package.bluej deleted file mode 100644 index 667b354..0000000 --- a/examples/zm_aquarium/package.bluej +++ /dev/null @@ -1,55 +0,0 @@ -#BlueJ package file -dependency1.from=Attractor -dependency1.to=Gravity -dependency1.type=UsesDependency -dependency2.from=Gravity -dependency2.to=Mover -dependency2.type=UsesDependency -dependency3.from=Gravity -dependency3.to=Attractor -dependency3.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=3 -package.numTargets=3 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=Mover -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=380 -target1.y=220 -target2.height=70 -target2.name=Attractor -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=380 -target2.y=350 -target3.height=70 -target3.name=Gravity -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=120 -target3.y=120 diff --git a/examples/zm_aquarium/tiles/fish1.png b/examples/zm_aquarium/tiles/fish1.png deleted file mode 100755 index 2b9669b..0000000 Binary files a/examples/zm_aquarium/tiles/fish1.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish1gs.png b/examples/zm_aquarium/tiles/fish1gs.png deleted file mode 100755 index efc04d1..0000000 Binary files a/examples/zm_aquarium/tiles/fish1gs.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish2.png b/examples/zm_aquarium/tiles/fish2.png deleted file mode 100755 index cda62e8..0000000 Binary files a/examples/zm_aquarium/tiles/fish2.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish2gs.png b/examples/zm_aquarium/tiles/fish2gs.png deleted file mode 100755 index e4f041a..0000000 Binary files a/examples/zm_aquarium/tiles/fish2gs.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish3.png b/examples/zm_aquarium/tiles/fish3.png deleted file mode 100755 index 0c66d35..0000000 Binary files a/examples/zm_aquarium/tiles/fish3.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish3gs.png b/examples/zm_aquarium/tiles/fish3gs.png deleted file mode 100755 index d44a90a..0000000 Binary files a/examples/zm_aquarium/tiles/fish3gs.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish4.png b/examples/zm_aquarium/tiles/fish4.png deleted file mode 100755 index 1a84d23..0000000 Binary files a/examples/zm_aquarium/tiles/fish4.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish4gs.png b/examples/zm_aquarium/tiles/fish4gs.png deleted file mode 100755 index 93d754b..0000000 Binary files a/examples/zm_aquarium/tiles/fish4gs.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish5.png b/examples/zm_aquarium/tiles/fish5.png deleted file mode 100755 index 377395b..0000000 Binary files a/examples/zm_aquarium/tiles/fish5.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish5gs.png b/examples/zm_aquarium/tiles/fish5gs.png deleted file mode 100755 index 2d3f7f8..0000000 Binary files a/examples/zm_aquarium/tiles/fish5gs.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish6.png b/examples/zm_aquarium/tiles/fish6.png deleted file mode 100755 index 3cb72f5..0000000 Binary files a/examples/zm_aquarium/tiles/fish6.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish6gs.png b/examples/zm_aquarium/tiles/fish6gs.png deleted file mode 100755 index 91d757d..0000000 Binary files a/examples/zm_aquarium/tiles/fish6gs.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish7.png b/examples/zm_aquarium/tiles/fish7.png deleted file mode 100755 index 0cb1b91..0000000 Binary files a/examples/zm_aquarium/tiles/fish7.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/fish7gs.png b/examples/zm_aquarium/tiles/fish7gs.png deleted file mode 100755 index 96d9b98..0000000 Binary files a/examples/zm_aquarium/tiles/fish7gs.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/floor1.png b/examples/zm_aquarium/tiles/floor1.png deleted file mode 100755 index b4dd530..0000000 Binary files a/examples/zm_aquarium/tiles/floor1.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/floor2.png b/examples/zm_aquarium/tiles/floor2.png deleted file mode 100755 index d7c0f11..0000000 Binary files a/examples/zm_aquarium/tiles/floor2.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/floor3.png b/examples/zm_aquarium/tiles/floor3.png deleted file mode 100755 index f28f203..0000000 Binary files a/examples/zm_aquarium/tiles/floor3.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/floor4.png b/examples/zm_aquarium/tiles/floor4.png deleted file mode 100755 index 662e516..0000000 Binary files a/examples/zm_aquarium/tiles/floor4.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/floor5.png b/examples/zm_aquarium/tiles/floor5.png deleted file mode 100755 index b4fbd5b..0000000 Binary files a/examples/zm_aquarium/tiles/floor5.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/floor6.png b/examples/zm_aquarium/tiles/floor6.png deleted file mode 100755 index b18c712..0000000 Binary files a/examples/zm_aquarium/tiles/floor6.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/floor7.png b/examples/zm_aquarium/tiles/floor7.png deleted file mode 100755 index 17a6d84..0000000 Binary files a/examples/zm_aquarium/tiles/floor7.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/floor8.png b/examples/zm_aquarium/tiles/floor8.png deleted file mode 100755 index 46d46ce..0000000 Binary files a/examples/zm_aquarium/tiles/floor8.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant1.png b/examples/zm_aquarium/tiles/plant1.png deleted file mode 100755 index 65a66b4..0000000 Binary files a/examples/zm_aquarium/tiles/plant1.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant10.png b/examples/zm_aquarium/tiles/plant10.png deleted file mode 100755 index 23b2e05..0000000 Binary files a/examples/zm_aquarium/tiles/plant10.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant11.png b/examples/zm_aquarium/tiles/plant11.png deleted file mode 100755 index 82ab129..0000000 Binary files a/examples/zm_aquarium/tiles/plant11.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant12.png b/examples/zm_aquarium/tiles/plant12.png deleted file mode 100755 index f31033d..0000000 Binary files a/examples/zm_aquarium/tiles/plant12.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant13.png b/examples/zm_aquarium/tiles/plant13.png deleted file mode 100755 index 8443d04..0000000 Binary files a/examples/zm_aquarium/tiles/plant13.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant14.png b/examples/zm_aquarium/tiles/plant14.png deleted file mode 100755 index 0222446..0000000 Binary files a/examples/zm_aquarium/tiles/plant14.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant2.png b/examples/zm_aquarium/tiles/plant2.png deleted file mode 100755 index ddb806f..0000000 Binary files a/examples/zm_aquarium/tiles/plant2.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant3.png b/examples/zm_aquarium/tiles/plant3.png deleted file mode 100755 index 1c5abf0..0000000 Binary files a/examples/zm_aquarium/tiles/plant3.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant4.png b/examples/zm_aquarium/tiles/plant4.png deleted file mode 100755 index a0bed9c..0000000 Binary files a/examples/zm_aquarium/tiles/plant4.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant5.png b/examples/zm_aquarium/tiles/plant5.png deleted file mode 100755 index 92b9fb6..0000000 Binary files a/examples/zm_aquarium/tiles/plant5.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant6.png b/examples/zm_aquarium/tiles/plant6.png deleted file mode 100755 index 17b890e..0000000 Binary files a/examples/zm_aquarium/tiles/plant6.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant7.png b/examples/zm_aquarium/tiles/plant7.png deleted file mode 100755 index 6d9ae63..0000000 Binary files a/examples/zm_aquarium/tiles/plant7.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant8.png b/examples/zm_aquarium/tiles/plant8.png deleted file mode 100755 index d2214e4..0000000 Binary files a/examples/zm_aquarium/tiles/plant8.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/plant9.png b/examples/zm_aquarium/tiles/plant9.png deleted file mode 100755 index b386816..0000000 Binary files a/examples/zm_aquarium/tiles/plant9.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/shark.png b/examples/zm_aquarium/tiles/shark.png deleted file mode 100644 index 15c2655..0000000 Binary files a/examples/zm_aquarium/tiles/shark.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton1.png b/examples/zm_aquarium/tiles/skeleton1.png deleted file mode 100755 index 6f5776d..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton1.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton10.png b/examples/zm_aquarium/tiles/skeleton10.png deleted file mode 100755 index e2c5ed7..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton10.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton2.png b/examples/zm_aquarium/tiles/skeleton2.png deleted file mode 100755 index 1190025..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton2.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton3.png b/examples/zm_aquarium/tiles/skeleton3.png deleted file mode 100755 index a3ed460..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton3.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton4.png b/examples/zm_aquarium/tiles/skeleton4.png deleted file mode 100755 index 588830d..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton4.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton5.png b/examples/zm_aquarium/tiles/skeleton5.png deleted file mode 100755 index 12ab78c..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton5.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton6.png b/examples/zm_aquarium/tiles/skeleton6.png deleted file mode 100755 index c1dc192..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton6.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton7.png b/examples/zm_aquarium/tiles/skeleton7.png deleted file mode 100755 index 0b01e7f..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton7.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton8.png b/examples/zm_aquarium/tiles/skeleton8.png deleted file mode 100755 index a693440..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton8.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/skeleton9.png b/examples/zm_aquarium/tiles/skeleton9.png deleted file mode 100755 index ead8d7e..0000000 Binary files a/examples/zm_aquarium/tiles/skeleton9.png and /dev/null differ diff --git a/examples/zm_aquarium/tiles/water.png b/examples/zm_aquarium/tiles/water.png deleted file mode 100755 index eb6e80f..0000000 Binary files a/examples/zm_aquarium/tiles/water.png and /dev/null differ diff --git a/examples/zm_boids/Boid.java b/examples/zm_boids/Boid.java deleted file mode 100644 index 9b5c118..0000000 --- a/examples/zm_boids/Boid.java +++ /dev/null @@ -1,128 +0,0 @@ -import schule.ngb.zm.*; - -import java.util.List; - -public class Boid extends Creature { - - public static final double COHESION_FACTOR = .5; - - public static final double SEPARATION_FACTOR = .5; - - public static final double ALIGNMENT_FACTOR = .5; - - public static final double FLIGHT_FACTOR = .5; - - public static final double VELOCITY_LIMIT = 3.0; - - public static final double FORCE_LIMIT = .1; - - public static final int FLOCK_RADIUS = 100; - - public static final boolean SHOW_RADIUS = false; - - public static final int BOID_WIDTH = 8; - - public static final int BOID_HEIGHT = 16; - - private boolean highlight = false; - - public Boid() { - // Generate random Boid - super( - Vector.random(BOID_WIDTH, width-BOID_WIDTH, BOID_HEIGHT, height-BOID_HEIGHT), - Vector.random(-1, 1).scale(random(VELOCITY_LIMIT)) - ); - senseRange = FLOCK_RADIUS; - } - - public Boid( Vector pos, Vector vel ) { - super(pos, vel); - senseRange = FLOCK_RADIUS; - } - - public void toggleHighlight() { - highlight = !highlight; - } - - public void draw( DrawingLayer drawing ) { - drawing.setFillColor(251, 241, 195); - drawing.setStrokeColor(239, 191, 77); - drawing.setStrokeWeight(2); - - drawing.pushMatrix(); - drawing.translate(position.x, position.y); - drawing.rotate(90 + velocity.angle()); - drawing.triangle(BOID_WIDTH / -2.0, BOID_HEIGHT / 2.0, 0, BOID_HEIGHT / -2.0, BOID_WIDTH / 2.0, BOID_HEIGHT / 2.0); - - if( SHOW_RADIUS || highlight ) { - drawing.setFillColor(251, 241, 195, 33); - drawing.noStroke(); - drawing.circle(0, 0, FLOCK_RADIUS); - } - drawing.popMatrix(); - } - - - public void update( List creatures ) { - Vector cohesion = new Vector(); - Vector separation = new Vector(); - Vector alignment = new Vector(); - Vector flight = new Vector(); - int boids = 0, predators = 0; - - for( Creature c: creatures ) { - if( isInRange(c) ) { - if( Predator.class.isInstance(c) ) { - double distSq = position.distanceSq(c.getPosition()); - flight.add(Vector.sub(position, c.getPosition()).div(distSq)); - - predators += 1; - } else { - cohesion.add(c.getPosition()); - alignment.add(c.getVelocity()); - - double distSq = position.distanceSq(c.getPosition()); - separation.add(Vector.sub(position, c.getPosition()).div(distSq)); - - boids += 1; - } - } - } - - if( boids > 0 ) { - // Cohesion - cohesion.div(boids).sub(position); - cohesion.setLength(VELOCITY_LIMIT).sub(velocity); - cohesion.limit(FORCE_LIMIT); - cohesion.scale(COHESION_FACTOR); - // Separation - separation.div(boids); - separation.setLength(VELOCITY_LIMIT).sub(velocity); - separation.limit(FORCE_LIMIT); - separation.scale(SEPARATION_FACTOR); - // Alignment - alignment.div(boids); - alignment.setLength(VELOCITY_LIMIT).sub(velocity); - alignment.limit(FORCE_LIMIT); - alignment.scale(ALIGNMENT_FACTOR); - } - if( predators > 0 ) { - flight.div(predators); - flight.setLength(VELOCITY_LIMIT).sub(velocity); - flight.limit(FORCE_LIMIT*4.0); - flight.scale(FLIGHT_FACTOR); - } - - acceleration - .scale(0.0) - .add(separation) - .add(cohesion) - .add(alignment) - .add(flight); - - position.add(velocity); - limitPosition(); - velocity.add(acceleration).limit(VELOCITY_LIMIT); - } - -} diff --git a/examples/zm_boids/Boids.java b/examples/zm_boids/Boids.java deleted file mode 100644 index 7b9a4c0..0000000 --- a/examples/zm_boids/Boids.java +++ /dev/null @@ -1,78 +0,0 @@ -import schule.ngb.zm.*; -import schule.ngb.zm.util.ImageLoader; - -import java.awt.event.KeyEvent; -import java.util.ArrayList; -import java.util.List; - -public class Boids extends Zeichenmaschine { - - public static void main( String[] args ) { - new Boids(); - } - - public static final boolean BORDER_WRAP = true; - - public static final int N_BOIDS = 200; - - public static final int N_PREDATORS = 0; - - private List creatures; - - public Boids() { - super(1280, 720, "ZM: Boids"); - } - - @Override - public void setup() { - setFullscreen(true); - setCursor(ImageLoader.loadImage("pointer.png"), 0, 0); - - creatures = new ArrayList(); - - synchronized( creatures ) { - for( int i = 0; i < N_BOIDS; i++ ) { - creatures.add(new Boid()); - } - for( int i = 0; i < N_PREDATORS; i++ ) { - creatures.add(new Predator()); - } - } - } - - @Override - public void update( double delta ) { - synchronized( creatures ) { - for( Creature c : creatures ) { - c.update(creatures); - } - } - } - - @Override - public void draw() { - drawing.clear(0, 125, 182); - synchronized( creatures ) { - for( Creature c : creatures ) { - c.draw(drawing); - } - } - } - - @Override - public void mouseClicked() { - synchronized( creatures ) { - creatures.add(new Predator( - Vector.mouse(), Vector.ZERO - )); - } - } - - @Override - public void keyPressed() { - if( keyCode == KeyEvent.VK_G ) { - setSize(800, 800); - } - } - -} diff --git a/examples/zm_boids/Creature.java b/examples/zm_boids/Creature.java deleted file mode 100644 index 42a584a..0000000 --- a/examples/zm_boids/Creature.java +++ /dev/null @@ -1,77 +0,0 @@ -import schule.ngb.zm.Constants; -import schule.ngb.zm.DrawingLayer; -import schule.ngb.zm.Vector; - -import java.util.List; - -public abstract class Creature extends Constants { - - protected Vector position, velocity, acceleration; - - protected int senseRange = 100; - - public Creature( Vector pos, Vector vel ) { - position = pos.copy(); - velocity = vel.copy(); - acceleration = new Vector(); - } - - public Vector getPosition() { - return position; - } - - public Vector getVelocity() { - return velocity; - } - - public abstract void draw( DrawingLayer drawing ); - - public abstract void update( List creatures ); - - protected void limitPosition() { - if( position.x < 0 ) { - if( Boids.BORDER_WRAP ) { - position.x = width; - } else { - position.x = 0; - //velocity.mult(-1); - acceleration.add(Vector.scale(velocity, -2)); - } - } else if( position.x > width ) { - if( Boids.BORDER_WRAP ) { - position.x = 0; - } else { - position.x = width; - //velocity.mult(-1); - acceleration.add(Vector.scale(velocity, -2)); - } - } - if( position.y < 0 ) { - if( Boids.BORDER_WRAP ) { - position.y = height; - } else { - position.y = 0; - //velocity.mult(-1); - acceleration.add(Vector.scale(velocity, -2)); - } - } else if( position.y > height ) { - if( Boids.BORDER_WRAP ) { - position.y = 0.0; - } else { - position.y = height; - //velocity.mult(-1); - acceleration.add(Vector.scale(velocity, -2)); - } - } - } - - protected boolean isInRange( Creature otherCreature ) { - if( otherCreature == this || otherCreature == null ) { - return false; - } - if( abs(position.x-otherCreature.getPosition().x) > senseRange ) { - return false; - } - return position.distanceSq(otherCreature.getPosition()) < senseRange*senseRange; - } -} diff --git a/examples/zm_boids/Predator.java b/examples/zm_boids/Predator.java deleted file mode 100644 index 00dfc70..0000000 --- a/examples/zm_boids/Predator.java +++ /dev/null @@ -1,97 +0,0 @@ -import schule.ngb.zm.DrawingLayer; -import schule.ngb.zm.Vector; - -import java.util.List; - -public class Predator extends Creature { - - public static final int SENSE_RADIUS = 180; - - public static final double AVOIDANCE_FACTOR = .65; - - public static final double HUNTING_FACTOR = .65; - - public static final double VELOCITY_LIMIT = 5.0; - - public static final double FORCE_LIMIT = .1; - - public static final int PREDATOR_WIDTH = 12; - - public static final int PREDATOR_HEIGHT = 26; - - public static final boolean SHOW_RADIUS = true; - - public Predator() { - // Generate random Predator - super( - Vector.random(PREDATOR_WIDTH, width - PREDATOR_WIDTH, PREDATOR_HEIGHT, height - PREDATOR_HEIGHT), - Vector.random(-1, 1).scale(random(VELOCITY_LIMIT)) - ); - senseRange = SENSE_RADIUS; - } - - public Predator( Vector pos, Vector vel ) { - super(pos, vel); - senseRange = SENSE_RADIUS; - } - - public void draw( DrawingLayer drawing ) { - drawing.setFillColor(152, 61, 83); - drawing.setStrokeColor(225, 33, 32); - drawing.setStrokeWeight(2); - - drawing.pushMatrix(); - drawing.translate(position.x, position.y); - drawing.rotate(90 + velocity.angle()); - drawing.triangle(PREDATOR_WIDTH / -2.0, PREDATOR_HEIGHT / 2.0, 0, PREDATOR_HEIGHT / -2.0, PREDATOR_WIDTH / 2.0, PREDATOR_HEIGHT / 2.0); - - if( SHOW_RADIUS ) { - drawing.setFillColor(152, 61, 83, 33); - drawing.noStroke(); - drawing.circle(0, 0, SENSE_RADIUS); - } - drawing.popMatrix(); - } - - public void update( List creatures ) { - Vector hunting = new Vector(); - Vector separation = new Vector(); - double boids = 0, predators = 0; - - for( Creature c : creatures ) { - if( isInRange(c) ) { - if( Boid.class.isInstance(c) ) { - hunting.add(c.getPosition()); - boids += 1; - } else { - double distSq = position.distanceSq(c.getPosition()); - separation.add(Vector.sub(position, c.getPosition()).div(distSq)); - - predators += 1; - } - } - } - if( boids > 0 ) { - hunting.div(boids).sub(position); - hunting.setLength(VELOCITY_LIMIT).sub(velocity); - hunting.limit(FORCE_LIMIT); - hunting.scale(HUNTING_FACTOR); - } - if( predators > 0 ) { - separation.div(predators); - separation.setLength(VELOCITY_LIMIT).sub(velocity); - separation.limit(FORCE_LIMIT); - separation.scale(AVOIDANCE_FACTOR); - } - - acceleration - .scale(0.0) - .add(hunting) - .add(separation); - - position.add(velocity); - limitPosition(); - velocity.add(acceleration).limit(VELOCITY_LIMIT); - } - -} diff --git a/examples/zm_boids/package.bluej b/examples/zm_boids/package.bluej deleted file mode 100644 index 667b354..0000000 --- a/examples/zm_boids/package.bluej +++ /dev/null @@ -1,55 +0,0 @@ -#BlueJ package file -dependency1.from=Attractor -dependency1.to=Gravity -dependency1.type=UsesDependency -dependency2.from=Gravity -dependency2.to=Mover -dependency2.type=UsesDependency -dependency3.from=Gravity -dependency3.to=Attractor -dependency3.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=3 -package.numTargets=3 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=Mover -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=380 -target1.y=220 -target2.height=70 -target2.name=Attractor -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=380 -target2.y=350 -target3.height=70 -target3.name=Gravity -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=120 -target3.y=120 diff --git a/examples/zm_boids/pointer.png b/examples/zm_boids/pointer.png deleted file mode 100644 index cba0d89..0000000 Binary files a/examples/zm_boids/pointer.png and /dev/null differ diff --git a/examples/zm_cookieclicker/Cookieclicker.java b/examples/zm_cookieclicker/Cookieclicker.java deleted file mode 100644 index b981dbd..0000000 --- a/examples/zm_cookieclicker/Cookieclicker.java +++ /dev/null @@ -1,101 +0,0 @@ -import schule.ngb.zm.GraphicsLayer; -import schule.ngb.zm.Options; -import schule.ngb.zm.Zeichenmaschine; -import schule.ngb.zm.shapes.Picture; -import schule.ngb.zm.shapes.Text; - -import java.util.ArrayList; - -public class Cookieclicker extends Zeichenmaschine { - - public static void main( String[] args ) { - new Cookieclicker(); - } - - private int cookies = 0; - - private int cookiesPerClick = 1; - - private int grandmas = 0; - - private int autoclicker = 12, autoclickerTimer = 0, autoClickerDelay = 600; - - private Text tCookies, tShopGrandma, tShopAutoclicker; - - private Picture pCookie; - - private boolean cookieDown = false; - - public Cookieclicker() { - super(1280, 900, "Cookieclicker"); - } - - @Override - public void setup() { - pCookie = new Picture(width / 2, height / 2, "assets/cookie.png"); - pCookie.scale(.5); - shapes.add(pCookie); - - tCookies = new Text(width - 60, 60, "" + cookies); - tCookies.setAnchor(NORTHEAST); - tCookies.setStrokeColor(255); - tCookies.setFontsize(36); - shapes.add(tCookies); - - background.setColor(0); - } - - @Override - public void update( double delta ) { - tCookies.setText("" + cookies); - - autoclickerTimer -= (int)(delta * 1000.0); - if( autoclickerTimer <= 0 ) { - cookies += autoclicker; - autoclickerTimer += autoClickerDelay; - } - - synchronized( particles ) { - ArrayList remove = new ArrayList<>(); - for( NumberParticle p : particles ) { - if( p.isActive() ) { - p.update(delta); - } else { - remove.add(p); - } - } - for( NumberParticle p : remove ) { - particles.remove(p); - } - } - } - - @Override - public void draw() { - } - - ArrayList particles = new ArrayList<>(); - - @Override - public void mousePressed() { - if( pCookie.getBounds().contains(mouseX, mouseY) ) { - cookieDown = true; - cookies += cookiesPerClick; - pCookie.scale(.95); - - synchronized( particles ) { - NumberParticle p = new NumberParticle(mouseX, mouseY, cookiesPerClick); - particles.add(p); - shapes.add(p); - } - } - } - - @Override - public void mouseReleased() { - if( cookieDown ) { - pCookie.scale(1 / .95); - } - } - -} diff --git a/examples/zm_cookieclicker/NumberParticle.java b/examples/zm_cookieclicker/NumberParticle.java deleted file mode 100644 index f63a9a3..0000000 --- a/examples/zm_cookieclicker/NumberParticle.java +++ /dev/null @@ -1,33 +0,0 @@ -import schule.ngb.zm.Updatable; -import schule.ngb.zm.shapes.Text; - -public class NumberParticle extends Text implements Updatable { - - double sinOffset, life = 0; - - public NumberParticle( double x, double y, int number ) { - super(x,y,"+"+number); - sinOffset = random(0.0, PI); - life = 1.5; - setStrokeColor(255); - setFontsize(36); - } - - @Override - public boolean isActive() { - return (life > 0); - } - - @Override - public void update( double delta ) { - if( isActive() ) { - double deltaX = sin(sinOffset + life); - x += deltaX; - y -= 100*delta; - - life -= delta; - setStrokeColor(strokeColor, (int) interpolate(0, 255, life / 1.5)); - } - } - -} diff --git a/examples/zm_cookieclicker/NumberParticleEmitter.java b/examples/zm_cookieclicker/NumberParticleEmitter.java deleted file mode 100644 index 5b36b4d..0000000 --- a/examples/zm_cookieclicker/NumberParticleEmitter.java +++ /dev/null @@ -1,111 +0,0 @@ -import schule.ngb.zm.Drawable; -import schule.ngb.zm.Updatable; -import schule.ngb.zm.shapes.Text; - -import java.awt.Graphics2D; -import java.util.ArrayList; - -public class NumberParticleEmitter implements Updatable, Drawable { - - public class NumberParticle extends Text implements Updatable { - - double velX = 0, velY = 0, accX = 0, accY = 0; - - double life = 0; - - public NumberParticle() { - super(0, 0, "0"); - // life = particleLifespan; - } - - @Override - public boolean isActive() { - return (life <= 0); - } - - public void activate() { - x = emitterX; - y = emitterY; - life = particleLifespan; - } - - @Override - public void update( double delta ) { - if( isActive() ) { - velX += accX; - velY += accY; - x += velX; - y += velY; - - life -= delta; - setFillColor(fillColor, (int) interpolate(0, 255, life / particleLifespan)); - } - } - - } - - private int particlesPerSecond, maxParticles; - - private double particleLifespan; - - private ArrayList particles; - - private int activeParticles = 0; - - private double lastEmit = 0.0; - - private double emitterX, emitterY; - - public NumberParticleEmitter( double x, double y, int particlesPerSecond, double particleLifespan ) { - emitterX = x; - emitterY = y; - - this.particlesPerSecond = particlesPerSecond; - this.particleLifespan = particleLifespan; - - maxParticles = (int) Math.ceil(particlesPerSecond * particleLifespan); - particles = new ArrayList<>(maxParticles); - - for( int i = 0; i < maxParticles; i++ ) { - particles.add(new NumberParticle()); - } - } - - @Override - public boolean isVisible() { - return activeParticles > 0; - } - - @Override - public void draw( Graphics2D graphics ) { - for( NumberParticle p : particles ) { - if( p.isActive() ) { - p.draw(graphics); - } - } - } - - @Override - public boolean isActive() { - return isVisible(); - } - - public void emitParticle( int number ) { - particles.add(new NumberParticle()); - } - - @Override - public void update( double delta ) { - //lastEmit -= delta; - - for( NumberParticle p : particles ) { - if( p.isActive() ) { - p.update(delta); - } /*else if( lastEmit <= 0 ) { - p.activate(); - lastEmit += 1/(double)particlesPerSecond; - }*/ - } - } - -} diff --git a/examples/zm_cookieclicker/ParticleTest.java b/examples/zm_cookieclicker/ParticleTest.java deleted file mode 100644 index 700298f..0000000 --- a/examples/zm_cookieclicker/ParticleTest.java +++ /dev/null @@ -1,36 +0,0 @@ -import schule.ngb.zm.GraphicsLayer; -import schule.ngb.zm.Zeichenmaschine; - -public class ParticleTest extends Zeichenmaschine { - - public static void main( String[] args ) { - new ParticleTest(); - } - - private NumberParticleEmitter emitter; - - private GraphicsLayer graphics; - - public ParticleTest() { - super(800,800, "Particles"); - } - - @Override - public void setup() { - graphics = new GraphicsLayer(); - canvas.addLayer(graphics); - - emitter = new NumberParticleEmitter(400, 400, 1, 10); - } - - @Override - public void update( double delta ) { - emitter.update(delta); - } - - @Override - public void draw() { - emitter.draw(graphics.getGraphics()); - } - -} diff --git a/examples/zm_cookieclicker/assets/Cookie.png b/examples/zm_cookieclicker/assets/Cookie.png deleted file mode 100644 index 231b81b..0000000 Binary files a/examples/zm_cookieclicker/assets/Cookie.png and /dev/null differ diff --git a/examples/zm_cookieclicker/package.bluej b/examples/zm_cookieclicker/package.bluej deleted file mode 100644 index 667b354..0000000 --- a/examples/zm_cookieclicker/package.bluej +++ /dev/null @@ -1,55 +0,0 @@ -#BlueJ package file -dependency1.from=Attractor -dependency1.to=Gravity -dependency1.type=UsesDependency -dependency2.from=Gravity -dependency2.to=Mover -dependency2.type=UsesDependency -dependency3.from=Gravity -dependency3.to=Attractor -dependency3.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=3 -package.numTargets=3 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=Mover -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=380 -target1.y=220 -target2.height=70 -target2.name=Attractor -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=380 -target2.y=350 -target3.height=70 -target3.name=Gravity -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=120 -target3.y=120 diff --git a/examples/zm_follow/Eye.java b/examples/zm_follow/Eye.java deleted file mode 100644 index 671c0db..0000000 --- a/examples/zm_follow/Eye.java +++ /dev/null @@ -1,51 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.DrawingLayer; -import schule.ngb.zm.Vector; -import schule.ngb.zm.Zeichenobjekt; - -public class Eye extends Zeichenobjekt { - - private Vector position; - - private double size; - - private Color color; - - public Eye() { - position = Vector.random(0, width, 0, height); - size = random(10.0, 25.0); - color = null; - } - - public Eye( float x, float y ) { - position = new Vector(x, y); - size = random(10.0, 25.0); - color = null; - } - - public Eye( float x, float y, Color color ) { - position = new Vector(x, y); - size = random(10.0, 25.0); - this.color = color; - } - - @Override - public void draw( DrawingLayer drawing ) { - Vector dir = Vector.sub(new Vector(mouseX, mouseY), position); - double len = dir.length(); - - drawing.setStrokeColor(0); - if( color == null ) { - drawing.setFillColor(colorHsb(354, 100.0 - limit(len, 0.0, 100.0), 100)); - } else { - drawing.setFillColor(color); - } - drawing.circle(position.x, position.y, size); - - - Vector pupil = Vector.add(position, dir.limit(size*.4)); - - drawing.setFillColor(0); - drawing.circle(pupil.x, pupil.y, size*.4); - } -} diff --git a/examples/zm_follow/Eyes.java b/examples/zm_follow/Eyes.java deleted file mode 100644 index d989711..0000000 --- a/examples/zm_follow/Eyes.java +++ /dev/null @@ -1,38 +0,0 @@ -import schule.ngb.zm.Zeichenmaschine; - -public class Eyes extends Zeichenmaschine { - - public static final int N_EYES = 30; - - public static void main( String[] args ) { - new Eyes(); - } - - Eye[] eyes; - - public Eyes() { - super(600, 600, "Eyes"); - } - - @Override - public void setup() { - eyes = new Eye[N_EYES]; - for( int i = 0; i < eyes.length; i++ ) { - eyes[i] = new Eye(); - } - } - - @Override - public void update( double delta ) { - - } - - @Override - public void draw() { - drawing.clear(200); - - for( int i = 0; i < eyes.length; i++ ) { - eyes[i].draw(drawing); - } - } -} diff --git a/examples/zm_follow/package.bluej b/examples/zm_follow/package.bluej deleted file mode 100644 index 667b354..0000000 --- a/examples/zm_follow/package.bluej +++ /dev/null @@ -1,55 +0,0 @@ -#BlueJ package file -dependency1.from=Attractor -dependency1.to=Gravity -dependency1.type=UsesDependency -dependency2.from=Gravity -dependency2.to=Mover -dependency2.type=UsesDependency -dependency3.from=Gravity -dependency3.to=Attractor -dependency3.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=3 -package.numTargets=3 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=Mover -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=380 -target1.y=220 -target2.height=70 -target2.name=Attractor -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=380 -target2.y=350 -target3.height=70 -target3.name=Gravity -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=120 -target3.y=120 diff --git a/examples/zm_forces/Atoms.java b/examples/zm_forces/Atoms.java deleted file mode 100644 index da47d15..0000000 --- a/examples/zm_forces/Atoms.java +++ /dev/null @@ -1,94 +0,0 @@ -import schule.ngb.zm.Zeichenmaschine; - -import java.util.LinkedList; - -public class Atoms extends Zeichenmaschine { - - /** - * Liste der beweglichen Objekte in der Welt. - */ - private LinkedList movers = new LinkedList<>(); - - /** - * Liste der Gravitationsquellen in der Welt. - */ - private LinkedList attractors = new LinkedList<>(); - - /** - * Erstellt die {@link Mover}- und {@link Attractor}-Objekte. - */ - public void setup() { - addAttractor( width / 2, height / 2, 20 ); - attractors.getFirst().setMovable(false); - attractors.getFirst().setActive(false); - attractors.getFirst().hide(); - - addAttractor( width*.4, height*.4, 8 ); - addAttractor( width*.6, height*.6, 8 ); - - for( int i = 0; i < 10; i++ ) { - Mover m = new Mover(random(10, width - 10), random(10, height - 10)); - movers.add(m); - shapes.add(m); - } - } - - private void addAttractor( double pX, double pY, int pMass ) { - Attractor a = new Attractor((int)pX, (int)pY, pMass); - attractors.add(a); - movers.add(a); - shapes.add(a); - } - - /** - * Aktualisiert die Beschleunigung der {@link Mover}-Objekte durch Anwenden - * der einwirkenden Kräfte und aktualisiert dann die Position entsprechend - * der Beschleunigung. - *

- * Die Position des ersten {@link Attractor} wird auf die Mausposition - * gesetzt. - * - * @param delta - */ - public void update( double delta ) { - // Erste Gravitationsquelle auf Mausposition setzen. - Attractor mouseFollow = attractors.get(0); - if( mouseFollow.isActive() ) { - mouseFollow.moveTo(mouseX, mouseY); - } - - // Kräfte anwenden - for( Attractor a : attractors ) { - if( a.isActive() ) { - for( Mover m : movers ) { - if( m.isActive() ) { - a.attract(m); - } - } - } - } - - // Position aktualisieren - for( Mover m : movers ) { - if( m.isActive() ) { - m.update(delta); - } - } - } - - /** - * Setzt die Position und Beschleunigung aller {@link Mover}-Objekte in der - * Welt zurück. - */ - public void mouseClicked() { - for( Mover m : movers ) { - m.moveTo(random(10, width - 10), random(10, height - 10)); - m.setVelocity(0, 0); - } - } - - public static void main( String[] args ) { - new Atoms(); - } - -} diff --git a/examples/zm_forces/Attractor.java b/examples/zm_forces/Attractor.java deleted file mode 100644 index e95c2b9..0000000 --- a/examples/zm_forces/Attractor.java +++ /dev/null @@ -1,110 +0,0 @@ -import schule.ngb.zm.Vector; - -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; - -/** - * Gravitationsquelle in der Simulation. - *

- * Eine Gravitationsquelle zieht mit einer Anziehungskraft proportional zu - * seiner Masse alle {@link Mover}-Objekte an. Dabei kommt die Newtonsche - * Gravitationsformel zur Anwendung. - *

- * Ein Attractor ist auch ein {@link Mover} und wird von anderen - * Gravitationsquellen beeinflusst. Dieses Verhalten kann durch Setzen von - * setMovable(false) abgeschaltet werden. - */ -public class Attractor extends Mover { - - /** - * Gravitationskonstante - *

- * Beeinflusst die Stärke der Anziehungskraft der {@link Attractor}en. - */ - public static final int G = 25; - - /** - * Ob dieser Attractor auch von anderen Kräften beeinflusst wird. - */ - private boolean movable = true; - - /** - * Erstellt einen Attractor an der angegebenen Position mit der angegebenen - * Masse. - * - * @param pX x-Koordinate des Objektes. - * @param pY y-Koordinate des Objektes. - * @param pMass Masse des Objektes. - */ - public Attractor( int pX, int pY, int pMass ) { - this(pX, pY, pMass, new Vector()); - } - - /** - * Erstellt einen Attractor an der angegebenen Position - * - * @param pX x-Koordinate des Objektes. - * @param pY y-Koordinate des Objektes. - * @param pMass Masse des Objektes. - * @param pVelocity Initialgeschwindigkeit des Objektes. - */ - public Attractor( int pX, int pY, int pMass, Vector pVelocity ) { - super(pX, pY, pVelocity); - mass = pMass; - - setFillColor(randomColor()); - } - - /** - * Stellt ein, ob dieser Attractor auch von anderen Kräften - * beeinflusst wird, oder ob er starr an einer Position bleibt. - * - * @param pMovable true oder false. - */ - public void setMovable( boolean pMovable ) { - this.movable = pMovable; - } - - /** - * Wendet die Anziehungskraft des Attractor auf einen - * Mover an. - * - * @param pMover Das Objekt, das angezogen wird. - */ - public void attract( Mover pMover ) { - if( pMover != this && isActive() ) { - Vector force = new Vector(this.x, this.y); - force.sub(pMover.getX(), pMover.getY()); - double v = G * mass / force.lengthSq(); - force.setLength(v).limit(1.0, 4 * G); - pMover.applyForce(force); - } - } - - /** - * Aktualisiert die momentante Geschwindigkeit und Position des Objektes und - * setzt die Beschleunigung zurück. - * - * @param delta Zeitintervall seit dem letzten Aufruf (in Sekunden). - */ - @Override - public void update( double delta ) { - if( movable ) { - super.update(delta); - } - } - - @Override - public void draw( Graphics2D graphics, AffineTransform transform ) { - double m = 2.0*mass; - - AffineTransform at = graphics.getTransform(); - graphics.transform(transform); - graphics.setColor(new java.awt.Color(255,193,64,66)); - graphics.fillOval((int)(-.5*m), (int)(-.5*m), (int)(2*getRadius()+m), (int)(2*getRadius()+m)); - graphics.setTransform(at); - - super.draw(graphics, transform); - } - -} diff --git a/examples/zm_forces/Gravity.java b/examples/zm_forces/Gravity.java deleted file mode 100644 index 96aec5e..0000000 --- a/examples/zm_forces/Gravity.java +++ /dev/null @@ -1,92 +0,0 @@ -import schule.ngb.zm.Vector; -import schule.ngb.zm.Zeichenmaschine; - -import java.util.LinkedList; - -/** - * Hauptklasse der Simulation. - */ -public class Gravity extends Zeichenmaschine { - - /** - * Liste der beweglichen Objekte in der Welt. - */ - private LinkedList movers = new LinkedList<>(); - - private final Vector gravity = new Vector(0, 6.734); - - /** - * Erstellt die {@link Mover}-Objekte. - */ - public void setup() { - for( int i = 0; i < 4; i++ ) { - //Mover m = new Mover(random(10, width - 10), 30); - Mover m = new Mover(10 + (i*30), 30, 5*(i+1)); - movers.add(m); - shapes.add(m); - } - } - - /** - * Aktualisiert die Beschleunigung der {@link Mover}-Objekte durch Anwenden - * der einwirkenden Kräfte und aktualisiert dann die Position entsprechend - * der Beschleunigung. - *

- * Die Position des ersten {@link Attractor} wird auf die Mausposition - * gesetzt. - * - * @param delta - */ - public void update( double delta ) { - // Kräfte anwenden - for( Mover m : movers ) { - if( m.isActive() ) { - m.applyForce(gravity); - } - } - - // Position aktualisieren - for( Mover m : movers ) { - if( m.isActive() ) { - m.update(delta); - - // Abprallen Boden - if( m.getY() >= height ) { - m.setY(height-1); - - m.getVelocity().y *= -1; - double s = 9.0 / m.getMass(); - m.getVelocity().scale(limit(s, 0.0, 1.0)); - } - // Abprallen rechter Rand - if( m.getX() >= width ) { - m.setX(width-1); - m.getVelocity().x *= -1; - } - // Abprallen linker Rand - if( m.getX() <= 0) { - m.setX(1); - m.getVelocity().x *= -1; - } - } - } - } - - /** - * Setzt die Position und Beschleunigung aller {@link Mover}-Objekte in der - * Welt zurück. - */ - public void mouseClicked() { - background.setColor(randomNiceColor()); - for( Mover m : movers ) { - m.moveTo(random(10, width - 10), 30); - m.setVelocity(mouseX-m.getX(), mouseY-m.getY()); - m.getVelocity().setLength(4.0); - } - } - - public static void main( String[] args ) { - new Gravity(); - } - -} diff --git a/examples/zm_forces/Mover.java b/examples/zm_forces/Mover.java deleted file mode 100644 index 4299c42..0000000 --- a/examples/zm_forces/Mover.java +++ /dev/null @@ -1,186 +0,0 @@ -import schule.ngb.zm.Updatable; -import schule.ngb.zm.Vector; -import schule.ngb.zm.shapes.Arrow; -import schule.ngb.zm.shapes.Circle; - -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; - -/** - * Ein bewegliches Objekt in der Simulation. - * - * Mover werden durch Kräfte beeinflusst. Diese Kräfte können von - * unterschiedlichen Quellen ausgehen. Die {@link Attractor}en wirken zum - * Beispiel mit ihrer Anziehungskraft auf die Mover ein. - */ -public class Mover extends Circle implements Updatable { - - /** - * Größe der Objekte in der Simulation. - */ - public static final int SIZE = 10; - - /** - * Ob die momentane Geschwindigkeit der Objekte als Pfeil dargestellt werden soll. - */ - public static final boolean SHOW_VELOCITY = true; - - - /** - * Masse des Objektes. - */ - protected double mass = 10.0; - - /** - * Ob dieses Objekt an der Simulation beteiligt ist. - */ - protected boolean active = true; - - /** - * Momentane Geschwindigkeit des Objektes. - */ - private Vector velocity; - - /** - * Momentane Beschleunigung des Objektes. - */ - private Vector acceleration = new Vector(); - - /** - * Erstellt einen Mover an der angegebenen Position mit der momentanen - * Geschwindigkeit (0, 0). - * - * @param pX x-Position des Objektes. - * @param pY y-Position des Objektes. - */ - public Mover( int pX, int pY ) { - this(pX, pY, new Vector()); - } - - - /** - * Erstellt einen Attractor an der angegebenen Position mit der angegebenen - * Masse. - * - * @param pX x-Koordinate des Objektes. - * @param pY y-Koordinate des Objektes. - * @param pMass Masse des Objektes. - */ - public Mover( int pX, int pY, int pMass ) { - this(pX, pY, pMass, new Vector()); - } - - /** - * Erstellt einen Mover an der angegebenen Position mit der angegebenen - * Initialgeschwindigkeit. - * - * @param pX x-Position des Objektes. - * @param pY y-Position des Objektes. - * @param pVelocity Momentane Geschwindigkeit des Objektes. - */ - public Mover( int pX, int pY, Vector pVelocity ) { - super(pX, pY, SIZE); - velocity = pVelocity.copy(); - } - - /** - * Erstellt einen Attractor an der angegebenen Position - * - * @param pX x-Koordinate des Objektes. - * @param pY y-Koordinate des Objektes. - * @param pMass Masse des Objektes. - * @param pVelocity Initialgeschwindigkeit des Objektes. - */ - public Mover( int pX, int pY, int pMass, Vector pVelocity ) { - super(pX, pY, SIZE); - mass = pMass; - velocity = pVelocity.copy(); - } - - /** - * Gibt die Masse des Objektes zurück. - * - * @return Die Masse des Objektes. - */ - public double getMass() { - return mass; - } - - /** - * Gibt die momentane Geschwindigkeit zurück. - * @return Die momentane Geschwindigkeit als Vektor. - */ - public Vector getVelocity() { - return velocity; - } - - /** - * Setzt die momentane Geschwindigkeit des Objektes. - * - * @param pDx Momentane Geschwindigkeit in x-Richtung. - * @param pDy Momentane Geschwindigkeit in y-Richtung. - */ - public void setVelocity( double pDx, double pDy ) { - velocity.set(pDx, pDy); - } - - /** - * Addiert eine Kraft zur momentanen Beschleunigung des Objektes. Die - * Beschleunigung wird einmal pro Update auf die Geschwindigkeit angewandt - * und dann wieder auf (0, 0) gesetzt. - * - * @param force Ein Vektor, der die Kraft darstellt. - */ - public void applyForce( Vector force ) { - acceleration.add(force); - } - - /** - * Ob dieses Objekt aktiv ist und Updates erhalten soll. - * - * @return Ob das Objekt simuliert wird. - */ - public boolean isActive() { - return active; - } - - /** - * Aktiviert / Deaktiviert das Objekt. - * @param pActive - */ - public void setActive( boolean pActive ) { - this.active = pActive; - } - - /** - * Aktualisiert die momentante Geschwindigkeit und Position des Objektes und - * setzt die Beschleunigung zurück. - * - * @param delta Zeitintervall seit dem letzten Aufruf (in Sekunden). - */ - public void update( double delta ) { - acceleration.scale(delta); - velocity.add(acceleration); - acceleration.scale(0.0); - - if( velocity.length() > 0.5 ) { - this.x += velocity.x; - this.y += velocity.y; - } - } - - @Override - public void draw( Graphics2D graphics, AffineTransform transform ) { - if( SHOW_VELOCITY ) { - Vector v = velocity.copy().scale(10); - Arrow ar = new Arrow(v); - - AffineTransform af = new AffineTransform(transform); - af.translate(radius, radius); - ar.draw(graphics, af); - } - - super.draw(graphics, transform); - } - -} diff --git a/examples/zm_forces/package.bluej b/examples/zm_forces/package.bluej deleted file mode 100644 index 667b354..0000000 --- a/examples/zm_forces/package.bluej +++ /dev/null @@ -1,55 +0,0 @@ -#BlueJ package file -dependency1.from=Attractor -dependency1.to=Gravity -dependency1.type=UsesDependency -dependency2.from=Gravity -dependency2.to=Mover -dependency2.type=UsesDependency -dependency3.from=Gravity -dependency3.to=Attractor -dependency3.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=3 -package.numTargets=3 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=Mover -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=380 -target1.y=220 -target2.height=70 -target2.name=Attractor -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=380 -target2.y=350 -target3.height=70 -target3.name=Gravity -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=120 -target3.y=120 diff --git a/examples/zm_formen/Dreieck.java b/examples/zm_formen/Dreieck.java deleted file mode 100644 index a14789a..0000000 --- a/examples/zm_formen/Dreieck.java +++ /dev/null @@ -1,33 +0,0 @@ - -/** - * Beschreiben Sie hier die Klasse Dreieck. - * - * @author (Ihr Name) - * @version (eine Versionsnummer oder ein Datum) - */ -public class Dreieck -{ - // Instanzvariablen - ersetzen Sie das folgende Beispiel mit Ihren Variablen - private int x; - - /** - * Konstruktor für Objekte der Klasse Dreieck - */ - public Dreieck() - { - // Instanzvariable initialisieren - x = 0; - } - - /** - * Ein Beispiel einer Methode - ersetzen Sie diesen Kommentar mit Ihrem eigenen - * - * @param y ein Beispielparameter für eine Methode - * @return die Summe aus x und y - */ - public int beispielMethode(int y) - { - // tragen Sie hier den Code ein - return x + y; - } -} diff --git a/examples/zm_formen/Kreis.java b/examples/zm_formen/Kreis.java deleted file mode 100644 index c3559c3..0000000 --- a/examples/zm_formen/Kreis.java +++ /dev/null @@ -1,88 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Drawable; - -import java.awt.Graphics2D; - -public class Kreis implements Drawable { - - private int x; - - private int y; - - private int radius; - - private Color farbe; - - private Color linienfarbe; - - private boolean sichtbar; - - public Kreis() { - x = 420; - y = 420; - - radius = 80; - - farbe = Color.BLUE; - linienfarbe = Color.BLACK; - - sichtbar = true; - } - - public void anzeigen() { - sichtbar = true; - } - - public void verstecken() { - sichtbar = false; - } - - public void setPosition( int pX, int pY ) { - x = pX; - y = pY; - } - - public void links() { - x -= 10; - } - - public void rechts() { - x += 10; - } - - public void hoch() { - y -= 10; - } - - public void runter() { - y += 10; - } - - public void setRadius( int pRadius ) { - radius = pRadius; - } - - public void setFarbe( String pFarbe ) { - farbe = Color.parseString(pFarbe); - } - - public void setLinienfarbe( String pLinienfarbe ) { - linienfarbe = Color.parseString(pLinienfarbe); - } - - @Override - public boolean isVisible() { - return sichtbar; - } - - @Override - public void draw( Graphics2D graphics ) { - if( x-(radius/2.0) > 0 && x+(radius/2.0) < 800 && y-(radius/2.0) > 0 && y+(radius/2.0) < 800 ) { - graphics.setColor(farbe.getJavaColor()); - graphics.fillOval(x, y, radius, radius); - graphics.setColor(linienfarbe.getJavaColor()); - graphics.drawOval(x, y, radius, radius); - } - } - -} diff --git a/examples/zm_formen/Leinwand.java b/examples/zm_formen/Leinwand.java deleted file mode 100644 index 2e51cec..0000000 --- a/examples/zm_formen/Leinwand.java +++ /dev/null @@ -1,29 +0,0 @@ -import schule.ngb.zm.Drawable; -import schule.ngb.zm.DrawableLayer; -import schule.ngb.zm.Zeichenmaschine; - -public class Leinwand { - - private final Zeichenmaschine zm; - - private final DrawableLayer zeichenflaeche; - - public Leinwand() { - zm = new Zeichenmaschine(800, 800, "ZM: Shapes", false); - zeichenflaeche = new DrawableLayer(); - zm.addLayer(zeichenflaeche); - } - - public void anzeigen( Drawable pForm ) { - zeichenflaeche.add(pForm); - } - - public void beenden() { - zm.exit(); - } - - public void zeichnen() { - zm.redraw(); - } - -} diff --git a/examples/zm_formen/Rechteck.java b/examples/zm_formen/Rechteck.java deleted file mode 100644 index 985a60d..0000000 --- a/examples/zm_formen/Rechteck.java +++ /dev/null @@ -1,95 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Drawable; - -import java.awt.Graphics2D; - -public class Rechteck implements Drawable { - - private int x; - - private int y; - - private int breite; - - private int hoehe; - - private Color farbe; - - private Color linienfarbe; - - private boolean sichtbar; - - public Rechteck() { - x = 320; - y = 240; - - breite = 100; - hoehe = 60; - - farbe = Color.RED; - linienfarbe = Color.BLACK; - - sichtbar = true; - } - - public void anzeigen() { - sichtbar = true; - } - - public void verstecken() { - sichtbar = false; - } - - public void setPosition( int pX, int pY ) { - x = pX; - y = pY; - } - - public void links() { - x -= 10; - } - - public void rechts() { - x += 10; - } - - public void hoch() { - y -= 10; - } - - public void runter() { - y += 10; - } - - public void setBreite( int pBreite ) { - breite = pBreite; - } - - public void setHoehe( int pHoehe ) { - hoehe = pHoehe; - } - - public void setFarbe( String pFarbe ) { - farbe = Color.parseString(pFarbe); - } - - public void setLinienfarbe( String pLinienfarbe ) { - linienfarbe = Color.parseString(pLinienfarbe); - } - - @Override - public boolean isVisible() { - return sichtbar; - } - - @Override - public void draw( Graphics2D graphics ) { - if( x-(breite/2.0) > 0 && x+(breite/2.0) < 800 && y-(hoehe/2.0) > 0 && y+(hoehe/2.0) < 800 ) { - graphics.setColor(farbe.getJavaColor()); - graphics.fillRect(x, y, breite, hoehe); - graphics.setColor(linienfarbe.getJavaColor()); - graphics.drawRect(x, y, breite, hoehe); - } - } - -} diff --git a/examples/zm_formen/package.bluej b/examples/zm_formen/package.bluej deleted file mode 100644 index f929cf9..0000000 --- a/examples/zm_formen/package.bluej +++ /dev/null @@ -1,53 +0,0 @@ -#BlueJ package file -editor.fx.0.height=728 -editor.fx.0.width=800 -editor.fx.0.x=488 -editor.fx.0.y=107 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=36 -package.editor.y=155 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=0 -package.numTargets=4 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=30 -target1.name=Rechteck -target1.showInterface=false -target1.type=ClassTarget -target1.width=70 -target1.x=190 -target1.y=50 -target2.height=30 -target2.name=Dreieck -target2.showInterface=false -target2.type=ClassTarget -target2.width=70 -target2.x=190 -target2.y=170 -target3.height=30 -target3.name=Kreis -target3.showInterface=false -target3.type=ClassTarget -target3.width=70 -target3.x=190 -target3.y=110 -target4.height=60 -target4.name=Leinwand -target4.showInterface=false -target4.type=ClassTarget -target4.width=120 -target4.x=40 -target4.y=100 diff --git a/examples/zm_gallery/Gallery.java b/examples/zm_gallery/Gallery.java deleted file mode 100644 index 3d98043..0000000 --- a/examples/zm_gallery/Gallery.java +++ /dev/null @@ -1,257 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Zeichenmaschine; - -/** - * Eine Bildergallerie von Bildern eines Informatikkurses des 10. Jahrgangs. - */ -public class Gallery extends Zeichenmaschine { - - public static void main(String[] args) { - new Gallery(); - } - - public void setup() { - setSize(600, 600); - setTitle("ZM: Gallery"); - } - - /** - * Wählt ein zufälliges Bild aus und zeigt es an. - */ - public void draw() { - drawRandom(); - } - - /** - * Zeigt ein zufälliges Bild an. - */ - public void drawRandom() { - switch (random(0, 4)) { - case 0: - rainbow(); - break; - case 1: - snowman(); - break; - case 2: - deathstar(); - break; - case 3: - prideflag(); - break; - } - } - - /** - * Bei Betätigen der Leertaste ein Zufallsbild anzeigen. - */ - public void keyPressed() { - if (keyCode == 32) { - drawRandom(); - } - } - - /** - * Startet eine fortlaufende Präsentation aller Bilder. - * - * @param pWartezeit Die Wartezeit zum nächsten Bildwechsel in Millisekunden. - */ - public void slideshow(int pWartezeit) { - int i = 0; - while (true) { - switch (i) { - case 0: - rainbow(); - break; - case 1: - snowman(); - break; - case 2: - deathstar(); - break; - case 3: - prideflag(); - break; - } - - i = (i + 1) % 4; - delay(pWartezeit); - } - } - - /** - * Regenbogen - *

- * von - */ - public void rainbow() { - // Blauer Hintergrund - drawing.clear(60, 155, 217); - - // Einige Werte berechnen, um Bild an Bildgröße anzupassen - double size = (width + height) * 0.03333; - double r = width / 2.0; - - // Kleiner werdende Kreise in den Farben des Bogens zeichnen - drawing.noStroke(); - drawing.setFillColor(207, 71, 67); - drawing.circle(width / 2.0, height, r - 1 * size); - drawing.setFillColor(235, 134, 42); - drawing.circle(width / 2.0, height, r - 2 * size); - drawing.setFillColor(234, 181, 58); - drawing.circle(width / 2.0, height, r - 3 * size); - // Mitte mit "himmelfarbe" übermalen, um Ringe zu erzeugen - drawing.setFillColor(60, 155, 217); - drawing.circle(width / 2.0, height, r - 4 * size); - - // Sonne zeichnen - drawing.setFillColor(232, 200, 52); - drawing.circle(width * 0.8333, size * 2.6666, size * 2); - - // Bild auf Leinwand übertragen - redraw(); - } - - /** - * LGBTQ-Flagge - *

- * von - */ - public void prideflag() { - // Schwarzer Hintergrund - drawing.clear(0); - drawing.setStrokeColor(0); - - // Farben der Streifen festlegen - Color[] colors = new Color[] { - RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE - }; - - // Breite der Streifen an Bildgröße anpassen - double size = height * 0.06666; - double borderX = width / 6.0, borderY = height / 6.0; - - // Flaggenstab zeichnen - drawing.setFillColor(BROWN); - drawing.rect(borderX, borderY, size, height - 2 * borderY, NORTHWEST); - // Streifen zeichnen - for (int i = 0; i < colors.length; i++) { - drawing.setFillColor(colors[i]); - drawing.rect(borderX, borderY + i * size, width - 2 * borderX, size, NORTHWEST); - } - - // Bild auf Leinwand übertragen - redraw(); - } - - /** - * Todesstern - *

- * von Niklas, Henry und Ben - */ - public void deathstar() { - // Sternenhimmel zeichnen - drawing.clear(0); - drawing.noStroke(); - drawing.setFillColor(WHITE); - - // Zufällig erzeugte Sterne - for (int i = 0; i < (width + height) / 4; i++) { - drawing.circle(random(0.0, width), random(0.0, height), random(1, 3)); - } - - // Einige Werte berechnen, die später verwendet werden, um - // die Zeichnung an die Bildschirmgröße anzupassen. - double radius = limit(width * 0.6666, 0.0, height * 0.6666) / 2.0; - double w2 = width / 2.0, h2 = height / 2.0; - double r2 = radius / 2.0, - r3 = radius / 3.0, r4 = radius / 4.0, - r5 = radius / 5.0, r10 = radius / 10.0; - - // Korpus zeichnen - drawing.setFillColor(128); - drawing.circle(width / 2.0, height / 2.0, radius); - // Graben am Äquator zeichnen - drawing.setStrokeColor(0); - drawing.setStrokeWeight(1.4); - drawing.arc(width / 2.0, height / 2.0, radius + radius, r2, 180, 360); - - // Schüssel zeichnen - drawing.setFillColor(96); - drawing.circle(w2 + r2, h2 - r3, r4); - drawing.setFillColor(84); - drawing.circle(w2 + r2 + 2, h2 - r3 - 2, r10); - - // Strahlen des Lasers zeichnen - int beams = 8; // Anzahl Strahlen - for (int i = 0; i < beams; i++) { - drawing.setStrokeColor(GREEN); - drawing.setStrokeType(SOLID); - drawing.setStrokeWeight(1.6); - drawing.line( - w2 + r2 - r4 * cos(radians(360 / beams * i)), - h2 - r3 + r4 * sin(radians(360 / beams * i)), - w2 + r2 + 2 + r5, - h2 - r3 - 2 - r5); - - drawing.setStrokeType(DASHED); - drawing.setStrokeWeight(3.0); - drawing.line( - w2 + r2 - r4 * cos(radians(360 / beams * i)), - h2 - r3 + r4 * sin(radians(360 / beams * i)), - w2 + r2 + 2 + r5, - h2 - r3 - 2 - r5); - - drawing.setStrokeColor(WHITE); - drawing.setStrokeType(SOLID); - drawing.setStrokeWeight(1.0); - drawing.line( - w2 + r2 - r4 * cos(radians(360 / beams * i)), - h2 - r3 + r4 * sin(radians(360 / beams * i)), - w2 + r2 + 2 + r5, - h2 - r3 - 2 - r5); - } - - // Hauptstrahl - drawing.setStrokeColor(GREEN); - drawing.setStrokeType(SOLID); - drawing.setStrokeWeight(4); - drawing.line(w2 + r2 + 2 + r5, h2 - r3 - 2 - r5, width, h2 - w2 + radius / 6.0); - drawing.setStrokeWeight(1); - drawing.setStrokeColor(255); - drawing.line(w2 + r2 + 2 + r5, h2 - r3 - 2 - r5, width, h2 - w2 + radius / 6.0); - - // Bild auf Leinwand übertragen - redraw(); - } - - /** - * Schneemann - *

- * von - */ - public void snowman() { - // Hellblauer Hintergrund - drawing.clear(219, 253, 255); - drawing.noStroke(); - drawing.setFillColor(255); - - // Drei Schneekugeln - drawing.circle(width / 2.0, height - 100, 100); - drawing.circle(width / 2.0, height - 180 - 60, 60); - drawing.circle(width / 2.0, height - 180 - 110 - 40, 40); - - // Zwei Augen - drawing.setFillColor(0); - drawing.circle(width / 2.0 - 15, height - 345, 4); - drawing.circle(width / 2.0 + 15, height - 345, 4); - - // Nase - drawing.setFillColor(255, 147, 0); - drawing.triangle(width / 2.0, height - 335, width / 2.0 - 5, height - 330, width / 2.0 + 5, height - 330); - - // Bild auf Leinwand übertragen - redraw(); - } - -} diff --git a/examples/zm_gallery/README.TXT b/examples/zm_gallery/README.TXT deleted file mode 100644 index faaa609..0000000 --- a/examples/zm_gallery/README.TXT +++ /dev/null @@ -1,22 +0,0 @@ -# Zeichenmaschine: Gallery - -Die Bildergallerie ist eine Sammlung statischer Bilder, die mit der -Zeichenmaschine programmiert wurden. - -Die Bilder selbst wurden von einem Informatikkurs des Jahrgangs 10 in -Processing (https://processing.org) erstellt und für dieses Beispielprojekt -auf die Zeichenmaschine angespasst. - -Die Autoren der Bilder sind: -- Schneemann: -- Todesstern -- Regenbogen: -- LGBTQ-Flagge: - -## Verwendung - -Beim Start wird eines der vorhandenen Bilder ausgewählt und angezeigt. - -Mit der Methode "slideshow()" wird eine fortlaufende Präsentation der Bilder -gestartet. Die Wartezeit bis zum nächsten Bildwechsel kann als Parameter in -Millisekunden angegeben werden. diff --git a/examples/zm_gallery/package.bluej b/examples/zm_gallery/package.bluej deleted file mode 100644 index 8b02d61..0000000 --- a/examples/zm_gallery/package.bluej +++ /dev/null @@ -1,32 +0,0 @@ -#BlueJ package file -editor.fx.0.height=0 -editor.fx.0.width=0 -editor.fx.0.x=0 -editor.fx.0.y=0 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=0 -package.editor.y=0 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=0 -package.numTargets=1 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=Gallery -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=70 -target1.y=10 diff --git a/examples/zm_generative/BouncingLine.java b/examples/zm_generative/BouncingLine.java deleted file mode 100644 index 39946dd..0000000 --- a/examples/zm_generative/BouncingLine.java +++ /dev/null @@ -1,77 +0,0 @@ -import schule.ngb.zm.Vector; -import schule.ngb.zm.Zeichenmaschine; - -public class BouncingLine extends Zeichenmaschine { - - public static void main( String[] args ) { - new BouncingLine(); - } - - private Vector start, end, dStart, dEnd; - - private int r, g, b; - - private boolean mouseFollow = false; - - public BouncingLine() { - super(800, 800, "Bouncing Lines"); - } - - @Override - public void setup() { - start = Vector.random(0, width, 0, height); - end = Vector.random(0, width, 0, height); - dStart = Vector.random(-5, 5); - dEnd = Vector.random(-5, 5); - - r = random(255); - g = random(255); - b = random(255); - } - - @Override - public void update( double delta ) { - start.add(dStart); - end.add(dEnd); - - if( start.x > width || start.x < 0 ) { - dStart.x *= -1; - } - if( end.x > width || end.x < 0 ) { - dEnd.x *= -1; - } - if( start.y > width || start.y < 0 ) { - dStart.y *= -1; - } - if( end.y > width || end.y < 0 ) { - dEnd.y *= -1; - } - - r += limit(random(-5, 5), 0, 255); - g += limit(random(-5, 5), 0, 255); - b += limit(random(-5, 5), 0, 255); - } - - @Override - public void draw() { - drawing.setStrokeColor(r, g, b); - drawing.line(start.x, start.y, end.x, end.y); - // bezier(0, 0, startX, startY, endX, endY, width, height); - } - - @Override - public void mouseClicked() { - mouseFollow = !mouseFollow; - } - - @Override - public void mouseMoved() { - if( mouseFollow ) { - dStart = new Vector(mouseX - start.x, mouseY - start.y); - dStart.setLength(5); - dEnd = new Vector(mouseX - end.x, mouseY - end.y); - dEnd.setLength(5); - } - } - -} diff --git a/examples/zm_generative/GenColors.java b/examples/zm_generative/GenColors.java deleted file mode 100644 index 3b478d1..0000000 --- a/examples/zm_generative/GenColors.java +++ /dev/null @@ -1,40 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Zeichenmaschine; - -public class GenColors extends Zeichenmaschine { - - public static void main( String[] args ) { - new GenColors(); - } - - public GenColors() { - super(800, 800, "Nice Colors"); - } - - @Override - public void setup() { - genPattern(); - } - - public void genPattern() { - drawing.noStroke(); - - int SIZE = 40; - for( int i = 0; i < width/SIZE; i++ ) { - for( int j = 0; j < height/SIZE; j++ ) { - Color c = randomNiceColor(); - float[] hsl = Color.RGBtoHSL(c.getRGBA(), null); - System.out.printf("%f, %f, %f\n", hsl[0], hsl[1], hsl[2]); - drawing.setFillColor(c); - drawing.square(i*SIZE, j*SIZE, SIZE, NORTHWEST); - } - } - } - - @Override - public void mouseClicked() { - genPattern(); - redraw(); - } - -} diff --git a/examples/zm_generative/GenHitomezashi.java b/examples/zm_generative/GenHitomezashi.java deleted file mode 100644 index f671637..0000000 --- a/examples/zm_generative/GenHitomezashi.java +++ /dev/null @@ -1,122 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Constants; -import schule.ngb.zm.Options; -import schule.ngb.zm.Zeichenmaschine; - -public class GenHitomezashi extends Zeichenmaschine { - - public static final int TILE_SIZE = 20; - - public static final int COLUMNS = 30; - - public static final int ROWS = 30; - - public static final double P = 0.5; - - public static final Color[] colors = new Color[]{ - randomNiceColor(), - randomNiceColor(), - color(241, 124, 55), - color(62, 156, 191) - }; - - private int[] stitchesX, stitchesY; - - public static void main( String[] args ) { - new GenHitomezashi(); - } - - - public GenHitomezashi() { - super(TILE_SIZE*COLUMNS, TILE_SIZE*ROWS, "Hitomezashi Pattern"); - } - - @Override - public void setup() { - drawing.setStrokeColor(0); - drawing.setFillColor(0); - - generatePattern(); - - pause(); - } - - public void generatePattern() { - stitchesX = new int[COLUMNS]; - for ( int i = 0; i < COLUMNS; i++ ) { - stitchesX[i] = random(0, 1) < P ? 1 : 0; - } - stitchesY = new int[COLUMNS]; - for ( int j = 0; j < ROWS; j++ ) { - stitchesY[j] = random(0, 1) < P ? 1 : 0; - } - } - - public boolean hasStitch( int i, int j, Options.Direction dir ) { - switch( dir ) { - case UP: - return (j > 0 && (stitchesY[j-1]+i)%2 == 1); - case DOWN: - return (j >= 0 && (stitchesY[j]+i)%2 == 1); - case LEFT: - return (i > 0 && (stitchesX[i-1]+j)%2 == 1); - case RIGHT: - return (i >= 0 && (stitchesX[i]+j)%2 == 1); - default: - return false; - } - } - - @Override - public void update( double delta ) { - - } - - @Override - public void draw() { - drawing.clear(200); - - int clr = random(1), clr2 = clr; - drawing.noStroke(); - for ( int i = 0; i < COLUMNS; i++ ) { - if ( hasStitch(i, 0, LEFT) ) { - clr = (clr2+1)%2; - clr2 = clr; - } else { - clr = clr2; - } - - for ( int j = 0; j < ROWS; j++ ) { - drawing.setFillColor(colors[clr]); - drawing.rect(i*TILE_SIZE, j*TILE_SIZE, TILE_SIZE, TILE_SIZE, NORTHWEST); - - if ( hasStitch(i, j, DOWN) ) { - clr = (clr+1)%2; - } - } - } - - drawing.setStrokeColor(0); - drawing.setStrokeWeight(2); - for ( int i = 0; i < COLUMNS; i++ ) { - for ( int j = 0; j < ROWS; j++ ) { - boolean stitchX = (stitchesX[i]+j)%2 == 1; - if ( i < COLUMNS-1 && stitchX ) { - drawing.line((i+1)*TILE_SIZE, j*TILE_SIZE, (i+1)*TILE_SIZE, (j+1)*TILE_SIZE); - } - - boolean stitchY = (stitchesY[j]+i)%2 == 1; - if ( j < ROWS-1 && stitchY ) { - drawing.line(i*TILE_SIZE, (j+1)*TILE_SIZE, (i+1)*TILE_SIZE, (j+1)*TILE_SIZE); - } - } - } - } - - @Override - public void mousePressed() { - generatePattern(); - redraw(); - } - -} diff --git a/examples/zm_generative/GenLines.java b/examples/zm_generative/GenLines.java deleted file mode 100644 index 2e57076..0000000 --- a/examples/zm_generative/GenLines.java +++ /dev/null @@ -1,62 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Zeichenmaschine; - -public class GenLines extends Zeichenmaschine { - - public static final int BORDER = 16; - - public static final int SIZE = 400; - - public static Color[] colors = new Color[]{ - color(62, 156, 191), - color(167, 236, 242), - color(242, 196, 61), - color(241, 124, 55), - color(242, 109, 80) - }; - - - public static void main( String[] args ) { - new GenLines(); - } - - - private int i = 0; - - public GenLines() { - super(SIZE, SIZE, "Lines"); - } - - @Override - public void setup() { - setFramesPerSecond(10); - drawing.clear(33); - } - - @Override - public void update( double delta ) { - } - - @Override - public void draw() { - int a = random(BORDER, SIZE-BORDER); - - //drawing.setStrokeColor(random(50, 200)); - drawing.setStrokeColor(colors[random(colors.length-1)]); - drawing.setStrokeWeight(random(4,12)); - - int d; - if( a > SIZE*0.5 ) { - d = random(1, (SIZE - a) - BORDER); - } else { - d = random(1, a - BORDER); - } - drawing.line(a - d, a + d, a + d, a - d); - - i += 1; - if( i == SIZE ) { - stop(); - } - } - -} diff --git a/examples/zm_generative/GenSpiral.java b/examples/zm_generative/GenSpiral.java deleted file mode 100644 index e8486ce..0000000 --- a/examples/zm_generative/GenSpiral.java +++ /dev/null @@ -1,60 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Zeichenmaschine; - -public class GenSpiral extends Zeichenmaschine { - - public static final int SIZE = 800; - - public static final double D_SCALE = .33; - public static final double A_SCALE = 6.0; - - public static Color[] colors = new Color[]{ - color(62, 156, 191, 44), - color(167, 236, 242, 44), - color(242, 196, 61, 44), - color(241, 124, 55, 44), - color(242, 109, 80, 44) - }; - - public static void main( String[] args ) { - new GenSpiral(); - } - - - private int i = 0; - - public GenSpiral() { - super(SIZE, SIZE, "Lines"); - } - - @Override - public void setup() { - setFramesPerSecond(60); - drawing.clear(33); - drawing.translate(SIZE/2, SIZE/2); - } - - @Override - public void update( double delta ) { - } - - @Override - public void draw() { - double d = (tick * D_SCALE); // + random(0,3); - double a = radians(tick * tick / A_SCALE); - // a = radians(tick * A_SCALE / D_SCALE); - // a = radians(d * A_SCALE); - - int x = (int)(d * cos(a)); - int y = (int)(d * sin(a)); - - drawing.setFillColor(colors[random(colors.length - 1)]); - drawing.noStroke(); - drawing.circle(x, y, 10); - - if( d > SIZE+SIZE ) { - stop(); - } - } - -} diff --git a/examples/zm_generative/GenWalk.java b/examples/zm_generative/GenWalk.java deleted file mode 100644 index 0828c55..0000000 --- a/examples/zm_generative/GenWalk.java +++ /dev/null @@ -1,43 +0,0 @@ -import schule.ngb.zm.DrawingLayer; -import schule.ngb.zm.Zeichenmaschine; - -public class GenWalk extends Zeichenmaschine { - - public static final int N_WALKERS = 8; - - private Walker[] walkers; - - public static void main( String[] args ) { - new GenWalk(); - } - - public GenWalk() { - super(800, 800, "Randomwalk"); - } - - @Override - public void setup() { - this.walkers = new Walker[N_WALKERS]; - for( int i = 0; i < walkers.length; i++ ) { - this.walkers[i] = new Walker(width/2, height/2, drawing); - } - - drawing.clear(0); - } - - @Override - public void update( double delta ) { - for( Walker walker: walkers ) { - walker.update(); - } - } - - @Override - public void draw() { - drawing.clear(0, 5); - for( Walker walker: walkers ) { - walker.draw(); - } - } - -} diff --git a/examples/zm_generative/README.TXT b/examples/zm_generative/README.TXT deleted file mode 100644 index 4a5e196..0000000 --- a/examples/zm_generative/README.TXT +++ /dev/null @@ -1,12 +0,0 @@ -# Zeichenmaschine: GenLines - -GenLines ist ein kreatives Projekt, bei dem ein Feld von Punkten durch geschickt -gewählte mathematische Formeln animiert wird. - -Die Idee stammt von der Seite http://tixy.land - -Dort finden sich auch weitere Beispiele für interessante Animationen. - -## Verwendung - - diff --git a/examples/zm_generative/Walker.java b/examples/zm_generative/Walker.java deleted file mode 100644 index bd90a71..0000000 --- a/examples/zm_generative/Walker.java +++ /dev/null @@ -1,50 +0,0 @@ -import schule.ngb.zm.*; - -// TODO: Build Interface (like Drawable) that uses DrawingLayer instead of Graphics2D ? - -public class Walker extends Constants { - - public static final int SIZE = 8; - - public static final int STEP = 6; - - private static final Color[] colors = new Color[]{ - Color.parseHexcode("#b13254"), - Color.parseHexcode("#ff5349"), - Color.parseHexcode("#ff7249"), - Color.parseHexcode("#ff9248") - }; - - private int x; - - private int y; - - private Color color; - - private DrawingLayer drawing; - - public Walker(DrawingLayer drawing) { - this(0, 0, drawing); - } - - public Walker( int x, int y, DrawingLayer drawing) { - this.x = x; - this.y = y; - this.color = choice(colors); - this.drawing = drawing; - } - - public void update() { - x += random(-STEP, STEP); - y += random(-STEP, STEP); - x = limit(x, 10, drawing.getWidth() - 10); - y = limit(y, 10, drawing.getHeight() - 10); - } - - public void draw() { - drawing.setFillColor(color); - drawing.noStroke(); - drawing.square(x, y, SIZE, MIDDLE); - } - -} diff --git a/examples/zm_generative/package.bluej b/examples/zm_generative/package.bluej deleted file mode 100644 index 2f9f212..0000000 --- a/examples/zm_generative/package.bluej +++ /dev/null @@ -1,58 +0,0 @@ -#BlueJ package file -dependency1.from=GenLines -dependency1.to=MyTIXY -dependency1.type=UsesDependency -dependency2.from=GenLines -dependency2.to=Dot -dependency2.type=UsesDependency -dependency3.from=MyTIXY -dependency3.to=Dot -dependency3.type=UsesDependency -dependency4.from=Dot -dependency4.to=GenLines -dependency4.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=4 -package.numTargets=3 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=GenLines -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=120 -target1.y=60 -target2.height=70 -target2.name=MyTIXY -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=200 -target2.y=210 -target3.height=70 -target3.name=Dot -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=420 -target3.y=140 diff --git a/examples/zm_raindrops/README.TXT b/examples/zm_raindrops/README.TXT deleted file mode 100644 index 71367fc..0000000 --- a/examples/zm_raindrops/README.TXT +++ /dev/null @@ -1,14 +0,0 @@ -# Zeichenmaschine: Raindrops - -Raindrops ist ein einfaches Spiel, bei dem Regentropfen vom Himmel fallen, -die von der Spieler:in mit einem Eimer aufgefangen werden müssen. - -Das Spiel wird nach und nach schwerer (mehr Tropfen, höheres Tempo), bis die -Spieler:in zu viele Tropfen fallen gelassen hat. - -Diese Variante des Spiels ist mit dem DrawingLayer implementiert. -Eine objektorientierte Version ist im Beispielprojekt zm_raindrops_oop zu finden. - -## Quellen -- raindrop.png by @lagartoflojo (https://openclipart.org/detail/4063/water-drop) -- bucket.png diff --git a/examples/zm_raindrops/Raindrops.java b/examples/zm_raindrops/Raindrops.java deleted file mode 100644 index bef8a47..0000000 --- a/examples/zm_raindrops/Raindrops.java +++ /dev/null @@ -1,183 +0,0 @@ -import schule.ngb.zm.Zeichenmaschine; - -public class Raindrops extends Zeichenmaschine { - - /** - * Zähler für die gefangenen und fallen gelassenen Tropfen. - */ - int catched = 0, dropped = 0; - - /** - * Startgeschwindigkeit, in der die Tropfen fallen. - * - * Die Tropfen fallen pro Frame (also ungefähr 60-mal pro Sekunde). - */ - double drop_speed = 1.0; - - /** - * Objektvariablen für die Positionen der fünf Tropfen. - */ - double x1 = random(10, 390), y1 = 10; - double x2 = random(10, 390), y2 = -60; - double x3 = random(10, 390), y3 = -100; - double x4 = random(10, 390), y4 = -170; - double x5 = random(10, 390), y5 = -230; - - /** - * Wird zu Beginn aufgerufen, um das Spiel zu initialisieren. - */ - public void setup() { - setSize(400, 600); - setTitle("ZM: Raindrops"); - hideCursor(); - - drawing.setFontSize(64); - } - - /** - * Aktualisiert die Position der Tropfen und prüft, ob die Tropfen mit dem - * Eimer oder dem Boden kollidieren. Ist dies der Fall, wird der Tropfen - * wieder "nach oben" befördert und der entsprechende Zähler wird um eins - * erhöht. Anschließend wird geprüft, ob die Geschwindigkeit erhöht werden - * muss, oder ob das Spiel verloren wurde. - */ - public void update( double delta ) { - // Tropfen bewegen - y1 += drop_speed; - y2 += drop_speed; - y3 += drop_speed; - y4 += drop_speed; - y5 += drop_speed; - - // Kollision mit Eimer prüfen (Tropfen 1) - if( x1 > mouseX-20 && x1 < mouseX+20 && y1 > 520 && y1 < 560 ) { - x1 = random(10, 390); - y1 = -10; - catched += 1; - - // Nächster Level? - if( catched > 0 && catched%10 == 0 ) { - drop_speed += 2; - } - } else if( y1 > 560 ) { - x1 = random(10, 390); - y1 = -10; - dropped += 1; - } - // Kollision mit Eimer prüfen (Tropfen 2) - if( x2 > mouseX-20 && x2 < mouseX+20 && y2 > 520 && y2 < 560 ) { - x2 = random(10, 390); - y2 = -10; - catched += 1; - - // Nächster Level? - if( catched > 0 && catched%10 == 0 ) { - drop_speed += 2; - } - } else if( y2 > 560 ) { - x2 = random(10, 390); - y2 = -10; - dropped += 1; - } - // Kollision mit Eimer prüfen (Tropfen 3) - if( x3 > mouseX-20 && x3 < mouseX+20 && y3 > 520 && y3 < 560 ) { - x3 = random(10, 390); - y3 = -10; - catched += 1; - - // Nächster Level? - if( catched > 0 && catched%10 == 0 ) { - drop_speed += 2; - } - } else if( y3 > 560 ) { - x3 = random(10, 390); - y3 = -10; - dropped += 1; - } - // Kollision mit Eimer prüfen (Tropfen 4) - if( x4 > mouseX-20 && x4 < mouseX+20 && y4 > 520 && y4 < 560 ) { - x4 = random(10, 390); - y4 = -10; - catched += 1; - - // Nächster Level? - if( catched > 0 && catched%10 == 0 ) { - drop_speed += 2; - } - } else if( y4 > 560 ) { - x4 = random(10, 390); - y4 = -10; - dropped += 1; - } - // Kollision mit Eimer prüfen (Tropfen 5) - if( x5 > mouseX-20 && x5 < mouseX+20 && y5 > 520 && y5< 560 ) { - x5 = random(10, 390); - y5 = -10; - catched += 1; - - // Nächster Level? - if( catched > 0 && catched%10 == 0 ) { - drop_speed += 2; - } - } else if( y5 > 560 ) { - x5 = random(10, 390); - y5 = -10; - dropped += 1; - } - - // Game over? - if( dropped >= 13 ) { - stop(); // Stoppt das Spiel und ruft teardown() auf - } - } - - /** - * Zeichnet die Spielszene (hintergrund, Eimer, Tropfen, Zähler) - */ - public void draw() { - // Hintergrund blaugrau - drawing.clear(129, 174, 206); - // Boden zeichnen - drawing.setFillColor(0, 144, 81); - drawing.noStroke(); - drawing.rect(0, height - 40, width, 40, NORTHWEST); - - // Eimer zeichnen - drawing.image("bucket.png", mouseX, 540, 0.25); - - // Tropfen zeichnen - drawing.image("raindrop.png", x1, y1, 0.1); - drawing.image("raindrop.png", x2, y2, 0.1); - drawing.image("raindrop.png", x3, y3, 0.1); - drawing.image("raindrop.png", x4, y4, 0.1); - drawing.image("raindrop.png", x5, y5, 0.1); - - // Punktezähler - drawing.setFillColor(0); - drawing.text(""+catched, 10, 10, NORTHWEST); - drawing.text(""+dropped, 390, 10, NORTHEAST); - } - - /** - * Wird nach dem Aufruf von {@code stop()} aufgerufen. - */ - public void teardown() { - // Alles löschen - drawing.clear(129, 174, 206); - - // Text anzeigen (Punkte und Zeit) - drawing.setFillColor(33); - drawing.setFontSize(64); - drawing.text("Game Over!", 200, 300); - drawing.setFontSize(32); - drawing.text("Punkte: " + catched, 200, 364); - drawing.text("Zeit: " + (runtime/1000.0), 200, 396); - - redraw(); - } - - public static void main( String[] args ) { - new Raindrops(); - } - -} diff --git a/examples/zm_raindrops/bucket.png b/examples/zm_raindrops/bucket.png deleted file mode 100644 index 0bdd04a..0000000 Binary files a/examples/zm_raindrops/bucket.png and /dev/null differ diff --git a/examples/zm_raindrops/package.bluej b/examples/zm_raindrops/package.bluej deleted file mode 100644 index 35e079d..0000000 --- a/examples/zm_raindrops/package.bluej +++ /dev/null @@ -1,32 +0,0 @@ -#BlueJ package file -editor.fx.0.height=0 -editor.fx.0.width=0 -editor.fx.0.x=0 -editor.fx.0.y=0 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=0 -package.numTargets=1 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=Raindrops -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=70 -target1.y=10 diff --git a/examples/zm_raindrops/raindrop.png b/examples/zm_raindrops/raindrop.png deleted file mode 100644 index fd5c2fd..0000000 Binary files a/examples/zm_raindrops/raindrop.png and /dev/null differ diff --git a/examples/zm_raindrops_oop/Bucket.java b/examples/zm_raindrops_oop/Bucket.java deleted file mode 100644 index 87d42b1..0000000 --- a/examples/zm_raindrops_oop/Bucket.java +++ /dev/null @@ -1,13 +0,0 @@ -import schule.ngb.zm.shapes.Picture; - -public class Bucket extends Picture { - - public Bucket( int x, int y ) { - super(x, y, "bucket.png"); - } - - public boolean contains( Drop pDrop ) { - return getBounds().contains(pDrop.getX(), pDrop.getY()); - } - -} diff --git a/examples/zm_raindrops_oop/Drop.java b/examples/zm_raindrops_oop/Drop.java deleted file mode 100644 index 86139f4..0000000 --- a/examples/zm_raindrops_oop/Drop.java +++ /dev/null @@ -1,45 +0,0 @@ -import schule.ngb.zm.Updatable; -import schule.ngb.zm.shapes.Picture; - -public class Drop extends Picture implements Updatable { - - public static final double SPEED_PIXELPERSECOND = 100.0; - - public static final double SPEED_INCREASE = 1.2; - - public static final int START_Y = 40; - - - private double speed = SPEED_PIXELPERSECOND; - - private boolean active = false; - - public Drop() { - super(0, 0, "raindrop.png"); - hide(); - } - - public void increaseSpeed() { - this.speed *= SPEED_INCREASE; - } - - public void activate() { - this.active = true; - } - - public void reset() { - moveTo(random(Raindrops.GAME_BORDER, Raindrops.GAME_WIDTH - Raindrops.GAME_BORDER), START_Y); - show(); - } - - @Override - public boolean isActive() { - return active; - } - - @Override - public void update( double delta ) { - y += speed * delta; - } - -} diff --git a/examples/zm_raindrops_oop/README.TXT b/examples/zm_raindrops_oop/README.TXT deleted file mode 100644 index ef5c43f..0000000 --- a/examples/zm_raindrops_oop/README.TXT +++ /dev/null @@ -1,14 +0,0 @@ -# Zeichenmaschine: Raindrops - -Raindrops ist ein einfaches Spiel, bei dem Regentropfen vom Himmel fallen, -die von der Spieler:in mit einem Eimer aufgefangen werden müssen. - -Das Spiel wird nach und nach schwerer (mehr Tropfen, höheres Tempo), bis die -Spieler:in zu viele Tropfen fallen gelassen hat. - -Diese Variante des Spiels ist Objektorientiert mit dem ShapesLayer implementiert. -Eine nicht OOP Version ist im Beispielprojekt zm_raindrops zu finden. - -## Quellen -- raindrop.png by @lagartoflojo (https://openclipart.org/detail/4063/water-drop) -- bucket.png diff --git a/examples/zm_raindrops_oop/Raindrops.java b/examples/zm_raindrops_oop/Raindrops.java deleted file mode 100644 index f98cdaa..0000000 --- a/examples/zm_raindrops_oop/Raindrops.java +++ /dev/null @@ -1,194 +0,0 @@ -import schule.ngb.zm.Zeichenmaschine; -import schule.ngb.zm.shapes.Text; - -import java.awt.Font; - -public class Raindrops extends Zeichenmaschine { - - //@formatter:off - // Einstellungen für das Spiel - - // Breite / Höhe des Spielfensters. - public static final int GAME_WIDTH = 400; - public static final int GAME_HEIGHT = 600; - // Breite des Randes (dort werden keine Tropfen erstellt). - public static final int GAME_BORDER = 10; - - // Anzahl Tropfen zu Beginn des Spiels. - public static final int GAME_START_DROPS = 5; - // Maximale Anzahl Tropfen im Spiel. - public static final int GAME_MAX_DROPS = 12; - // Anzahl gefangenere Tropfen, ab der ein neuer Level anfängt. - public static final int GAME_NEXT_LEVEL_AT = 10; - // Anzahl Tropfen, die fallen gelassen werden dürfen, - // bevor das Spiel vorbei ist. - public static final int GAME_MAX_DROPPED = 13; - //@formatter:on - - public static void main( String[] args ) { - new Raindrops(); - } - - /** - * Array der Tropfen-Objekte. - */ - private Drop[] drops; - - /** - * Das Eimer-Objekt. - */ - private Bucket bucket; - - /** - * Anzahl Tropfen, die aktuell im Spiel sind. - */ - private int noOfDrops = GAME_START_DROPS; - - /** - * Anzahl gefangener / fallen gelassener Tropfen. - */ - private int dropped = 0, catched = 0; - - /** - * Text-Objekte für die Anzeige der gefangenen / fallen gelassenen Tropfen. - */ - private Text textDropped, textCatched; - - public Raindrops() { - super(GAME_WIDTH, GAME_HEIGHT, "ZM: Raindrops"); - } - - /** - * Initialisierung des Spiels und der benötigten Objekte. - */ - public void setup() { - hideCursor(); - - // Hintergrund blaugrau - background.setColor(129, 174, 206); - - // Hintergrundbild zeichnen (DrawingLayer) - drawing.setFillColor(0, 144, 81); - drawing.noStroke(); - drawing.rect(0, height - 40, width, 40, NORTHWEST); - - // Eimer-Objekt erstellen - bucket = new Bucket(width / 2, height - 60); - bucket.scale(.25); - shapes.add(bucket); - - // Tropfen-Objekte erstellen - // Die maximale Anzahl Tropfen kann durch GAME_MAX_DROPS eingestellt werden - // Es werden alle Tropfen erstellt, anber nur die ersten noOfDrops - // Objekt im Spiel angezeigt. - drops = new Drop[GAME_MAX_DROPS]; - for( int i = 0; i < drops.length; i++ ) { - drops[i] = new Drop(); - drops[i].scale(.1); - shapes.add(drops[i]); - } - - // Die ersten noOfDrops Tropfen zu Beginn anzeigen. - // Die Tropfen werden mit zufälliger x-Koordinate etwas versetzt - // übereinander positioniert, damit sie nicht alle gleichzeitig fallen. - for( int i = 0; i < noOfDrops; i++ ) { - drops[i].moveTo( - random(GAME_BORDER, GAME_WIDTH - GAME_BORDER), - Drop.START_Y - i * (2 * Drop.START_Y) - random(-10, 10) - ); - drops[i].activate(); - drops[i].show(); - } - - // Erstellen der Text-Objekte für dei Zähler. - textCatched = new Text(10, 60, "0"); - textCatched.setFontsize(64); - textCatched.setAnchor(NORTHWEST); //linksbündig - shapes.add(textCatched); - textDropped = new Text(width - 10, 60, "0"); - textDropped.setFontsize(64); - textDropped.setAnchor(NORTHEAST); // rechtsbündig - shapes.add(textDropped); - } - - /** - * Hier passiert die Spiel-Logik. Die Position des Eimers und der Tropfen - * wird aktualisiert. Es wird geprüft, ob ein Tropfen mit dem Eimer - * kollidiert oder den Boden erreicht hat. - * - * @param delta - */ - public void update( double delta ) { - // Position des Eimers auf Mausposition setzen. - if( mouseX < 0 ) { - // Stopp am linken Rand - bucket.setX(0); - } else if( mouseX > width ) { - // Stopp am rechten Rand - bucket.setX(width); - } else { - bucket.setX(mouseX); - } - - // Die Position der Tropfen aktualisieren und Kollisionen prüfen. - for( int i = 0; i < noOfDrops; i++ ) { - drops[i].update(delta); - - if( drops[i].getY() >= height - 40 ) { - // Tropfen hat Boden erreicht! - dropped += 1; - textDropped.setText("" + dropped); - drops[i].reset(); - } else if( bucket.contains(drops[i]) ) { - // Tropfen wurde gefangen! - catched += 1; - textCatched.setText("" + catched); - drops[i].reset(); - - // Wurde ein Tropfen gefangen (und nur dann) wird geprüft, ob - // das nächste Level erreicht wurde. - if( catched > 0 && (catched % GAME_NEXT_LEVEL_AT) == 0 ) { - // Falls noch nicht alle Tropfen im Spiel, aktiviere einen - // weiteren. - if( noOfDrops < drops.length ) { - noOfDrops += 1; - drops[noOfDrops - 1].activate(); - drops[noOfDrops - 1].reset(); - } - - // Geschwindigkeit aller Tropfen erhöhen. - for( int j = 0; j < drops.length; j++ ) { - drops[j].increaseSpeed(); - } - } - } - } - - // Prüfen, ob das Spiel verloren wurde. - if( dropped >= GAME_MAX_DROPPED ) { - stop(); // Stoppt den Game-Loop und ruft dann teardown() auf. - } - } - - /** - * Wird aufgerufen, nachdem das Spiel gestoppt wurde und zeigt den Game Over - * Bildschirm an. - */ - public void teardown() { - // Alle Formen (Eimer, Tropfen, Texte) entfernen. - shapes.removeAll(); - - // "Game Over" Text - Text gameOver = new Text(width / 2, 300, "Game Over"); - gameOver.setFontsize(64); - shapes.add(gameOver); - - // Ergebnis anzeigen (Anzahl gefangener Tropfen, Spielzeit in Sekunden). - Font f = new Font(Font.SANS_SERIF, Font.PLAIN, 32); - shapes.add(new Text(width / 2, 360, "Catched: " + catched, f)); - shapes.add(new Text(width / 2, 360 + f.getSize(), "Time: " + (runtime / 1000.0), f)); - - redraw(); - } - -} diff --git a/examples/zm_raindrops_oop/bucket.png b/examples/zm_raindrops_oop/bucket.png deleted file mode 100644 index 0bdd04a..0000000 Binary files a/examples/zm_raindrops_oop/bucket.png and /dev/null differ diff --git a/examples/zm_raindrops_oop/package.bluej b/examples/zm_raindrops_oop/package.bluej deleted file mode 100644 index 2f9f212..0000000 --- a/examples/zm_raindrops_oop/package.bluej +++ /dev/null @@ -1,58 +0,0 @@ -#BlueJ package file -dependency1.from=GenLines -dependency1.to=MyTIXY -dependency1.type=UsesDependency -dependency2.from=GenLines -dependency2.to=Dot -dependency2.type=UsesDependency -dependency3.from=MyTIXY -dependency3.to=Dot -dependency3.type=UsesDependency -dependency4.from=Dot -dependency4.to=GenLines -dependency4.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=4 -package.numTargets=3 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=GenLines -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=120 -target1.y=60 -target2.height=70 -target2.name=MyTIXY -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=200 -target2.y=210 -target3.height=70 -target3.name=Dot -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=420 -target3.y=140 diff --git a/examples/zm_raindrops_oop/raindrop.png b/examples/zm_raindrops_oop/raindrop.png deleted file mode 100644 index fd5c2fd..0000000 Binary files a/examples/zm_raindrops_oop/raindrop.png and /dev/null differ diff --git a/examples/zm_skyline/README.TXT b/examples/zm_skyline/README.TXT deleted file mode 100644 index b3600d4..0000000 --- a/examples/zm_skyline/README.TXT +++ /dev/null @@ -1,10 +0,0 @@ -# Zeichenmaschine: Skyline - -Skyline ist ein Projekt mit der Turtle-Ebene der Zeichenmaschine. Jede -Schüler:in programmiert ein Haus der Skyline. Dazu müssen vorher -gemeinsam Vorgaben festgelegt werden. Wichtig ist, dass die Turtle immer in der -unteren linken Ecke des Hauses startet und in der rechten unteren Ecke endet -(damit dort dann das nächste Haus gezeichnet werden kann). - -Wenn jede Schüler:in ihr Haus in eine eigene Methode schreibt, können diese -nacheinander aufgerufen werden, um die finale Skyline zu erstellen. diff --git a/examples/zm_skyline/Skyline.java b/examples/zm_skyline/Skyline.java deleted file mode 100644 index 32b8a79..0000000 --- a/examples/zm_skyline/Skyline.java +++ /dev/null @@ -1,65 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Zeichenmaschine; -import schule.ngb.zm.turtle.TurtleLayer; - -public class Skyline extends Zeichenmaschine { - - TurtleLayer turtle; - - public void setup() { - setSize(1000, 600); - setTitle("Zeichenmaschine: Skyline"); - - // Turtle-Ebene erstellen - turtle = new TurtleLayer(); - addLayer(turtle); - - // Hintergrund schwarz - background.setColor(BLACK); - turtle.noStroke(); - - turtle.penUp(); - turtle.lt(90); - turtle.fd(300); - turtle.rt(90); - turtle.penDown(); - } - - public void draw() { - for( int i = 0; i < 6; i++ ) { - haus1(randomColor()); - } - turtle.setStrokeColor(BLUE); - } - - public void haus1( Color clr ) { - turtle.setFillColor(clr); - quad(100, 80); - turtle.fill(); - turtle.fd(100); - turtle.rt(45); - turtle.fd(80/sqrt(2)); - turtle.rt(90); - turtle.fd(80/sqrt(2)); - turtle.rt(45); - turtle.fill(); - turtle.fd(100); - turtle.rt(180); - } - - public void quad( int a, int b ) { - turtle.fd(a); - turtle.rt(90); - turtle.fd(b); - turtle.rt(90); - turtle.fd(a); - turtle.rt(90); - turtle.fd(b); - turtle.rt(90); - } - - public static void main( String[] args ) { - new Skyline(); - } - -} diff --git a/examples/zm_skyline/package.bluej b/examples/zm_skyline/package.bluej deleted file mode 100644 index b9734eb..0000000 --- a/examples/zm_skyline/package.bluej +++ /dev/null @@ -1,69 +0,0 @@ -#BlueJ package file -dependency1.from=BluejTest -dependency1.to=ClasspathInspector -dependency1.type=UsesDependency -dependency2.from=Gravity -dependency2.to=Mover -dependency2.type=UsesDependency -dependency3.from=Gravity -dependency3.to=Attractor -dependency3.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=3 -package.numTargets=5 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=Mover -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=320 -target1.y=200 -target2.height=70 -target2.name=BluejTest -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=70 -target2.y=10 -target3.height=70 -target3.name=Attractor -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=390 -target3.y=350 -target4.height=70 -target4.name=ClasspathInspector -target4.showInterface=false -target4.type=ClassTarget -target4.width=130 -target4.x=380 -target4.y=30 -target5.height=70 -target5.name=Gravity -target5.showInterface=false -target5.type=ClassTarget -target5.width=120 -target5.x=100 -target5.y=300 diff --git a/examples/zm_tixy/Dot.java b/examples/zm_tixy/Dot.java deleted file mode 100644 index 7550aaa..0000000 --- a/examples/zm_tixy/Dot.java +++ /dev/null @@ -1,58 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Constants; -import schule.ngb.zm.Drawable; - -import java.awt.Graphics2D; - - -public class Dot implements Drawable { - - public static final Color DOT_RED = new Color(244, 32, 65); - - public static final Color DOT_WHITE = new Color(255); - - public static final int SIZE = TIXY.DOT_SIZE; - - public static final int RADIUS = TIXY.DOT_SIZE / 2; - - public static final int GAP = TIXY.DOT_GAP; - - public final int x, y, i; - - public int size = SIZE; - - public Color color; - - - public Dot( int x, int y, int i ) { - this.x = x; - this.y = y; - this.i = i; - - color = DOT_WHITE; - } - - public void setValue( double value ) { - if( value < 0 ) { - color = DOT_RED; - value = value * -1; - } else { - color = DOT_WHITE; - } - - value = Constants.limit(value, 0.0, 1.0); - size = (int) (SIZE * value); - } - - @Override - public boolean isVisible() { - return true; - } - - @Override - public void draw( Graphics2D graphics ) { - graphics.setColor(color.getJavaColor()); - graphics.fillOval(GAP + x * (SIZE + GAP) + RADIUS - (size / 2), GAP + y * (SIZE + GAP) + RADIUS - (size / 2), size, size); - } - -} diff --git a/examples/zm_tixy/MyTIXY.java b/examples/zm_tixy/MyTIXY.java deleted file mode 100644 index c3ffaa4..0000000 --- a/examples/zm_tixy/MyTIXY.java +++ /dev/null @@ -1,16 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Constants; - -public class MyTIXY extends Constants { - - public double update( double t, int i, int x, int y ) { - return sin(t-sqrt(pow((x-7.5),2)+pow((y-6),2))); - } - - public void update( double t, Dot dot ) { - if( dot.color.equals(Dot.DOT_RED) ) { - dot.color = BLUE; - } - } - -} diff --git a/examples/zm_tixy/README.TXT b/examples/zm_tixy/README.TXT deleted file mode 100644 index 4a5e196..0000000 --- a/examples/zm_tixy/README.TXT +++ /dev/null @@ -1,12 +0,0 @@ -# Zeichenmaschine: GenLines - -GenLines ist ein kreatives Projekt, bei dem ein Feld von Punkten durch geschickt -gewählte mathematische Formeln animiert wird. - -Die Idee stammt von der Seite http://tixy.land - -Dort finden sich auch weitere Beispiele für interessante Animationen. - -## Verwendung - - diff --git a/examples/zm_tixy/TIXY.java b/examples/zm_tixy/TIXY.java deleted file mode 100644 index 7264ab5..0000000 --- a/examples/zm_tixy/TIXY.java +++ /dev/null @@ -1,68 +0,0 @@ -import schule.ngb.zm.DrawableLayer; -import schule.ngb.zm.Zeichenmaschine; - -public class TIXY extends Zeichenmaschine { - - public static final int FIELD_SIZE = 16; - - public static final int DOT_SIZE = 16; - - public static final int DOT_GAP = 4; - - public static void main( String[] args ) { - new TIXY(); - } - - private Dot[][] dots; - - private MyTIXY tixy; - - public TIXY() { - super( - DOT_GAP + FIELD_SIZE * (DOT_SIZE + DOT_GAP), - DOT_GAP + FIELD_SIZE * (DOT_SIZE + DOT_GAP), - "tixy" - ); - } - - @Override - public void setup() { - background.setColor(BLACK); - - addLayer(new DrawableLayer()); - DrawableLayer dl = canvas.getLayer(DrawableLayer.class); - - tixy = new MyTIXY(); - - dots = new Dot[FIELD_SIZE][FIELD_SIZE]; - for( int i = 0; i < FIELD_SIZE; i++ ) { - for( int j = 0; j < FIELD_SIZE; j++ ) { - dots[i][j] = new Dot(i, j, i * FIELD_SIZE + j); - dl.add(dots[i][j]); - } - } - } - - @Override - public void draw() { - } - - @Override - public void update( double delta ) { - for( int i = 0; i < FIELD_SIZE; i++ ) { - for( int j = 0; j < FIELD_SIZE; j++ ) { - Dot d = dots[i][j]; - double value = tixy.update(runtime / 1000.0, d.i, d.x, d.y); - dots[i][j].setValue(value); - - tixy.update(runtime / 1000.0, dots[i][j]); - } - } - } - - @Override - public void mousePressed() { - stop(); - } - -} diff --git a/examples/zm_tixy/package.bluej b/examples/zm_tixy/package.bluej deleted file mode 100644 index 2f9f212..0000000 --- a/examples/zm_tixy/package.bluej +++ /dev/null @@ -1,58 +0,0 @@ -#BlueJ package file -dependency1.from=GenLines -dependency1.to=MyTIXY -dependency1.type=UsesDependency -dependency2.from=GenLines -dependency2.to=Dot -dependency2.type=UsesDependency -dependency3.from=MyTIXY -dependency3.to=Dot -dependency3.type=UsesDependency -dependency4.from=Dot -dependency4.to=GenLines -dependency4.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=4 -package.numTargets=3 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=GenLines -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=120 -target1.y=60 -target2.height=70 -target2.name=MyTIXY -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=200 -target2.y=210 -target3.height=70 -target3.name=Dot -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=420 -target3.y=140 diff --git a/examples/zm_wahlkamp/DatabaseConnector.java b/examples/zm_wahlkamp/DatabaseConnector.java deleted file mode 100755 index 1b0ebda..0000000 --- a/examples/zm_wahlkamp/DatabaseConnector.java +++ /dev/null @@ -1,148 +0,0 @@ -import java.sql.*; - -/** - *

- * Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018 - *

- *

- * Klasse DatabaseConnector - *

- *

- * Ein Objekt der Klasse DatabaseConnector ermoeglicht die Abfrage und Manipulation - * einer SQLite-Datenbank. - * Beim Erzeugen des Objekts wird eine Datenbankverbindung aufgebaut, so dass - * anschließend SQL-Anweisungen an diese Datenbank gerichtet werden koennen. - *

- * - * @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule - * @version 2016-01-24 - */ -public class DatabaseConnector{ - private Connection connection; - private QueryResult currentQueryResult = null; - private String message = null; - - /** - * Ein Objekt vom Typ DatabaseConnector wird erstellt, und eine Verbindung zur Datenbank - * wird aufgebaut. Mit den Parametern pIP und pPort werden die IP-Adresse und die - * Port-Nummer uebergeben, unter denen die Datenbank mit Namen pDatabase zu erreichen ist. - * Mit den Parametern pUsername und pPassword werden Benutzername und Passwort fuer die - * Datenbank uebergeben. - */ - public DatabaseConnector(String pIP, int pPort, String pDatabase, String pUsername, String pPassword){ - //Eine Impementierung dieser Schnittstelle fuer SQLite ignoriert pID und pPort, da die Datenbank immer lokal ist. - //Auch pUsername und pPassword werden nicht verwendet, da SQLite sie nicht unterstuetzt. - try { - //Laden der Treiberklasse - Class.forName("org.sqlite.JDBC"); - - //Verbindung herstellen - connection = DriverManager.getConnection("jdbc:sqlite:"+pDatabase); - - } catch (Exception e) { - message = e.getMessage(); - } - } - - /** - * Der Auftrag schickt den im Parameter pSQLStatement enthaltenen SQL-Befehl an die - * Datenbank ab. - * Handelt es sich bei pSQLStatement um einen SQL-Befehl, der eine Ergebnismenge - * liefert, so kann dieses Ergebnis anschließend mit der Methode getCurrentQueryResult - * abgerufen werden. - */ - public void executeStatement(String pSQLStatement){ - //Altes Ergebnis loeschen - currentQueryResult = null; - message = null; - - try { - //Neues Statement erstellen - Statement statement = connection.createStatement(); - - //SQL Anweisung an die DB schicken. - if (statement.execute(pSQLStatement)) { //Fall 1: Es gibt ein Ergebnis - - //Resultset auslesen - ResultSet resultset = statement.getResultSet(); - - //Spaltenanzahl ermitteln - int columnCount = resultset.getMetaData().getColumnCount(); - - //Spaltennamen und Spaltentypen in Felder uebertragen - String[] resultColumnNames = new String[columnCount]; - String[] resultColumnTypes = new String[columnCount]; - for (int i = 0; i < columnCount; i++){ - resultColumnNames[i] = resultset.getMetaData().getColumnLabel(i+1); - resultColumnTypes[i] = resultset.getMetaData().getColumnTypeName(i+1); - } - - //Queue fuer die Zeilen der Ergebnistabelle erstellen - Queue rows = new Queue(); - - //Daten in Queue uebertragen und Zeilen zaehlen - int rowCount = 0; - while (resultset.next()){ - String[] resultrow = new String[columnCount]; - for (int s = 0; s < columnCount; s++){ - resultrow[s] = resultset.getString(s+1); - } - rows.enqueue(resultrow); - rowCount = rowCount + 1; - } - - //Ergebnisfeld erstellen und Zeilen aus Queue uebertragen - String[][] resultData = new String[rowCount][columnCount]; - int j = 0; - while (!rows.isEmpty()){ - resultData[j] = rows.front(); - rows.dequeue(); - j = j + 1; - } - - //Statement schließen und Ergebnisobjekt erstellen - statement.close(); - currentQueryResult = new QueryResult(resultData, resultColumnNames, resultColumnTypes); - - } else { //Fall 2: Es gibt kein Ergebnis. - //Statement ohne Ergebnisobjekt schliessen - statement.close(); - } - - } catch (Exception e) { - //Fehlermeldung speichern - message = e.getMessage(); - } - } - - /** - * Die Anfrage liefert das Ergebnis des letzten mit der Methode executeStatement an - * die Datenbank geschickten SQL-Befehls als Ob-jekt vom Typ QueryResult zurueck. - * Wurde bisher kein SQL-Befehl abgeschickt oder ergab der letzte Aufruf von - * executeStatement keine Ergebnismenge (z.B. bei einem INSERT-Befehl oder einem - * Syntaxfehler), so wird null geliefert. - */ - public QueryResult getCurrentQueryResult(){ - return currentQueryResult; - } - - /** - * Die Anfrage liefert null oder eine Fehlermeldung, die sich jeweils auf die letzte zuvor ausgefuehrte - * Datenbankoperation bezieht. - */ - public String getErrorMessage(){ - return message; - } - - /** - * Die Datenbankverbindung wird geschlossen. - */ - public void close(){ - try{ - connection.close(); - } catch (Exception e) { - message = e.getMessage(); - } - } - -} diff --git a/examples/zm_wahlkamp/QueryResult.java b/examples/zm_wahlkamp/QueryResult.java deleted file mode 100755 index cb35dc4..0000000 --- a/examples/zm_wahlkamp/QueryResult.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - *

- * Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018 - *

- *

- * Klasse QueryResult - *

- *

- * Ein Objekt der Klasse QueryResult stellt die Ergebnistabelle einer Datenbankanfrage mit Hilfe - * der Klasse DatabaseConnector dar. Objekte dieser Klasse werden nur von der Klasse DatabaseConnector erstellt. - * Die Klasse verfuegt ueber keinen oeffentlichen Konstruktor. - *

- * - * @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule - * @version 2015-01-31 - */ -public class QueryResult{ - private String[][] data; - private String[] columnNames; - private String[] columnTypes; - - /** - * Paketinterner Konstruktor. - */ - QueryResult(String[][] pData, String[] pColumnNames, String[] pColumnTypes){ - data = pData; - columnNames = pColumnNames; - columnTypes = pColumnTypes; - } - - /** - * Die Anfrage liefert die Eintraege der Ergebnistabelle als zweidimensionales Feld - * vom Typ String. Der erste Index des Feldes stellt die Zeile und der zweite die - * Spalte dar (d.h. Object[zeile][spalte]). - */ - public String[][] getData(){ - return data; - } - - /** - * Die Anfrage liefert die Bezeichner der Spalten der Ergebnistabelle als Feld vom - * Typ String zurueck. - */ - public String[] getColumnNames(){ - return columnNames; - } - - /** - * Die Anfrage liefert die Typenbezeichnung der Spalten der Ergebnistabelle als Feld - * vom Typ String zurueck. Die Bezeichnungen entsprechen den Angaben in der MySQL-Datenbank. - */ - public String[] getColumnTypes(){ - return columnTypes; - } - - /** - * Die Anfrage liefert die Anzahl der Zeilen der Ergebnistabelle als Integer. - */ - public int getRowCount(){ - if (data != null ) - return data.length; - else - return 0; - } - - /** - * Die Anfrage liefert die Anzahl der Spalten der Ergebnistabelle als Integer. - */ - public int getColumnCount(){ - if (data != null && data.length > 0 && data[0] != null) - return data[0].length; - else - return 0; - } - -} \ No newline at end of file diff --git a/examples/zm_wahlkamp/Queue.java b/examples/zm_wahlkamp/Queue.java deleted file mode 100755 index 5a6ef1f..0000000 --- a/examples/zm_wahlkamp/Queue.java +++ /dev/null @@ -1,142 +0,0 @@ -/** - *

- * Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018 - *

- *

- * Generische Klasse Queue - *

- *

- * Objekte der generischen Klasse Queue (Warteschlange) verwalten beliebige - * Objekte vom Typ ContentType nach dem First-In-First-Out-Prinzip, d.h., das - * zuerst abgelegte Objekt wird als erstes wieder entnommen. Alle Methoden haben - * eine konstante Laufzeit, unabhaengig von der Anzahl der verwalteten Objekte. - *

- * - * @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule - * @version Generisch_02 2014-02-21 - */ -public class Queue { - - /* --------- Anfang der privaten inneren Klasse -------------- */ - - private class QueueNode { - - private ContentType content = null; - private QueueNode nextNode = null; - - /** - * Ein neues Objekt vom Typ QueueNode wird erschaffen. - * Der Inhalt wird per Parameter gesetzt. Der Verweis ist leer. - * - * @param pContent das Inhaltselement des Knotens vom Typ ContentType - */ - public QueueNode(ContentType pContent) { - content = pContent; - nextNode = null; - } - - /** - * Der Verweis wird auf das Objekt, das als Parameter uebergeben wird, - * gesetzt. - * - * @param pNext der Nachfolger des Knotens - */ - public void setNext(QueueNode pNext) { - nextNode = pNext; - } - - /** - * Liefert das naechste Element des aktuellen Knotens. - * - * @return das Objekt vom Typ QueueNode, auf das der aktuelle Verweis zeigt - */ - public QueueNode getNext() { - return nextNode; - } - - /** - * Liefert das Inhaltsobjekt des Knotens vom Typ ContentType. - * - * @return das Inhaltsobjekt des Knotens - */ - public ContentType getContent() { - return content; - } - - } - - /* ----------- Ende der privaten inneren Klasse -------------- */ - - private QueueNode head; - private QueueNode tail; - - /** - * Eine leere Schlange wird erzeugt. - * Objekte, die in dieser Schlange verwaltet werden, muessen vom Typ - * ContentType sein. - */ - public Queue() { - head = null; - tail = null; - } - - /** - * Die Anfrage liefert den Wert true, wenn die Schlange keine Objekte enthaelt, - * sonst liefert sie den Wert false. - * - * @return true, falls die Schlange leer ist, sonst false - */ - public boolean isEmpty() { - return head == null; - } - - /** - * Das Objekt pContentType wird an die Schlange angehaengt. - * Falls pContentType gleich null ist, bleibt die Schlange unveraendert. - * - * @param pContent - * das anzuhaengende Objekt vom Typ ContentType - */ - public void enqueue(ContentType pContent) { - if (pContent != null) { - QueueNode newNode = new QueueNode(pContent); - if (this.isEmpty()) { - head = newNode; - tail = newNode; - } else { - tail.setNext(newNode); - tail = newNode; - } - } - } - - /** - * Das erste Objekt wird aus der Schlange entfernt. - * Falls die Schlange leer ist, wird sie nicht veraendert. - */ - public void dequeue() { - if (!this.isEmpty()) { - head = head.getNext(); - if (this.isEmpty()) { - head = null; - tail = null; - } - } - } - - /** - * Die Anfrage liefert das erste Objekt der Schlange. - * Die Schlange bleibt unveraendert. - * Falls die Schlange leer ist, wird null zurueckgegeben. - * - * @return das erste Objekt der Schlange vom Typ ContentType oder null, - * falls die Schlange leer ist - */ - public ContentType front() { - if (this.isEmpty()) { - return null; - } else { - return head.getContent(); - } - } -} diff --git a/examples/zm_wahlkamp/Wahlkampf.java b/examples/zm_wahlkamp/Wahlkampf.java deleted file mode 100644 index ad91399..0000000 --- a/examples/zm_wahlkamp/Wahlkampf.java +++ /dev/null @@ -1,75 +0,0 @@ -import schule.ngb.zm.Color; -import schule.ngb.zm.Zeichenmaschine; -import schule.ngb.zm.charts.*; - -public class Wahlkampf extends Zeichenmaschine { - - public static void main( String[] args ) { - new Wahlkampf(); - } - - public Wahlkampf() { - super(800, 800, "Landtagswahl 2022 in Bielefeld"); - } - - public void draw() { - background.setColor(240); - - DatabaseConnector dbc = new DatabaseConnector("", 0, "wahlergebnisse.db", "", ""); - QueryResult res; - - String[] parteien = new String[]{"CDU", "SPD", "FDP", "AfD", "GRÜNE", "DIELINKE", "PIRATEN"}; - Color[] farben = new Color[]{BLACK, RED, YELLOW, BLUE, GREEN, MAGENTA, ORANGE}; - - // Erststimmen im Bezirk - StringBuilder sb = new StringBuilder("SELECT "); - for( int i = 0; i < parteien.length; i++ ) { - sb.append("SUM("); - sb.append('"'); - sb.append('D'); - sb.append(i+1); - sb.append('-'); - sb.append(parteien[i]); - sb.append("Erstimmen"); - sb.append('"'); - sb.append(')'); - if( i < parteien.length-1 ) { - sb.append(','); - } - } - sb.append(" FROM \"2022_Landtagswahl\""); - - dbc.executeStatement(sb.toString()); - res = dbc.getCurrentQueryResult(); - if( res != null ) { - String[] data = res.getData()[0]; - - double[] values = new double[parteien.length]; - for( int i = 0; i < data.length; i++ ) { - values[i] = Double.parseDouble(data[i]); - } - RingChart chart1 = new RingChart(200, 200, 100, parteien.length); - RingChart chart2 = new RingChart(600, 200, 100, parteien.length); - RingChart chart3 = new RingChart(200, 600, 100, parteien.length); - RingChart chart4 = new RingChart(600, 600, 100, parteien.length); - - double s = sum(values); - for( int i = 0; i < values.length; i++ ) { - double value = values[i]; - chart1.setValue(i, value, s, parteien[i], farben[i]); - chart2.setValue(i, value, s, parteien[i], farben[i]); - chart3.setValue(i, value, s, parteien[i], farben[i]); - chart4.setValue(i, value, s, parteien[i], farben[i]); - } - - chart4.setStrokeColor(200); - - shapes.add(chart1, chart2, chart3, chart4); - } else { - System.err.println(dbc.getErrorMessage()); - } - - pause(); - } - -} diff --git a/examples/zm_wahlkamp/package.bluej b/examples/zm_wahlkamp/package.bluej deleted file mode 100644 index 667b354..0000000 --- a/examples/zm_wahlkamp/package.bluej +++ /dev/null @@ -1,55 +0,0 @@ -#BlueJ package file -dependency1.from=Attractor -dependency1.to=Gravity -dependency1.type=UsesDependency -dependency2.from=Gravity -dependency2.to=Mover -dependency2.type=UsesDependency -dependency3.from=Gravity -dependency3.to=Attractor -dependency3.type=UsesDependency -editor.fx.0.height=728 -editor.fx.0.width=1037 -editor.fx.0.x=95 -editor.fx.0.y=53 -objectbench.height=94 -objectbench.width=776 -package.divider.horizontal=0.6 -package.divider.vertical=0.8305369127516778 -package.editor.height=488 -package.editor.width=661 -package.editor.x=374 -package.editor.y=158 -package.frame.height=660 -package.frame.width=800 -package.numDependencies=3 -package.numTargets=3 -package.showExtends=true -package.showUses=true -project.charset=UTF-8 -readme.height=60 -readme.name=@README -readme.width=48 -readme.x=10 -readme.y=10 -target1.height=70 -target1.name=Mover -target1.showInterface=false -target1.type=ClassTarget -target1.width=120 -target1.x=380 -target1.y=220 -target2.height=70 -target2.name=Attractor -target2.showInterface=false -target2.type=ClassTarget -target2.width=120 -target2.x=380 -target2.y=350 -target3.height=70 -target3.name=Gravity -target3.showInterface=false -target3.type=ClassTarget -target3.width=120 -target3.x=120 -target3.y=120 diff --git a/examples/zm_wahlkamp/wahlergebnisse.db b/examples/zm_wahlkamp/wahlergebnisse.db deleted file mode 100644 index 6ad0e9e..0000000 Binary files a/examples/zm_wahlkamp/wahlergebnisse.db and /dev/null differ diff --git a/src/schule/ngb/zm/Constants.java b/src/schule/ngb/zm/Constants.java index 04c0c35..bde0492 100644 --- a/src/schule/ngb/zm/Constants.java +++ b/src/schule/ngb/zm/Constants.java @@ -28,7 +28,7 @@ public class Constants { /** * Patchversion der Zeichenmaschine. */ - public static final int APP_VERSION_REV = 12; + public static final int APP_VERSION_REV = 21; /** * Version der Zeichenmaschine als Text-String. @@ -71,6 +71,11 @@ public class Constants { */ public static final int STD_FONTSIZE = 14; + /** + * Standardwert für den Abstand von Formen. + */ + public static final int STD_BUFFER = 10; + public static final Options.StrokeType SOLID = Options.StrokeType.SOLID; public static final Options.StrokeType DASHED = Options.StrokeType.DASHED; @@ -242,6 +247,11 @@ public class Constants { * Zeichenmaschine selbst vorgenommen werden. */ + /** + * Aktuell dargestellte Bilder pro Sekunde. + */ + public static int framesPerSecond = STD_FPS; + /** * Anzahl der Ticks (Frames), die das Programm bisher läuft. */ @@ -279,6 +289,18 @@ public class Constants { */ public static double pmouseY = 0.0; + /** + * Die aktuelle (current) {@code x}-Koordinate der Maus + * (wird bei jeder Mausbewegung aktualisiert). + */ + public static double cmouseX = 0.0; + + /** + * Die aktuelle (current) {@code y}-Koordinate der Maus + * (wird bei jeder Mausbewegung aktualisiert). + */ + public static double cmouseY = 0.0; + /** * Gibt an, ob ein Mausknopf derzeit gedrückt ist. */ @@ -364,8 +386,8 @@ public class Constants { // Farben /** - * Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen - * Grauwert zwischen 0 und 255 an, wobei + * Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen Grauwert + * zwischen 0 und 255 an, wobei * 0 schwarz und 255 weiß ist. * * @param gray Grauwert zwischen 0 und 255. @@ -376,8 +398,8 @@ public class Constants { } /** - * Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen - * Grauwert zwischen 0 und 255 an, wobei + * Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen Grauwert + * zwischen 0 und 255 an, wobei * 0 schwarz und 255 weiß ist. * {@code alpha} gibt den den Transparentwert an (auch zwischen * 0 und 255), wobei @@ -395,8 +417,8 @@ public class Constants { /** * Erstellt eine Farbe. Die Parameter {@code red}, {@code green} und - * {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die - * Werte liegen zwischen 0 und 255. + * {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte + * liegen zwischen 0 und 255. * * @param red Rotwert zwischen 0 und 255. * @param green Grünwert zwischen 0 und 255. @@ -409,10 +431,10 @@ public class Constants { /** * Erstellt eine Farbe. Die Parameter {@code red}, {@code green} und - * {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die - * Werte liegen zwischen 0 und 255. - * {@code alpha} gibt den den Transparentwert an (auch zwischen - * code>0 und 255), wobei + * {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte + * liegen zwischen 0 und 255. {@code alpha} gibt + * den den Transparentwert an (auch zwischen code>0 und + * 255), wobei * 0 komplett durchsichtig ist und 255 komplett * deckend. * @@ -705,9 +727,9 @@ public class Constants { } /** - * Beschränkt die Zahl {@code x} auf das Intervall [min, - * max]. Liegt {@code x} außerhalb des Intervalls, wird eine der - * Grenzen zurückgegeben. + * Beschränkt die Zahl {@code x} auf das Intervall [min, max]. + * Liegt {@code x} außerhalb des Intervalls, wird eine der Grenzen + * zurückgegeben. * * @param x Eine Zahl. * @param max Das Maximum des Intervalls. @@ -724,9 +746,9 @@ public class Constants { } /** - * Beschränkt die Zahl {@code x} auf das Intervall [min, - * max]. Liegt {@code x} außerhalb des Intervalls, wird eine der - * Grenzen zurückgegeben. + * Beschränkt die Zahl {@code x} auf das Intervall [min, max]. + * Liegt {@code x} außerhalb des Intervalls, wird eine der Grenzen + * zurückgegeben. * * @param x Eine Zahl. * @param max Das Maximum des Intervalls. @@ -743,8 +765,8 @@ public class Constants { } /** - * Interpoliert einen Wert zwischen {@code from} und {@code to} - * linear, abhängig von {@code t}. Das Ergebnis entspricht der Formel + * Interpoliert einen Wert zwischen {@code from} und {@code to} linear, + * abhängig von {@code t}. Das Ergebnis entspricht der Formel * *
 	 * from - t * (from + to)
@@ -759,6 +781,10 @@ public class Constants {
 		return from + t * (to - from);
 	}
 
+	public static final double morph( double from, double to, double t ) {
+		return interpolate(from, to, limit(t, 0.0, 1.0));
+	}
+
 	public static final double map( double value, double fromMin, double fromMax, double toMin, double toMax ) {
 		return interpolate(toMin, toMax, (value - fromMin) / (fromMax - fromMin));
 	}
@@ -1038,4 +1064,320 @@ public class Constants {
 		return Boolean.parseBoolean(value);
 	}
 
+	// Konstants for Key events (Copied from KeyEvent)
+
+	/**
+	 * Constant for the ENTER virtual key.
+	 */
+	public static final int KEY_ENTER = KeyEvent.VK_ENTER;
+
+	/**
+	 * Constant for the BACK_SPACE virtual key.
+	 */
+	public static final int KEY_BACK_SPACE = KeyEvent.VK_BACK_SPACE;
+
+	/**
+	 * Constant for the TAB virtual key.
+	 */
+	public static final int KEY_TAB = KeyEvent.VK_TAB;
+
+	/**
+	 * Constant for the CANCEL virtual key.
+	 */
+	public static final int KEY_CANCEL = KeyEvent.VK_CANCEL;
+
+	/**
+	 * Constant for the CLEAR virtual key.
+	 */
+	public static final int KEY_CLEAR = KeyEvent.VK_CLEAR;
+
+	/**
+	 * Constant for the SHIFT virtual key.
+	 */
+	public static final int KEY_SHIFT = KeyEvent.VK_SHIFT;
+
+	/**
+	 * Constant for the CONTROL virtual key.
+	 */
+	public static final int KEY_CONTROL = KeyEvent.VK_CONTROL;
+
+	/**
+	 * Constant for the ALT virtual key.
+	 */
+	public static final int KEY_ALT = KeyEvent.VK_ALT;
+
+	/**
+	 * Constant for the PAUSE virtual key.
+	 */
+	public static final int KEY_PAUSE = KeyEvent.VK_PAUSE;
+
+	/**
+	 * Constant for the CAPS_LOCK virtual key.
+	 */
+	public static final int KEY_CAPS_LOCK = KeyEvent.VK_CAPS_LOCK;
+
+	/**
+	 * Constant for the ESCAPE virtual key.
+	 */
+	public static final int KEY_ESCAPE = KeyEvent.VK_ESCAPE;
+
+	/**
+	 * Constant for the SPACE virtual key.
+	 */
+	public static final int KEY_SPACE = KeyEvent.VK_SPACE;
+
+	/**
+	 * Constant for the PAGE_UP virtual key.
+	 */
+	public static final int KEY_PAGE_UP = KeyEvent.VK_PAGE_UP;
+
+	/**
+	 * Constant for the PAGE_DOWN virtual key.
+	 */
+	public static final int KEY_PAGE_DOWN = KeyEvent.VK_PAGE_DOWN;
+
+	/**
+	 * Constant for the END virtual key.
+	 */
+	public static final int KEY_END = KeyEvent.VK_END;
+
+	/**
+	 * Constant for the HOME virtual key.
+	 */
+	public static final int KEY_HOME = KeyEvent.VK_HOME;
+
+	/**
+	 * Constant for the non-numpad left arrow key.
+	 */
+	public static final int KEY_LEFT = KeyEvent.VK_LEFT;
+
+	/**
+	 * Constant for the non-numpad up arrow key.
+	 */
+	public static final int KEY_UP = KeyEvent.VK_UP;
+
+	/**
+	 * Constant for the non-numpad right arrow key.
+	 */
+	public static final int KEY_RIGHT = KeyEvent.VK_RIGHT;
+
+	/**
+	 * Constant for the non-numpad down arrow key.
+	 */
+	public static final int KEY_DOWN = KeyEvent.VK_DOWN;
+
+	/**
+	 * Constant for the comma key, ","
+	 */
+	public static final int KEY_COMMA = KeyEvent.VK_COMMA;
+
+	/**
+	 * Constant for the minus key, "-"
+	 *
+	 * @since 1.2
+	 */
+	public static final int KEY_MINUS = KeyEvent.VK_MINUS;
+
+	/**
+	 * Constant for the period key, "."
+	 */
+	public static final int KEY_PERIOD = KeyEvent.VK_PERIOD;
+
+	/**
+	 * Constant for the forward slash key, "/"
+	 */
+	public static final int KEY_SLASH = KeyEvent.VK_SLASH;
+
+	/**
+	 * Constant for the "0" key.
+	 */
+	public static final int KEY_0 = KeyEvent.VK_0;
+
+	/**
+	 * Constant for the "1" key.
+	 */
+	public static final int KEY_1 = KeyEvent.VK_1;
+
+	/**
+	 * Constant for the "2" key.
+	 */
+	public static final int KEY_2 = KeyEvent.VK_2;
+
+	/**
+	 * Constant for the "3" key.
+	 */
+	public static final int KEY_3 = KeyEvent.VK_3;
+
+	/**
+	 * Constant for the "4" key.
+	 */
+	public static final int KEY_4 = KeyEvent.VK_4;
+
+	/**
+	 * Constant for the "5" key.
+	 */
+	public static final int KEY_5 = KeyEvent.VK_5;
+
+	/**
+	 * Constant for the "6" key.
+	 */
+	public static final int KEY_6 = KeyEvent.VK_6;
+
+	/**
+	 * Constant for the "7" key.
+	 */
+	public static final int KEY_7 = KeyEvent.VK_7;
+
+	/**
+	 * Constant for the "8" key.
+	 */
+	public static final int KEY_8 = KeyEvent.VK_8;
+
+	/**
+	 * Constant for the "9" key.
+	 */
+	public static final int KEY_9 = KeyEvent.VK_9;
+
+	/**
+	 * Constant for the semicolon key, ";"
+	 */
+	public static final int KEY_SEMICOLON = KeyEvent.VK_SEMICOLON;
+
+	/**
+	 * Constant for the equals key, "="
+	 */
+	public static final int KEY_EQUALS = KeyEvent.VK_EQUALS;
+
+	/** VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */
+
+	/**
+	 * Constant for the "A" key.
+	 */
+	public static final int KEY_A = KeyEvent.VK_A;
+
+	/**
+	 * Constant for the "B" key.
+	 */
+	public static final int KEY_B = KeyEvent.VK_B;
+
+	/**
+	 * Constant for the "C" key.
+	 */
+	public static final int KEY_C = KeyEvent.VK_C;
+
+	/**
+	 * Constant for the "D" key.
+	 */
+	public static final int KEY_D = KeyEvent.VK_D;
+
+	/**
+	 * Constant for the "E" key.
+	 */
+	public static final int KEY_E = KeyEvent.VK_E;
+
+	/**
+	 * Constant for the "F" key.
+	 */
+	public static final int KEY_F = KeyEvent.VK_F;
+
+	/**
+	 * Constant for the "G" key.
+	 */
+	public static final int KEY_G = KeyEvent.VK_G;
+
+	/**
+	 * Constant for the "H" key.
+	 */
+	public static final int KEY_H = KeyEvent.VK_H;
+
+	/**
+	 * Constant for the "I" key.
+	 */
+	public static final int KEY_I = KeyEvent.VK_I;
+
+	/**
+	 * Constant for the "J" key.
+	 */
+	public static final int KEY_J = KeyEvent.VK_J;
+
+	/**
+	 * Constant for the "K" key.
+	 */
+	public static final int KEY_K = KeyEvent.VK_K;
+
+	/**
+	 * Constant for the "L" key.
+	 */
+	public static final int KEY_L = KeyEvent.VK_L;
+
+	/**
+	 * Constant for the "M" key.
+	 */
+	public static final int KEY_M = KeyEvent.VK_M;
+
+	/**
+	 * Constant for the "N" key.
+	 */
+	public static final int KEY_N = KeyEvent.VK_N;
+
+	/**
+	 * Constant for the "O" key.
+	 */
+	public static final int KEY_O = KeyEvent.VK_O;
+
+	/**
+	 * Constant for the "P" key.
+	 */
+	public static final int KEY_P = KeyEvent.VK_P;
+
+	/**
+	 * Constant for the "Q" key.
+	 */
+	public static final int KEY_Q = KeyEvent.VK_Q;
+
+	/**
+	 * Constant for the "R" key.
+	 */
+	public static final int KEY_R = KeyEvent.VK_R;
+
+	/**
+	 * Constant for the "S" key.
+	 */
+	public static final int KEY_S = KeyEvent.VK_S;
+
+	/**
+	 * Constant for the "T" key.
+	 */
+	public static final int KEY_T = KeyEvent.VK_T;
+
+	/**
+	 * Constant for the "U" key.
+	 */
+	public static final int KEY_U = KeyEvent.VK_U;
+
+	/**
+	 * Constant for the "V" key.
+	 */
+	public static final int KEY_V = KeyEvent.VK_V;
+
+	/**
+	 * Constant for the "W" key.
+	 */
+	public static final int KEY_W = KeyEvent.VK_W;
+
+	/**
+	 * Constant for the "X" key.
+	 */
+	public static final int KEY_X = KeyEvent.VK_X;
+
+	/**
+	 * Constant for the "Y" key.
+	 */
+	public static final int KEY_Y = KeyEvent.VK_Y;
+
+	/**
+	 * Constant for the "Z" key.
+	 */
+	public static final int KEY_Z = KeyEvent.VK_Z;
+
 }
diff --git a/src/schule/ngb/zm/GraphicsLayer.java b/src/schule/ngb/zm/GraphicsLayer.java
new file mode 100644
index 0000000..480bfc7
--- /dev/null
+++ b/src/schule/ngb/zm/GraphicsLayer.java
@@ -0,0 +1,11 @@
+package schule.ngb.zm;
+
+import java.awt.Graphics2D;
+
+public class GraphicsLayer extends Layer {
+
+	public Graphics2D getGraphics() {
+		return drawing;
+	}
+
+}
diff --git a/src/schule/ngb/zm/Layer.java b/src/schule/ngb/zm/Layer.java
index 175f77f..6662708 100644
--- a/src/schule/ngb/zm/Layer.java
+++ b/src/schule/ngb/zm/Layer.java
@@ -73,7 +73,7 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
 	}
 
 	/**
-	 * Erstellt einen neuen Puffer für dei Ebene mit der angegebenen Größe
+	 * Erstellt einen neuen Puffer für die Ebene mit der angegebenen Größe
 	 * und kopiert den Inhalt des alten Puffers in den Neuen.
 	 *
 	 * @param width  Width des neuen Puffers.
diff --git a/src/schule/ngb/zm/Options.java b/src/schule/ngb/zm/Options.java
index e6c01a5..70cf071 100644
--- a/src/schule/ngb/zm/Options.java
+++ b/src/schule/ngb/zm/Options.java
@@ -41,43 +41,58 @@ public final class Options {
 	}
 
 	public enum Direction {
-		CENTER(0),
-		NORTH(1 << 0),
-		EAST(1 << 1),
-		SOUTH(1 << 2),
-		WEST(1 << 3),
+		CENTER(0, 0),
+		NORTH(0, -1),
+		EAST(1, 0),
+		SOUTH(0, 1),
+		WEST(-1, 0),
 
-		NORTHEAST(NORTH.mask | EAST.mask),
-		SOUTHEAST(SOUTH.mask | EAST.mask),
-		NORTHWEST(NORTH.mask | WEST.mask),
-		SOUTHWEST(SOUTH.mask | WEST.mask),
+		NORTHEAST(1, -1),
+		SOUTHEAST(1, 1),
+		NORTHWEST(-1, -1),
+		SOUTHWEST(-1, 1),
 
-		MIDDLE(CENTER.mask),
-		UP(NORTH.mask),
-		DOWN(SOUTH.mask),
-		LEFT(WEST.mask),
-		RIGHT(EAST.mask);
+		MIDDLE(CENTER),
+		UP(NORTH),
+		DOWN(SOUTH),
+		LEFT(WEST),
+		RIGHT(EAST);
 
-		public final byte mask;
+		public final byte x, y;
 
-		Direction( int mask ) {
-			this.mask = (byte) mask;
+		Direction( int x, int y ) {
+			this.x = (byte)x;
+			this.y = (byte)y;
 		}
 
-		public boolean in( int mask ) {
-			return (mask & this.mask) == this.mask;
+		Direction( Direction original ) {
+			this.x = original.x;
+			this.y = original.y;
 		}
 
 		public boolean in( Direction dir ) {
-			return in(dir.mask);
-		}
-
-		public boolean contains( int mask ) {
-			return (mask & this.mask) == mask;
+			return (this.x == dir.x && this.y == 0) || (this.y == dir.y && this.x == 0) || (this.x == dir.x && this.y == dir.y);
 		}
 
 		public boolean contains( Direction dir ) {
-			return contains(dir.mask);
+			return dir.in(this);
+		}
+
+		public Vector asVector() {
+			return new Vector(x, y);
+		}
+
+		/**
+		 * Gibt die entgegengesetzte Richtung zu dieser zurück.
+		 * @return
+		 */
+		public Direction inverse() {
+			for( Direction dir: Direction.values() ) {
+				if( dir.x == -this.x && dir.y == -this.y ) {
+					return dir;
+				}
+			}
+			return CENTER;
 		}
 	}
 
diff --git a/src/schule/ngb/zm/Spielemaschine.java b/src/schule/ngb/zm/Spielemaschine.java
new file mode 100644
index 0000000..edcc7d5
--- /dev/null
+++ b/src/schule/ngb/zm/Spielemaschine.java
@@ -0,0 +1,104 @@
+package schule.ngb.zm;
+
+import java.awt.Graphics;
+import java.util.LinkedList;
+
+public class Spielemaschine extends Zeichenmaschine {
+
+	private LinkedList drawables;
+
+	private LinkedList updatables;
+
+	private GraphicsLayer mainLayer;
+
+	public Spielemaschine( String title ) {
+		this(STD_WIDTH, STD_HEIGHT, title);
+	}
+
+
+	public Spielemaschine( int width, int height, String title ) {
+		super(width, height, title, false);
+
+		drawables = new LinkedList<>();
+		updatables = new LinkedList<>();
+
+		mainLayer = new GraphicsLayer();
+		canvas.addLayer(mainLayer);
+	}
+
+	public final void add( Object... pObjects ) {
+		for( Object obj : pObjects ) {
+			if( obj instanceof Drawable ) {
+				addDrawable((Drawable) obj);
+			}
+			if( obj instanceof Updatable ) {
+				addUpdatable((Updatable) obj);
+			}
+		}
+	}
+
+	public final void addDrawable( Drawable pDrawable ) {
+		synchronized( drawables ) {
+			drawables.add(pDrawable);
+		}
+	}
+
+	public final void addUpdatable( Updatable pUpdatable ) {
+		synchronized( updatables ) {
+			updatables.add(pUpdatable);
+		}
+	}
+
+	public final void remove( Object... pObjects ) {
+		for( Object obj : pObjects ) {
+			if( obj instanceof Drawable ) {
+				removeDrawable((Drawable) obj);
+			}
+			if( obj instanceof Updatable ) {
+				removeUpdatable((Updatable) obj);
+			}
+		}
+	}
+
+	public final void removeDrawable( Drawable pDrawable ) {
+		synchronized( drawables ) {
+			drawables.remove(pDrawable);
+		}
+	}
+
+	public final void removeUpdatable( Updatable pUpdatable ) {
+		synchronized( updatables ) {
+			updatables.remove(pUpdatable);
+		}
+	}
+
+	protected void updateGame( double delta ) {
+
+	}
+
+	@Override
+	public final void update( double delta ) {
+		synchronized( updatables ) {
+			for( Updatable updatable : updatables ) {
+				if( updatable.isActive() ) {
+					updatable.update(delta);
+				}
+			}
+		}
+
+		updateGame(delta);
+	}
+
+	@Override
+	public final void draw() {
+		mainLayer.clear();
+		synchronized( drawables ) {
+			for( Drawable drawable : drawables ) {
+				if( drawable.isVisible() ) {
+					drawable.draw(mainLayer.getGraphics());
+				}
+			}
+		}
+	}
+
+}
diff --git a/src/schule/ngb/zm/Vector.java b/src/schule/ngb/zm/Vector.java
index f90e11b..3af5651 100644
--- a/src/schule/ngb/zm/Vector.java
+++ b/src/schule/ngb/zm/Vector.java
@@ -361,6 +361,9 @@ public class Vector extends Point2D.Double {
 	}
 
 	public Vector div( double scalar ) {
+		if( scalar == 0.0 ) {
+			throw new IllegalArgumentException("Can't divide by zero.");
+		}
 		x /= scalar;
 		y /= scalar;
 		return this;
diff --git a/src/schule/ngb/zm/Zeichenleinwand.java b/src/schule/ngb/zm/Zeichenleinwand.java
index 7562a54..73b9bae 100644
--- a/src/schule/ngb/zm/Zeichenleinwand.java
+++ b/src/schule/ngb/zm/Zeichenleinwand.java
@@ -29,6 +29,7 @@ public class Zeichenleinwand extends Canvas {
 
 	/**
 	 * Erstellt eine neue Zeichenleinwand mit einer festen Größe.
+	 *
 	 * @param width Breite der Zeichenleinwand.
 	 * @param height Höhe der Zeichenleinwand.
 	 */
@@ -42,8 +43,6 @@ public class Zeichenleinwand extends Canvas {
 		layers = new LinkedList<>();
 		synchronized( layers ) {
 			layers.add(new ColorLayer(width, height, Constants.STD_BACKGROUND));
-			layers.add(new DrawingLayer(width, height));
-			layers.add(new ShapesLayer(width, height));
 		}
 	}
 
@@ -53,7 +52,7 @@ public class Zeichenleinwand extends Canvas {
 	 * Bei einer Größenänderung wird auch die Größe aller bisher hinzugefügter
 	 * {@link Layer Ebenen} angepasst, sodass sie die gesamte Leinwand füllen.
 	 *
-	 * @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
@@ -85,16 +84,24 @@ public class Zeichenleinwand extends Canvas {
 	}
 
 	/**
-	 * Fügt der Zeichenleinwand eine Ebene an einer bestimmten Stelle hinzu.
+	 * 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
+	 * 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 ) {
 		if( layer != null ) {
 			synchronized( layers ) {
 				layer.setSize(getWidth(), getHeight());
-				layers.add(i, layer);
+				if( i > layers.size() ) {
+					layers.add(layer);
+				} else {
+					layers.add(i, layer);
+				}
 			}
 		}
 	}
@@ -168,6 +175,20 @@ public class Zeichenleinwand extends Canvas {
 		return result;
 	}
 
+	public boolean removeLayer( Layer pLayer ) {
+		return layers.remove(pLayer);
+	}
+
+	public void removeLayers( Layer... pLayers ) {
+		for( Layer layer : pLayers ) {
+			layers.remove(layer);
+		}
+	}
+
+	public void clearLayers() {
+		layers.clear();
+	}
+
 	/**
 	 * Erstellt eine passende {@link BufferStrategy} für diese Ebene.
 	 */
@@ -193,6 +214,7 @@ public class Zeichenleinwand extends Canvas {
 
 	/**
 	 * Zeichnet den Inhalt aller {@link Layer Ebenen} in den Grafik-Kontext.
+	 *
 	 * @param graphics
 	 */
 	public void draw( Graphics graphics ) {
diff --git a/src/schule/ngb/zm/Zeichenmaschine.java b/src/schule/ngb/zm/Zeichenmaschine.java
index 0aed369..3ca7645 100644
--- a/src/schule/ngb/zm/Zeichenmaschine.java
+++ b/src/schule/ngb/zm/Zeichenmaschine.java
@@ -1,7 +1,9 @@
 package schule.ngb.zm;
 
 import schule.ngb.zm.shapes.ShapesLayer;
+import schule.ngb.zm.tasks.TaskRunner;
 import schule.ngb.zm.util.ImageLoader;
+import schule.ngb.zm.util.Log;
 
 import javax.imageio.ImageIO;
 import javax.swing.*;
@@ -11,6 +13,8 @@ import java.awt.event.*;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
+import java.util.concurrent.*;
+import java.util.logging.Level;
 
 /**
  * Hauptklasse der Zeichenmaschine.
@@ -19,17 +23,45 @@ import java.io.IOException;
  * Die Klasse übernimmt die Initialisierung eines Programmfensters und der
  * nötigen Komponenten.
  */
-public class Zeichenmaschine extends Constants implements MouseInputListener, KeyListener {
+public class Zeichenmaschine extends Constants {
 
 	/**
 	 * Gibt an, ob die Zeichenmaschine aus BlueJ heraus gestartet wurde.
 	 */
-	public static boolean IN_BLUEJ;
+	public static final boolean IN_BLUEJ;
 
 	static {
 		IN_BLUEJ = System.getProperty("java.class.path").contains("bluej");
 	}
 
+	public static final boolean MACOS;
+
+	public static final boolean WINDOWS;
+
+	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.
 	 */
@@ -106,11 +138,17 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
 	private boolean stop_after_update = false, run_once = true;
 
 	// Aktuelle Frames pro Sekunde der Zeichenmaschine.
-	private int framesPerSecond;
+	private int framesPerSecondInternal;
 
 	// Hauptthread der Zeichenmaschine.
 	private Thread mainThread;
 
+	// Queue für geplante Aufgaben
+	private DelayQueue taskQueue = new DelayQueue<>();
+
+	// Queue für abgefangene InputEvents
+	private BlockingQueue eventQueue = new LinkedBlockingQueue<>();
+
 	// Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
 	// oder das Zeichenfenster weiter geöffnet bleibt.
 	private boolean quitAfterTeardown = false;
@@ -125,7 +163,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
 	 * Erstellt eine neue Zeichenmaschine mit Standardwerten für Titel und
 	 * Größe.
 	 * 

- * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details. + * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr + * Details. */ public Zeichenmaschine() { this(APP_NAME + " " + APP_VERSION); @@ -135,10 +174,11 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * Erstellt eine neue Zeichenmaschine mit Standardwerten für Titel und * Größe. *

- * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details. + * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr + * Details. * * @param run_once {@code true} beendet die Zeichenmaschine nach einem - * Aufruf von {@code draw()}. + * Aufruf von {@code draw()}. */ public Zeichenmaschine( boolean run_once ) { this(APP_NAME + " " + APP_VERSION, run_once); @@ -148,7 +188,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * Erstellt eine neue Zeichenmaschine mit dem angegebene Titel und * Standardwerten für die Größe. *

- * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details. + * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr + * Details. * * @param title Der Titel, der oben im Fenster steht. */ @@ -160,11 +201,12 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * Erstellt eine neue Zeichenmaschine mit dem angegebene Titel und * Standardwerten für die Größe. *

- * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details. + * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr + * Details. * * @param title Der Titel, der oben im Fenster steht. * @param run_once {@code true} beendet die Zeichenmaschine nach einem - * Aufruf von {@code draw()}. + * Aufruf von {@code draw()}. */ public Zeichenmaschine( String title, boolean run_once ) { this(STD_WIDTH, STD_HEIGHT, title, run_once); @@ -174,11 +216,12 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * Erstellt eine neue zeichenmaschine mit einer Leinwand der angegebenen * Größe und dem angegebenen Titel. *

- * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr Details. + * Siehe {@link #Zeichenmaschine(int, int, String, boolean)} für mehr + * Details. * - * @param width Breite der {@link Zeichenleinwand Zeichenleinwand}. + * @param width Breite der {@link Zeichenleinwand Zeichenleinwand}. * @param height Höhe der {@link Zeichenleinwand Zeichenleinwand}. - * @param title Der Titel, der oben im Fenster steht. + * @param title Der Titel, der oben im Fenster steht. */ public Zeichenmaschine( int width, int height, String title ) { this(width, height, title, true); @@ -191,28 +234,30 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * Die Zeichenmaschine öffnet automatisch ein Fenster mit einer * {@link Zeichenleinwand}, die {@code width} Pixel breit und {@code height} * Pixel hoch ist. Die Leinwand hat immer eine Mindestgröße von 100x100 - * Pixeln und kann nicht größer als der aktuelle Bildschirm werden. - * Das Fenster bekommt den angegebenen Titel. + * Pixeln und kann nicht größer als der aktuelle Bildschirm werden. Das + * Fenster bekommt den angegebenen Titel. *

* Falls {@code run_once} gleich {@code false} ist, werden * {@link #update(double)} und {@link #draw()} entsprechend der - * {@link #framesPerSecond} kontinuierlich aufgerufen. - * Falls das Programm als Unterklasse der Zeichenmaschine verfasst wird, - * dann kann auch, {@code update(double)} überschrieben werden, damit die - * Maschine nicht automatisch beendet. + * {@link #framesPerSecond} kontinuierlich aufgerufen. Falls das Programm + * als Unterklasse der Zeichenmaschine verfasst wird, dann kann auch, + * {@code update(double)} überschrieben werden, damit die Maschine nicht + * automatisch beendet. * - * @param width Breite der {@link Zeichenleinwand Zeichenleinwand}. + * @param width Breite der {@link Zeichenleinwand Zeichenleinwand}. * @param height Höhe der {@link Zeichenleinwand Zeichenleinwand}. - * @param title Der Titel, der oben im Fenster steht. + * @param title Der Titel, der oben im Fenster steht. * @param run_once {@code true} beendet die Zeichenmaschine nach einem - * Aufruf von {@code draw()}. + * Aufruf von {@code draw()}. */ public Zeichenmaschine( int width, int height, String title, boolean run_once ) { + LOG.info("Starting " + APP_NAME + " " + APP_VERSION); + // Setzen des "Look&Feel" try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch( Exception e ) { - System.err.println("Error setting the look and feel: " + e.getMessage()); + } catch( Exception ex ) { + LOG.log(Level.SEVERE, "Error setting the look and feel: " + ex.getMessage(), ex); } // Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um @@ -244,16 +289,22 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); // Das Icon des Fensters ändern + try { ImageIcon icon = new ImageIcon(ImageIO.read(new File("res/icon_64.png"))); - frame.setIconImage(icon.getImage()); - // Dock Icon in macOS setzen - Taskbar taskbar = Taskbar.getTaskbar(); - taskbar.setIconImage(icon.getImage()); + if( MACOS ) { + // Dock Icon in macOS setzen + Taskbar taskbar = Taskbar.getTaskbar(); + taskbar.setIconImage(icon.getImage()); + } else { + // Kleines Icon des Frames setzen + frame.setIconImage(icon.getImage()); + } } catch( IOException e ) { } + // Erstellen der Leinwand canvas = new Zeichenleinwand(width, height); frame.add(canvas); @@ -264,7 +315,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke shapes = getShapesLayer(); // FPS setzen - framesPerSecond = STD_FPS; + framesPerSecondInternal = STD_FPS; this.run_once = run_once; // Settings der Unterklasse aufrufen, falls das Fenster vor dem Öffnen @@ -273,9 +324,13 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke settings(); // Listener hinzufügen, um auf Maus- und Tastatureingaben zu hören. - canvas.addMouseListener(this); - canvas.addMouseMotionListener(this); - canvas.addKeyListener(this); + InputListener inputListener = new InputListener(); + canvas.addMouseListener(inputListener); + canvas.addMouseMotionListener(inputListener); + canvas.addMouseWheelListener(inputListener); + canvas.addKeyListener(inputListener); + + // Programm beenden, wenn Fenster geschlossen wird frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing( WindowEvent e ) { @@ -343,9 +398,10 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * wiederhergestellt. * * @param pEnable Wenn {@code true}, wird der Vollbildmodus aktiviert, - * ansonsten deaktiviert. + * ansonsten deaktiviert. */ public final void setFullscreen( boolean pEnable ) { + // See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html if( displayDevice.isFullScreenSupported() ) { if( pEnable && !fullscreen ) { // frame.setUndecorated(true); @@ -354,24 +410,33 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // Update width / height initialWidth = width; initialHeight = height; - setSize(screenWidth, screenHeight); + changeSize(screenWidth, screenHeight); // Register ESC as exit fullscreen canvas.addKeyListener(fullscreenExitListener); fullscreen = true; + fullscreenChanged(); } else if( !pEnable && fullscreen ) { fullscreen = false; canvas.removeKeyListener(fullscreenExitListener); displayDevice.setFullScreenWindow(null); - setSize(initialWidth, initialHeight); + changeSize(initialWidth, initialHeight); + frame.pack(); // frame.setUndecorated(false); + fullscreenChanged(); } } } + public boolean isFullscreen() { + Window win = displayDevice.getFullScreenWindow(); + return fullscreen && win != null; + } + /** - * Gibt den aktuellen {@link Options.AppState Zustand} der Zeichenmaschine zurück. + * Gibt den aktuellen {@link Options.AppState Zustand} der Zeichenmaschine + * zurück. * * @return Der Zustand der Zeichenmaschine. */ @@ -416,8 +481,9 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * Pausiert die Ausführung von {@link #update(double)} und {@link #draw()} * nach dem nächsten vollständigen Frame. *

- * Die Zeichenmaschine wechselt in den Zustand {@link Options.AppState#PAUSED}, - * sobald der aktuelle Frame beendet wurde. + * Die Zeichenmaschine wechselt in den Zustand + * {@link Options.AppState#PAUSED}, sobald der aktuelle Frame beendet + * wurde. */ public final void pause() { pause_pending = true; @@ -436,6 +502,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke /** * Prüft, ob die Zeichenmaschine gerade pausiert ist. + * * @return */ public final boolean isPaused() { @@ -446,15 +513,32 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * Stoppt die Zeichenmaschine. *

* Nachdem der aktuelle Frame gezeichnet wurde wechselt die Zeichenmaschine - * in den Zustand {@link Options.AppState#STOPPED} und ruft {@link #teardown()} - * auf. Nachdem {@code teardown()} ausgeführt wurde wechselt der Zustand zu - * {@link Options.AppState#TERMINATED}. Das Zeichenfenster bleibt weiter - * geöffnet. + * in den Zustand {@link Options.AppState#STOPPED} und ruft + * {@link #teardown()} auf. Nachdem {@code teardown()} ausgeführt wurde + * wechselt der Zustand zu {@link Options.AppState#TERMINATED}. Das + * Zeichenfenster bleibt weiter geöffnet. */ public final void stop() { running = false; } + /** + * Führt interne Aufräumarbeiten durch. + *

+ * Wird nach dem {@link #stop() Stopp} der Zeichenmaschine aufgerufen und + * verbleibende Threads, Tasks, etc. zu stoppen und aufzuräumen. Die + * Äquivalente Methode für Unterklassen ist {@link #teardown()}, die direkt + * vor {@code cleanup()} aufgerufen wird. + */ + private void cleanup() { + // Alle noch nicht ausgelösten Events werden entfernt + eventQueue.clear(); + // Alle noch nicht ausgeführten Tasks werden entfernt + taskQueue.clear(); + // TaskRunner stoppen + TaskRunner.shutdown(); + } + /** * Beendet die Zeichenmaschine vollständig. *

@@ -480,8 +564,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke } /** - * Beendet das Programm. Falls {@code exit} gleich {@code true} ist, - * wird die komplette VM beendet. + * Beendet das Programm. Falls {@code exit} gleich {@code true} ist, wird + * die komplette VM beendet. * * @param exit Ob die VM beendet werden soll. * @see System#exit(int) @@ -496,6 +580,28 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke } } + /** + * Interne Methode um die Größe der Zeichenfläche zu ändern. + *

+ * Die Methode berücksichtigt nicht den Zustand des Fensters (z.B. + * Vollbildmodus) und geht davon aus, dass die aufrufende Methode + * sichergestellt hat, dass eine Änderung der Größe der Zeichenfläche + * zulässig und sinnvoll ist. + * + * @param newWidth Neue Breite der Zeichenleinwand. + * @param newHeight Neue Höhe der Zeichenleinwand. + * @see #setSize(int, int) + * @see #setFullscreen(boolean) + */ + private void changeSize( int newWidth, int newHeight ) { + width = Math.min(Math.max(newWidth, 100), screenWidth); + height = Math.min(Math.max(newHeight, 100), screenHeight); + + if( canvas != null ) { + canvas.setSize(width, height); + } + } + /** * Ändert die Größe der {@link Zeichenleinwand}. * @@ -509,11 +615,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke initialHeight = Math.min(Math.max(height, 100), screenHeight); setFullscreen(false); } else { - if( canvas != null ) { - canvas.setSize(width, height); - } - this.width = Math.min(Math.max(width, 100), screenWidth); - this.height = Math.min(Math.max(height, 100), screenHeight); + changeSize(width, height); //frame.setSize(width, height); frame.pack(); @@ -557,8 +659,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke } /** - * Fügt der {@link Zeichenleinwand} eine weitere {@link Layer Ebene} - * hinzu. + * Fügt der {@link Zeichenleinwand} eine weitere {@link Layer Ebene} hinzu. * * @param layer Die neue Ebene. */ @@ -605,9 +706,9 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke } /** - * 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. + * 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. *

* In der Regel sollte dies dieselbe Ebene sein wie {@link #background}. * @@ -623,8 +724,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke } /** - * Gibt die Standard-{@link DrawingLayer Zeichenebene} zurück. Gibt es - * keine solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand} + * Gibt die Standard-{@link DrawingLayer Zeichenebene} zurück. Gibt es keine + * solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand} * hinzugefügt. *

* In der Regel sollte dies dieselbe Ebene sein wie {@link #drawing}. @@ -641,8 +742,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke } /** - * Gibt die Standard-{@link ShapesLayer Formenebene} zurück. Gibt es - * keine solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand} + * Gibt die Standard-{@link ShapesLayer Formenebene} zurück. Gibt es keine + * solche Ebene, so wird eine erstellt und der {@link Zeichenleinwand} * hinzugefügt. *

* In der Regel sollte dies dieselbe Ebene sein wie {@link #shapes}. @@ -653,7 +754,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke ShapesLayer layer = canvas.getLayer(ShapesLayer.class); if( layer == null ) { layer = new ShapesLayer(getWidth(), getHeight()); - canvas.addLayer(layer); + canvas.addLayer(2, layer); } return layer; } @@ -664,7 +765,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * @return Angepeilte Frames pro Sekunde */ public final int getFramesPerSecond() { - return framesPerSecond; + return framesPerSecondInternal; } /** @@ -673,7 +774,13 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * @param pFramesPerSecond Neue FPS. */ public final void setFramesPerSecond( int pFramesPerSecond ) { - framesPerSecond = pFramesPerSecond; + if( pFramesPerSecond > 0 ) { + framesPerSecondInternal = pFramesPerSecond; + } else { + framesPerSecondInternal = 1; + // Logger ... + } + framesPerSecond = framesPerSecondInternal; } /** @@ -718,9 +825,9 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke /** * 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. + * {@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. */ @@ -806,8 +913,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke } /** - * Zeigt den Mauszeiger wieder an, falls er zuvor {@link #hideCursor() versteckt} - * wurde. + * Zeigt den Mauszeiger wieder an, falls er zuvor + * {@link #hideCursor() versteckt} wurde. *

* Nach dem Aufruf gilt {@code cursorVisible == true}. *

@@ -846,6 +953,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke *

 	 *     setCursor(Cursor.HAND_CURSOR);
 	 * 
+ * * @param pPredefinedCursor Eine der Cursor-Konstanten. * @see Cursor */ @@ -854,13 +962,14 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke } /** - * Setzt den Mauszeiger auf das übergebenen Cursor-Objekt. - * Wenn {@code pCursor} {@code null} ist, wird der Mauszeiger unsichtbar - * gemacht (dies ist dasselbe wie der Aufruf von {@link #hideCursor()}). + * Setzt den Mauszeiger auf das übergebenen Cursor-Objekt. Wenn + * {@code pCursor} {@code null} ist, wird der Mauszeiger unsichtbar gemacht + * (dies ist dasselbe wie der Aufruf von {@link #hideCursor()}). + * * @param pCursor Ein Cursor-Objekt oder {@code null}. - *

- * Nach Aufruf der Methode kann über {@link #cursorVisible} abgefragt werden, - * ob der Cursor zurzeit sichtbar ist oder nicht. + *

+ * Nach Aufruf der Methode kann über {@link #cursorVisible} abgefragt + * werden, ob der Cursor zurzeit sichtbar ist oder nicht. */ public final void setCursor( Cursor pCursor ) { if( pCursor == null && cursorVisible ) { @@ -887,7 +996,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke */ /** - * Die Settings werden einmal beim Erstellten der Zeichenmaschine aufgerufen. + * Die Settings werden einmal beim Erstellten der Zeichenmaschine + * aufgerufen. *

* {@code settings()} wird nur selten benötigt, wenn das Zeichenfenster */ @@ -912,9 +1022,9 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke * Aktualisierungen abhängig von der echten Verzögerung zwischen zwei Frames * zu berechnen. *

- * {@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: + * {@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: *

 	 * shape.move(50*delta, 0.0);
 	 * 
@@ -928,23 +1038,24 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke /** * {@code draw()} wird einmal pro Frame aufgerufen. Bei einer - * {@link #getFramesPerSecond() Framerate} von 60 also in etwa 60-Mal - * pro Sekunde. In der {@code draw}-Methode wird der Inhalt der Ebenen + * {@link #getFramesPerSecond() Framerate} von 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. *

- * {@code draw()} stellt die wichtigste Methode für eine Zeichenmaschine dar, - * da hier die Zeichnung des Programms erstellt wird. + * {@code draw()} stellt die wichtigste Methode für eine Zeichenmaschine + * dar, da hier die Zeichnung des Programms erstellt wird. */ public void draw() { running = !stop_after_update; } /** - * {@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. + * {@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. *

* In {@code teardown()} kann zum Beispiel der Abschlussbildschirm eines * Spiels oder der Abspann einer Animation angezeigt werden, oder mit @@ -954,12 +1065,121 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // Intentionally left blank } + /* + * Task scheduling + */ + + public void scheduleTask( Runnable runnable, int delay ) { + taskQueue.add(new DelayedTask(delay, runnable)); + } + + public void scheduleTask( Runnable runnable, int delay, boolean concurrent ) { + DelayedTask task = new DelayedTask(delay, runnable); + task.concurrent = concurrent; + taskQueue.add(task); + } + + private void runTasks() { + synchronized( taskQueue ) { + DelayedTask task = taskQueue.poll(); + while( task != null ) { + if( task.concurrent ) { + // SwingUtilities.invokeLater(task.runnable); + TaskRunner.run(task.runnable); + } else { + task.runnable.run(); + } + + task = taskQueue.poll(); + } + } + } + /* * Mouse handling */ - @Override - public final void mouseClicked( MouseEvent e ) { - mouseEvent = e; + private void enqueueEvent( InputEvent evt ) { + eventQueue.add(evt); + + if( isPaused() ) { + dispatchEvents(); + } + } + + private void dispatchEvents() { + synchronized( eventQueue ) { + while( !eventQueue.isEmpty() ) { + InputEvent evt = eventQueue.poll(); + + switch( evt.getID() ) { + case KeyEvent.KEY_TYPED: + case KeyEvent.KEY_PRESSED: + case KeyEvent.KEY_RELEASED: + handleKeyEvent((KeyEvent) evt); + break; + + case MouseEvent.MOUSE_CLICKED: + case MouseEvent.MOUSE_PRESSED: + case MouseEvent.MOUSE_RELEASED: + case MouseEvent.MOUSE_MOVED: + case MouseEvent.MOUSE_DRAGGED: + case MouseEvent.MOUSE_WHEEL: + handleMouseEvent((MouseEvent) evt); + break; + } + } + } + } + + private void handleKeyEvent( KeyEvent evt ) { + keyEvent = evt; + key = evt.getKeyChar(); + keyCode = evt.getKeyCode(); + + switch( evt.getID() ) { + case KeyEvent.KEY_TYPED: + keyTyped(evt); + break; + case KeyEvent.KEY_PRESSED: + keyPressed = true; + keyPressed(evt); + break; + case KeyEvent.KEY_RELEASED: + keyPressed = false; + keyReleased(evt); + break; + } + } + + private void handleMouseEvent( MouseEvent evt ) { + mouseEvent = evt; + + switch( evt.getID() ) { + case MouseEvent.MOUSE_CLICKED: + mouseClicked(evt); + break; + case MouseEvent.MOUSE_PRESSED: + mousePressed = true; + mouseButton = evt.getButton(); + mousePressed(evt); + break; + case MouseEvent.MOUSE_RELEASED: + mousePressed = false; + mouseButton = NOBUTTON; + mousePressed(evt); + break; + case MouseEvent.MOUSE_DRAGGED: + //saveMousePosition(evt); + mouseDragged(evt); + break; + case MouseEvent.MOUSE_MOVED: + //saveMousePosition(evt); + mouseMoved(evt); + break; + } + } + + public void mouseClicked( MouseEvent e ) { mouseClicked(); } @@ -967,11 +1187,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // Intentionally left blank } - @Override - public final void mousePressed( MouseEvent e ) { - mouseEvent = e; - mousePressed = true; - mouseButton = e.getButton(); + public void mousePressed( MouseEvent e ) { mousePressed(); } @@ -979,11 +1195,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // Intentionally left blank } - @Override - public final void mouseReleased( MouseEvent e ) { - mouseEvent = e; - mousePressed = false; - mouseButton = NOBUTTON; + public void mouseReleased( MouseEvent e ) { mouseReleased(); } @@ -991,19 +1203,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // Intentionally left blank } - @Override - public final void mouseEntered( MouseEvent e ) { - // Intentionally left blank - } - - @Override - public final void mouseExited( MouseEvent e ) { - // Intentionally left blank - } - - @Override - public final void mouseDragged( MouseEvent e ) { - mouseEvent = e; + public void mouseDragged( MouseEvent e ) { mouseDragged(); } @@ -1011,9 +1211,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // Intentionally left blank } - @Override - public final void mouseMoved( MouseEvent e ) { - mouseEvent = e; + public void mouseMoved( MouseEvent e ) { mouseMoved(); } @@ -1026,8 +1224,8 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke pmouseX = mouseX; pmouseY = mouseY; - mouseX = event.getX(); - mouseY = event.getY(); + mouseX = cmouseX; + mouseY = cmouseY; } } @@ -1045,9 +1243,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke /* * Keyboard handling */ - @Override - public final void keyTyped( KeyEvent e ) { - saveKeys(e); + public void keyTyped( KeyEvent e ) { keyTyped(); } @@ -1055,10 +1251,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // Intentionally left blank } - @Override - public final void keyPressed( KeyEvent e ) { - saveKeys(e); - keyPressed = true; + public void keyPressed( KeyEvent e ) { keyPressed(); } @@ -1066,10 +1259,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // Intentionally left blank } - @Override - public final void keyReleased( KeyEvent e ) { - saveKeys(e); - keyPressed = false; + public void keyReleased( KeyEvent e ) { keyReleased(); } @@ -1077,12 +1267,15 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // Intentionally left blank } - private final void saveKeys( KeyEvent event ) { - keyEvent = event; - key = event.getKeyChar(); - keyCode = event.getKeyCode(); + // Window changes + public void fullscreenChanged() { + // Intentionally left blank } + //// + // Zeichenthread + //// + class Zeichenthread extends Thread { @Override @@ -1125,12 +1318,16 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke // canvas.invalidate(); // frame.repaint(); } + + dispatchEvents(); } + runTasks(); + // delta time in ns long afterTime = System.nanoTime(); long dt = afterTime - beforeTime; - long sleep = ((1000000000L / framesPerSecond) - dt) - overslept; + long sleep = ((1000000000L / framesPerSecondInternal) - dt) - overslept; if( sleep > 0 ) { @@ -1143,13 +1340,13 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke overslept = (System.nanoTime() - afterTime) - sleep; } else { overslept = 0L; - } _tick += 1; _runtime = System.currentTimeMillis() - start; tick = _tick; runtime = _runtime; + framesPerSecond = framesPerSecondInternal; if( pause_pending ) { state = Options.AppState.PAUSED; @@ -1159,6 +1356,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke state = Options.AppState.STOPPED; teardown(); + cleanup(); state = Options.AppState.TERMINATED; if( quitAfterTeardown ) { @@ -1184,4 +1382,94 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke } + class DelayedTask implements Delayed { + + long startTime; // in ms + + Runnable runnable; + + boolean concurrent = false; + + public DelayedTask( int delay, Runnable runnable ) { + this.startTime = System.currentTimeMillis() + delay; + this.runnable = runnable; + } + + @Override + public long getDelay( TimeUnit unit ) { + int diff = (int) (startTime - System.currentTimeMillis()); + return unit.convert(diff, TimeUnit.MILLISECONDS); + } + + @Override + public int compareTo( Delayed o ) { + return (int) (startTime - ((DelayedTask) o).startTime); + } + + } + + class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener{ + @Override + public void mouseClicked( MouseEvent e ) { + enqueueEvent(e); + } + + @Override + public void mousePressed( MouseEvent e ) { + enqueueEvent(e); + } + + @Override + public void mouseReleased( MouseEvent e ) { + enqueueEvent(e); + } + + @Override + public void mouseEntered( MouseEvent e ) { + // Intentionally left blank + } + + @Override + public void mouseExited( MouseEvent e ) { + // Intentionally left blank + } + + @Override + public void mouseDragged( MouseEvent e ) { + cmouseX = e.getX(); + cmouseY = e.getY(); + enqueueEvent(e); + } + + @Override + public void mouseMoved( MouseEvent e ) { + cmouseX = e.getX(); + cmouseY = e.getY(); + enqueueEvent(e); + } + + @Override + public void keyTyped( KeyEvent e ) { + enqueueEvent(e); + } + + @Override + public void keyPressed( KeyEvent e ) { + enqueueEvent(e); + } + + @Override + public void keyReleased( KeyEvent e ) { + enqueueEvent(e); + } + + @Override + public void mouseWheelMoved( MouseWheelEvent e ) { + // enqueueEvent(e); + } + + } + + private static final Log LOG = Log.getLogger(Zeichenmaschine.class); + } diff --git a/src/schule/ngb/zm/media/Music.java b/src/schule/ngb/zm/media/Music.java new file mode 100644 index 0000000..01ae8f2 --- /dev/null +++ b/src/schule/ngb/zm/media/Music.java @@ -0,0 +1,191 @@ +package schule.ngb.zm.media; + +import schule.ngb.zm.tasks.TaskRunner; +import schule.ngb.zm.util.ResourceStreamProvider; + +import javax.sound.sampled.*; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Music { + + // size of the byte buffer used to read/write the audio stream + private static final int BUFFER_SIZE = 4096; + + + private boolean playing = false; + + private boolean looping = false; + + private String audioSource; + + private AudioInputStream audioStream; + + private SourceDataLine audioLine; + + private float volume = 1.0f; + + public Music( String source ) { + this.audioSource = source; + } + + public boolean isPlaying() { + return playing; + } + + public boolean isLooping() { + if( !playing ) { + looping = false; + } + return looping; + } + + public void setVolume( double volume ) { + this.volume = (float) volume; + if( audioLine != null ) { + applyVolume(); + } + } + + private void applyVolume() { + FloatControl gainControl = + (FloatControl) audioLine.getControl(FloatControl.Type.MASTER_GAIN); + + float vol = 20f * (float) Math.log10(volume); + // vol = (float) Constants.limit(vol, gainControl.getMinimum(), gainControl.getMaximum()); + gainControl.setValue(vol); + } + + public void playOnce() { + if( openLine() ) { + TaskRunner.run(new Runnable() { + @Override + public void run() { + stream(); + } + }); + } + } + + public void playOnceAndWait() { + if( openLine() ) { + stream(); + } + } + + public void loop() { + looping = true; + playOnce(); + } + + public void stop() { + playing = false; + looping = false; + dispose(); + } + + public void dispose() { + if( audioLine != null ) { + if( audioLine.isRunning() ) { + playing = false; + audioLine.stop(); + } + if( audioLine.isOpen() ) { + audioLine.drain(); + audioLine.close(); + + } + } + try { + if( audioStream != null ) { + audioStream.close(); + } + } catch( IOException ex ) {} + + audioLine = null; + audioStream = null; + } + + private void stream() { + audioLine.start(); + playing = true; + + byte[] bytesBuffer = new byte[BUFFER_SIZE]; + int bytesRead = -1; + + try { + while (playing && (bytesRead = audioStream.read(bytesBuffer)) != -1) { + audioLine.write(bytesBuffer, 0, bytesRead); + } + + audioLine.drain(); + audioLine.stop(); + } catch( IOException ex ) { + LOGGER.warning("Error while playing Music source <" + audioSource + ">"); + LOGGER.throwing("Music", "stream", ex); + } + + // Wait for the remaining audio to play + /*while( audioLine.isRunning() ) { + try { + Thread.sleep(10); + } catch( InterruptedException ex ) { + // Just keep waiting ... + } + }*/ + + playing = false; + streamingStopped(); + } + + private boolean openLine() { + if( audioLine != null ) { + return true; + } + + try { + InputStream in = ResourceStreamProvider.getResourceStream(audioSource); + if( in != null ) { + final AudioInputStream inStream = AudioSystem.getAudioInputStream(in); + AudioFormat format = inStream.getFormat(); + + final int ch = format.getChannels(); + final float rate = format.getSampleRate(); + AudioFormat outFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch*2, rate, false); + + DataLine.Info info = new DataLine.Info(SourceDataLine.class, outFormat); + + audioLine = (SourceDataLine) AudioSystem.getLine(info); + audioLine.open(outFormat); + applyVolume(); + + audioStream = AudioSystem.getAudioInputStream(outFormat, inStream); + return true; + } else { + LOGGER.warning("Sound source <" + audioSource + "> could not be played: No audio source found."); + } + } catch( UnsupportedAudioFileException ex ) { + LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: The specified audio file is not supported.", ex); + } catch( LineUnavailableException ex ) { + LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Audio line for playing back is unavailable.", ex); + } catch( IOException ex ) { + LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Error playing the audio file.", ex); + } + return false; + } + + private void streamingStopped() { + playing = false; + dispose(); + + if( looping ) { + playOnce(); + } + } + + //private static final Logger LOGGER = Logger.getLogger("schule.ngb.zm.media.Music"); + private static final Logger LOGGER = Logger.getLogger(Music.class.getName()); + +} diff --git a/src/schule/ngb/zm/media/Sound.java b/src/schule/ngb/zm/media/Sound.java new file mode 100644 index 0000000..004207f --- /dev/null +++ b/src/schule/ngb/zm/media/Sound.java @@ -0,0 +1,204 @@ +package schule.ngb.zm.media; + +import schule.ngb.zm.util.ResourceStreamProvider; + +import javax.sound.sampled.*; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + +public class Sound { + + private boolean playing = false; + + private boolean looping = false; + + private String audioSource; + + private Clip audioClip; + + private boolean disposeAfterPlay = false; + + private float volume = 1.0f; + + public Sound( String source ) { + this.audioSource = source; + } + + public boolean isPlaying() { + // return audioClip != null && audioClip.isRunning(); + return playing; + } + + public boolean isLooping() { + if( !playing ) { + looping = false; + } + return looping; + } + + public void setVolume( double volume ) { + this.volume = (float) volume; + if( audioClip != null ) { + applyVolume(); + } + } + + private void applyVolume() { + FloatControl gainControl = + (FloatControl) audioClip.getControl(FloatControl.Type.MASTER_GAIN); + + float vol = 20f * (float) Math.log10(volume); + // vol = (float) Constants.limit(vol, gainControl.getMinimum(), gainControl.getMaximum()); + gainControl.setValue(vol); + } + + public void stop() { + looping = false; + if( audioClip.isRunning() ) { + audioClip.stop(); + } + playing = false; + } + + public void play() { + if( this.openClip() ) { + audioClip.start(); + playing = true; + } + } + + public void playOnce() { + disposeAfterPlay = true; + play(); + } + + public void playOnceAndWait() { + disposeAfterPlay = true; + playAndWait(); + } + + public void playAndWait() { + this.play(); + + long audioLen = audioClip.getMicrosecondLength(); + while( playing ) { + try { + long ms = (audioLen - audioClip.getMicrosecondPosition()) / 1000L; + Thread.sleep(ms); + } catch( InterruptedException ex ) { + // Ignore + } + } + + audioClip.close(); + } + + public void loop() { + loop(Clip.LOOP_CONTINUOUSLY); + } + + public void loop( int count ) { + int loopCount = count; + if( loopCount != Clip.LOOP_CONTINUOUSLY ) { + if( loopCount <= 0 ) { + return; + } + // Adjust Number of loops + loopCount -= 1; + } + if( openClip() ) { + looping = true; + audioClip.loop(loopCount); + playing = true; + } + } + + public void dispose() { + if( audioClip != null ) { + if( audioClip.isRunning() ) { + audioClip.stop(); + } + audioClip.close(); + audioClip = null; + } + } + + private boolean openClip() { + if( audioClip != null ) { + audioClip.setFramePosition(0); + return true; + } + + try { + InputStream in = ResourceStreamProvider.getResourceStream(audioSource); + if( in != null ) { + AudioInputStream audioStream = AudioSystem.getAudioInputStream(in); + AudioFormat format = audioStream.getFormat(); + DataLine.Info info = new DataLine.Info(Clip.class, format); + + audioClip = (Clip) AudioSystem.getLine(info); + audioClip.addLineListener(new LineListener() { + @Override + public void update( LineEvent event ) { + if( event.getType() == LineEvent.Type.STOP ) { + playbackStopped(); + } + } + }); + audioClip.open(audioStream); + applyVolume(); + return true; + } else { + LOGGER.warning("Sound source " + audioSource + " could not be played: No audio source found."); + } + } catch( UnsupportedAudioFileException ex ) { + LOGGER.warning("Sound source " + audioSource + " could not be played: The specified audio file is not supported."); + LOGGER.throwing("Sound", "openClip", ex); + } catch( LineUnavailableException ex ) { + LOGGER.warning("Sound source " + audioSource + " could not be played: Audio line for playing back is unavailable."); + LOGGER.throwing("Sound", "openClip", ex); + } catch( IOException ex ) { + LOGGER.warning("Sound source " + audioSource + " could not be played: Error playing the audio file."); + LOGGER.throwing("Sound", "openClip", ex); + } + return false; + } + + /*@Override + public void update( LineEvent event ) { + LineEvent.Type type = event.getType(); + + if( type == LineEvent.Type.START ) { + playing = true; + } else if( type == LineEvent.Type.STOP ) { + playing = false; + if( disposeAfterPlay ) { + this.dispose(); + disposeAfterPlay = false; + } + } + }*/ + + private void playbackStopped() { + playing = false; + if( disposeAfterPlay ) { + this.dispose(); + disposeAfterPlay = false; + } + } + + /* + public void addLineListener( LineListener listener ) { + if( audioClip == null ) { + openClip(); + } + if( audioClip != null ) { + audioClip.addLineListener(listener); + } + } + */ + + private static final Logger LOGGER = Logger.getLogger(Sound.class.getName()); + +} diff --git a/src/schule/ngb/zm/shapes/FilledShape.java b/src/schule/ngb/zm/shapes/FilledShape.java index 1a34d65..57ca862 100644 --- a/src/schule/ngb/zm/shapes/FilledShape.java +++ b/src/schule/ngb/zm/shapes/FilledShape.java @@ -15,7 +15,7 @@ public abstract class FilledShape extends StrokedShape { } public void setFillColor( Color color, int alpha ) { - fillColor = new Color(color, alpha); + setFillColor(new Color(color, alpha)); } public void setFillColor( int gray ) { @@ -23,7 +23,7 @@ public abstract class FilledShape extends StrokedShape { } public void noFill() { - fillColor = null; + setFillColor(null); } public void setFillColor( int gray, int alpha ) { diff --git a/src/schule/ngb/zm/shapes/Picture.java b/src/schule/ngb/zm/shapes/Picture.java index 5b573b5..5141817 100644 --- a/src/schule/ngb/zm/shapes/Picture.java +++ b/src/schule/ngb/zm/shapes/Picture.java @@ -14,9 +14,9 @@ public class Picture extends Shape { private BufferedImage image; - private double width; + private double imgWidth; - private double height; + private double imgHeight; public Picture( String source ) { this(0, 0, source); @@ -30,8 +30,8 @@ public class Picture extends Shape { throw new IllegalArgumentException("Could not initialize image from source " + source); } - width = image.getWidth(); - height = image.getHeight(); + imgWidth = image.getWidth(); + imgHeight = image.getHeight(); this.anchor = Options.Direction.CENTER; } @@ -55,25 +55,25 @@ public class Picture extends Shape { g.drawImage(pic.image, 0, 0, null); g.dispose(); - width = image.getWidth(); - height = image.getHeight(); + imgWidth = image.getWidth(); + imgHeight = image.getHeight(); } } public double getWidth() { - return width; + return imgWidth; } public void setWidth( double width ) { - scale(width / this.width); + scale(width / this.imgWidth); } public double getHeight() { - return height; + return imgHeight; } public void setHeight( double height ) { - scale(height / this.height); + scale(height / this.imgHeight); } public BufferedImage getImage() { @@ -83,13 +83,13 @@ public class Picture extends Shape { @Override public void scale( double factor ) { super.scale(factor); - width *= factor; - height *= factor; + imgWidth *= factor; + imgHeight *= factor; } @Override public java.awt.Shape getShape() { - return new Rectangle2D.Double(0, 0, width, height); + return new Rectangle2D.Double(0, 0, imgWidth, imgHeight); } /* @@ -161,8 +161,8 @@ public class Picture extends Shape { } AffineTransform current = graphics.getTransform(); - graphics.transform(getTransform()); - graphics.drawImage(image, 0, 0, (int) width, (int) height, null); + graphics.transform(transform); + graphics.drawImage(image, 0, 0, (int) imgWidth, (int) imgHeight, null); graphics.setTransform(current); } diff --git a/src/schule/ngb/zm/shapes/Shape.java b/src/schule/ngb/zm/shapes/Shape.java index f2f5414..7d3d3cc 100644 --- a/src/schule/ngb/zm/shapes/Shape.java +++ b/src/schule/ngb/zm/shapes/Shape.java @@ -52,8 +52,8 @@ public abstract class Shape extends FilledShape { * Die Breite einer Form ist immer die Breite ihrer Begrenzung, bevor * Drehungen und andere Transformationen auf sei angewandt wurden. *

- * Die Begrenzungen der tatsächlich gezeichneten Form kann mit {@link #getBounds()} - * abgerufen werden. + * Die Begrenzungen der tatsächlich gezeichneten Form kann mit + * {@link #getBounds()} abgerufen werden. * * @return */ @@ -65,8 +65,8 @@ public abstract class Shape extends FilledShape { * Die Höhe einer Form ist immer die Höhe ihrer Begrenzung, bevor * Drehungen und andere Transformationen auf sei angewandt wurden. *

- * Die Begrenzungen der tatsächlich gezeichneten Form kann mit {@link #getBounds()} - * abgerufen werden. + * Die Begrenzungen der tatsächlich gezeichneten Form kann mit + * {@link #getBounds()} abgerufen werden. * * @return */ @@ -104,9 +104,10 @@ public abstract class Shape extends FilledShape { * Setzt den Ankerpunkt der Form basierend auf der angegebenen * {@link Options.Direction Richtung}. *

- * Für das Setzen des Ankers muss das {@link #getBounds() begrenzende - * Rechteck} berechnet werden. Unterklassen sollten die Methode - * überschreiben, wenn der Anker auch direkt gesetzt werden kann. + * Für das Setzen des Ankers muss das + * {@link #getBounds() begrenzende Rechteck} berechnet werden. Unterklassen + * sollten die Methode überschreiben, wenn der Anker auch direkt gesetzt + * werden kann. * * @param anchor */ @@ -116,39 +117,58 @@ public abstract class Shape extends FilledShape { } } + protected static Point2D.Double getAnchorPoint( double width, double height, Options.Direction anchor ) { + double wHalf = width * .5, hHalf = height * .5; + + // anchor == CENTER + Point2D.Double anchorPoint = new Point2D.Double( + wHalf + wHalf * anchor.x, + hHalf + hHalf * anchor.y + ); + + return anchorPoint; + } + /** - * Bestimmt den Ankerpunkt der Form relativ zur oberen linken Ecke und - * abhängig vom gesetzten {@link #setAnchor(Options.Direction) Anker}. + * Bestimmt den Ankerpunkt der Form relativ zum gesetzten + * {@link #setAnchor(Options.Direction) Ankerpunkt}. + * + * @param anchor Die Richtung des Ankerpunktes. + * @return Der relative Ankerpunkt. */ public Point2D.Double getAnchorPoint( Options.Direction anchor ) { - Point2D.Double anchorpoint = new Point2D.Double(0, 0); + double wHalf = getWidth() * .5, hHalf = getHeight() * .5; - double bHalf = getWidth() * .5, hHalf = getHeight() * .5; // anchor == CENTER - anchorpoint.x = bHalf; - anchorpoint.y = hHalf; + Point2D.Double anchorPoint = new Point2D.Double( + wHalf * (anchor.x - this.anchor.x), + hHalf * (anchor.y - this.anchor.y) + ); - if( NORTH.in(anchor) ) { - anchorpoint.y -= hHalf; - } - if( SOUTH.in(anchor) ) { - anchorpoint.y += hHalf; - } - if( WEST.in(anchor) ) { - anchorpoint.x -= bHalf; - } - if( EAST.in(anchor) ) { - anchorpoint.x += bHalf; - } + return anchorPoint; + } - return anchorpoint; + /** + * Ermittelt die absoluten Koordinaten eines angegebenen + * {@link #setAnchor(Options.Direction) Ankers}. + * + * @param anchor Die Richtung des Ankerpunktes. + * @return Der absolute Ankerpunkt. + */ + public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) { + // TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen. + Point2D.Double ap = getAnchorPoint(anchor); + ap.x += getX(); + ap.y += getY(); + return ap; } /** * Kopiert die Eigenschaften der übergebenen Form in diese. *

- * Unterklassen sollten diese Methode überschreiben, um weitere Eigenschaften - * zu kopieren (zum Beispiel den Radius eines Kreises). Mit dem Aufruf + * Unterklassen sollten diese Methode überschreiben, um weitere + * Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises). Mit + * dem Aufruf * super.copyFrom(shape) sollten die Basiseigenschaften * kopiert werden. * @@ -186,6 +206,96 @@ public abstract class Shape extends FilledShape { this.y = y; } + public void moveTo( Shape shape ) { + moveTo(shape.getX(), shape.getY()); + } + + public void moveTo( Shape shape, Options.Direction dir ) { + moveTo(shape, dir, 0.0); + } + + /** + * Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen Form. + * Mit {@code buff} kann ein zusätzlicher Abstand angegeben werden, um den + * die Form entlang des Ankerpunktes {@code anchor} verschoben werden soll. + * Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um + * {@code buff} nach oben verschoben. + * + * @param shape + * @param anchor + * @param buff + */ + public void moveTo( Shape shape, Options.Direction dir, double buff ) { + Point2D ap = shape.getAbsAnchorPoint(dir); + + this.x = ap.getX() + dir.x * buff; + this.y = ap.getY() + dir.y * buff; + } + + /** + * Richtet die Form entlang der angegebenen Richtung am Rand der + * Zeichenfläche aus. + * + * @param dir Die Richtung der Ausrichtung. + */ + public void alignTo( Options.Direction dir ) { + alignTo(dir, 0.0); + } + + public void alignTo( Options.Direction dir, double buff ) { + Point2D anchorShape = Shape.getAnchorPoint(width, height, dir); + Point2D anchorThis = this.getAbsAnchorPoint(dir); + + this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff; + this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff; + } + + public void alignTo( Shape shape, Options.Direction dir ) { + alignTo(shape, dir, 0.0); + } + + /** + * Richtet die Form entlang der angegebenen Richtung an einer anderen Form + * aus. Für {@code DOWN} wird beispielsweise die y-Koordinate der unteren + * Kante dieser Form an der unteren Kante der angegebenen Form {@code shape} + * ausgerichtet. Die x-Koordinate wird nicht verändert. {@code buff} gibt + * einen Abstand ab, um den diese From versetzt ausgerichtet werden soll. + * + * @param shape + * @param dir + * @param buff + */ + public void alignTo( Shape shape, Options.Direction dir, double buff ) { + Point2D anchorShape = shape.getAbsAnchorPoint(dir); + Point2D anchorThis = this.getAbsAnchorPoint(dir); + + this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff; + this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff; + } + + public void nextTo( Shape shape, Options.Direction dir ) { + nextTo(shape, dir, STD_BUFFER); + } + + /** + * Bewegt die Form neben eine andere in Richtung des angegebenen + * Ankerpunktes. Im Gegensatz zu + * {@link #moveTo(Shape, Options.Direction, double)} wird die Breite bzw. + * Höhe der Formen berücksichtigt und die Formen so platziert, dass keine + * Überlappungen vorhanden sind. + * + * @param shape + * @param dir + * @param buff + */ + public void nextTo( Shape shape, Options.Direction dir, double buff ) { + Point2D anchorShape = shape.getAbsAnchorPoint(dir); + Point2D anchorThis = this.getAbsAnchorPoint(dir.inverse()); + + this.x += (anchorShape.getX() - anchorThis.getX()) + dir.x * buff; + this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff; + } + public void scale( double factor ) { scale = factor; } @@ -210,7 +320,7 @@ public abstract class Shape extends FilledShape { this.rotation += angle % 360; // Rotate x/y position - double x1 = this.x-x, y1 = this.y-y; + double x1 = this.x - x, y1 = this.y - y; double rad = Math.toRadians(angle); double x2 = x1 * Math.cos(rad) - y1 * Math.sin(rad); @@ -225,13 +335,15 @@ public abstract class Shape extends FilledShape { }*/ public AffineTransform getTransform() { - Point2D.Double anchorPoint = getAnchorPoint(this.anchor); + // Point2D.Double anchorPoint = getAnchorPoint(); + Point2D.Double basePoint = getAnchorPoint(Options.Direction.NORTHWEST); AffineTransform transform = new AffineTransform(); transform.translate(x, y); transform.rotate(Math.toRadians(rotation)); //transform.scale(scale, scale); - transform.translate(-anchorPoint.x, -anchorPoint.y); + //transform.translate(-anchorPoint.x, -anchorPoint.y); + transform.translate(basePoint.x, basePoint.y); return transform; } @@ -246,8 +358,9 @@ public abstract class Shape extends FilledShape { } /** - * Zeichnet die Form, aber wendet zuvor noch eine zusätzliche Transformations- - * matrix an. Wird u.A. von der {@link ShapeGroup} verwendet. + * Zeichnet die Form, aber wendet zuvor noch eine zusätzliche + * Transformations- matrix an. Wird u.A. von der {@link ShapeGroup} + * verwendet. * * @param graphics * @param transform diff --git a/src/schule/ngb/zm/shapes/ShapeGroup.java b/src/schule/ngb/zm/shapes/ShapeGroup.java index b60923a..46a56d5 100644 --- a/src/schule/ngb/zm/shapes/ShapeGroup.java +++ b/src/schule/ngb/zm/shapes/ShapeGroup.java @@ -2,20 +2,40 @@ package schule.ngb.zm.shapes; import schule.ngb.zm.Options; -import java.awt.*; +import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +/** + * Eine {@code ShapwGroup} ist eine Sammlung von {@link Shape}s, die gemeinsam + * eine Gruppe bilden. Transformationen der Gruppe werden nach den + * Transformationen der einzelnen Formen auf die gesammte Gruppe angewandt. So + * kann ein Rechteck in der Gruppe zunächst um 45 Grad gedreht werden und dann + * die Gruppe um -45 Grad. Das Rechteck wird dann wieder waagerecht dargestellt, + * während alle anderen Formen der Gruppe nun gedreht erscheinen. Da die Gruppe + * selbst ein eigenes Drehzentrum hat, können so komplexe Strukturen als eine + * eigene, zusammenhängende Form verwendet werden. (Im Gegensatz zu einer + * {@link CustomShape} haben Formgruppen den Vorteil, dass die einzelnen Formen + * individuelle Farben und Konturen bekommen können.) + *

+ * Da die Größe der Gruppe durch seine zugewiesenen Formen fefstgelegt wird, + * sollten Modifikation wie {@link #setAnchor(Options.Direction)}, + * {@link #nextTo(Shape, Options.Direction)} oder + * {@link #alignTo(Shape, Options.Direction, double)}, die die Größe der Gruppe + * benötigen, erst nach Hinzufügen der Gruppenelemente ausgeführt werden. + * Nachdem sich die Zusammensetzung der Gruppe geändert hat, muss die Gruppe + * ggf. neu ausgerichtet werden. + */ public class ShapeGroup extends Shape { private List shapes; - protected double width = -1.0; + private double groupWidth = -1.0; - protected double height = -1.0; + private double groupHeight = -1.0; public ShapeGroup() { super(); @@ -33,7 +53,6 @@ public class ShapeGroup extends Shape { for( Shape pShape : shapes ) { this.shapes.add(pShape); } - this.anchor = Options.Direction.CENTER; } public Shape copy() { @@ -68,9 +87,9 @@ public class ShapeGroup extends Shape { public List getShapes( Class typeClass ) { LinkedList list = new LinkedList<>(); - for( Shape s: shapes ) { + for( Shape s : shapes ) { if( typeClass.getClass().isInstance(s) ) { - list.add((ShapeType)s); + list.add((ShapeType) s); } } return list; @@ -99,23 +118,23 @@ public class ShapeGroup extends Shape { @Override public double getWidth() { - if( width < 0 ) { + if( groupWidth < 0 ) { calculateBounds(); } - return width; + return groupWidth; } @Override public double getHeight() { - if( height < 0 ) { + if( groupHeight < 0 ) { calculateBounds(); } - return height; + return groupHeight; } private void invalidateBounds() { - width = -1.0; - height = -1.0; + groupWidth = -1.0; + groupHeight = -1.0; } private void calculateBounds() { @@ -124,13 +143,13 @@ public class ShapeGroup extends Shape { for( Shape pShape : shapes ) { Bounds bounds = pShape.getBounds(); minx = Math.min(minx, bounds.x); - maxx = Math.max(maxx, bounds.x+bounds.width); + maxx = Math.max(maxx, bounds.x + bounds.width); miny = Math.min(miny, bounds.y); - maxy = Math.max(maxy, bounds.y+bounds.height); + maxy = Math.max(maxy, bounds.y + bounds.height); } - width = maxx-minx; - height = maxy-miny; + groupWidth = maxx - minx; + groupHeight = maxy - miny; } @Override @@ -156,7 +175,7 @@ public class ShapeGroup extends Shape { verzerrung.translate(-anchor.x, -anchor.y); */ - for( Shape f: shapes ) { + for( Shape f : shapes ) { AffineTransform af = f.getTransform(); af.preConcatenate(transform); f.draw(graphics, af); diff --git a/src/schule/ngb/zm/shapes/ShapesLayer.java b/src/schule/ngb/zm/shapes/ShapesLayer.java index 6fdfed9..8626d12 100644 --- a/src/schule/ngb/zm/shapes/ShapesLayer.java +++ b/src/schule/ngb/zm/shapes/ShapesLayer.java @@ -3,6 +3,7 @@ package schule.ngb.zm.shapes; import schule.ngb.zm.Layer; import java.awt.Graphics2D; +import java.util.Collection; import java.util.LinkedList; public class ShapesLayer extends Layer { @@ -48,17 +49,35 @@ public class ShapesLayer extends Layer { return result; } - public void add( Shape... shape ) { + public void add( Shape... shapes ) { synchronized( shapes ) { - for( Shape f : shape ) { - shapes.add(f); + for( Shape s : shapes ) { + this.shapes.add(s); } } } - public void remove( Shape shape ) { + public void add( Collection shapes ) { synchronized( shapes ) { - shapes.remove(shape); + for( Shape s : shapes ) { + this.shapes.add(s); + } + } + } + + public void remove( Shape... shapes ) { + synchronized( shapes ) { + for( Shape s: shapes ) { + this.shapes.remove(s); + } + } + } + + public void remove( Collection shapes ) { + synchronized( shapes ) { + for( Shape s: shapes ) { + this.shapes.remove(s); + } } } diff --git a/src/schule/ngb/zm/shapes/StrokedShape.java b/src/schule/ngb/zm/shapes/StrokedShape.java index 1d9ab43..1dc1290 100644 --- a/src/schule/ngb/zm/shapes/StrokedShape.java +++ b/src/schule/ngb/zm/shapes/StrokedShape.java @@ -27,7 +27,7 @@ public abstract class StrokedShape extends Constants implements Drawable { } public void setStrokeColor( Color color , int alpha ) { - this.strokeColor = new Color(color, alpha); + setStrokeColor(new Color(color, alpha)); } public void setStrokeColor( int gray ) { @@ -35,7 +35,7 @@ public abstract class StrokedShape extends Constants implements Drawable { } public void noStroke() { - strokeColor = null; + setStrokeColor(null); } public void setStrokeColor( int gray, int alpha ) { diff --git a/src/schule/ngb/zm/shapes/Text.java b/src/schule/ngb/zm/shapes/Text.java index 52dd658..41ce092 100644 --- a/src/schule/ngb/zm/shapes/Text.java +++ b/src/schule/ngb/zm/shapes/Text.java @@ -1,8 +1,13 @@ package schule.ngb.zm.shapes; +import schule.ngb.zm.Color; import schule.ngb.zm.Options; +import schule.ngb.zm.util.FontLoader; -import java.awt.*; +import java.awt.Canvas; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; @@ -12,16 +17,33 @@ public class Text extends Shape { protected Font font; + protected Color fontColor = BLACK; + protected int width = 0, height = 0, ascent = 0; public Text( double x, double y, String text ) { this(x, y, text, new Font(Font.SANS_SERIF, Font.PLAIN, STD_FONTSIZE)); } + public Text( double x, double y, String text, String fontname ) { + super(x, y); + Font userfont = FontLoader.loadFont(fontname); + if( userfont != null ) { + font = userfont; + } else { + font = new Font(Font.SANS_SERIF, Font.PLAIN, STD_FONTSIZE); + } + setText(text); + fillColor = null; + strokeColor = null; + anchor = Options.Direction.CENTER; + } public Text( double x, double y, String text, Font font ) { super(x, y); this.font = font; setText(text); + fillColor = null; + strokeColor = null; anchor = Options.Direction.CENTER; } @@ -38,6 +60,18 @@ public class Text extends Shape { return height; } + public void setFont( String fontname ) { + Font newFont = FontLoader.loadFont(fontname); + if( newFont != null ) { + setFont(newFont); + } + } + + public void setFont( Font newFont ) { + font = newFont.deriveFont(font.getSize2D()); + calculateBounds(); + } + public Font getFont() { return font; } @@ -47,6 +81,10 @@ public class Text extends Shape { calculateBounds(); } + public double getFontsize() { + return font.getSize2D(); + } + public String getText() { return text; } @@ -56,6 +94,46 @@ public class Text extends Shape { calculateBounds(); } + public Color getFontColor() { + return fontColor; + } + + public void setFontColor( Color color ) { + if( color != null ) { + fontColor = color; + } else { + fontColor = BLACK; + } + } + + public void setFontColor( Color color, int alpha ) { + if( color != null ) { + fontColor = new Color(color, alpha); + } else { + fontColor = BLACK; + } + } + + public void setFontColor( int gray ) { + setFontColor(gray, gray, gray, 255); + } + + public void setFontColor( int gray, int alpha ) { + setFontColor(gray, gray, gray, alpha); + } + + public void setFontColor( int red, int green, int blue ) { + setFontColor(red, green, blue, 255); + } + + public void setFontColor( int red, int green, int blue, int alpha ) { + setFontColor(new Color(red, green, blue, alpha)); + } + + public void resetFontColor() { + setFontColor(BLACK); + } + private void calculateBounds() { //GraphicsDevice gd; //gd.getDefaultConfiguration().createCompatibleImage(1,1); @@ -64,6 +142,7 @@ public class Text extends Shape { width = metrics.stringWidth(text); //height = metrics.getHeight(); height = metrics.getDescent() + metrics.getAscent(); + ascent = metrics.getMaxAscent(); } public Shape copy() { @@ -100,18 +179,32 @@ public class Text extends Shape { // Aktuelle Werte speichern Font currentFont = graphics.getFont(); - Color currentColor = graphics.getColor(); + java.awt.Color currentColor = graphics.getColor(); AffineTransform af = graphics.getTransform(); - - // Neue Werte setzen - graphics.setFont(font); - graphics.setColor(strokeColor.getJavaColor()); graphics.transform(transform); - // Draw text - //FontMetrics fm = graphics.getFontMetrics(); - //graphics.drawString(text, (float) (x - fm.stringWidth(text)/2.0), (float) (y + fm.getDescent())); - graphics.drawString(text, 0, 0); + // Hintergrund + if( fillColor != null && fillColor.getAlpha() > 0 ) { + graphics.setColor(fillColor.getJavaColor()); + graphics.fillRect(0, 0, width, height); + } + if( strokeColor != null && strokeColor.getAlpha() > 0 + && strokeWeight > 0.0 ) { + graphics.setColor(strokeColor.getJavaColor()); + graphics.setStroke(createStroke()); + graphics.drawRect(0, 0, width, height); + } + + // Neue Werte setzen + if( font != null && fontColor != null && fontColor.getAlpha() > 0 ) { + graphics.setFont(font); + graphics.setColor(fontColor.getJavaColor()); + + // Draw text + //FontMetrics fm = graphics.getFontMetrics(); + //graphics.drawString(text, (float) (x - fm.stringWidth(text)/2.0), (float) (y + fm.getDescent())); + graphics.drawString(text, 0, ascent); + } // Alte Werte wiederherstellen graphics.setTransform(af); diff --git a/src/schule/ngb/zm/tasks/TaskRunner.java b/src/schule/ngb/zm/tasks/TaskRunner.java new file mode 100644 index 0000000..340ffd1 --- /dev/null +++ b/src/schule/ngb/zm/tasks/TaskRunner.java @@ -0,0 +1,76 @@ +package schule.ngb.zm.tasks; + +import javax.swing.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * Führt Aufgaben (Tasks) parallel zum Hauptprogramm aus. + */ +public class TaskRunner { + + private static final int POOL_SIZE = 4; + + private static final int SHUTDOWN_TIME = 100; + + + private static TaskRunner runner; + + public static TaskRunner getTaskRunner() { + if( runner == null ) { + runner = new TaskRunner(); + } + return runner; + } + + public static void run( Runnable task ) { + TaskRunner r = getTaskRunner(); + r.pool.execute(task); + } + + public static void schedule( Runnable task, int ms ) { + TaskRunner r = getTaskRunner(); + r.pool.schedule(task, ms, TimeUnit.MILLISECONDS); + } + + public static void invokeLater( Runnable task ) { + SwingUtilities.invokeLater(task); + } + + public static void shutdown() { + if( runner != null ) { + runner.pool.shutdown(); + try { + runner.pool.awaitTermination(SHUTDOWN_TIME, TimeUnit.MILLISECONDS); + } catch( InterruptedException ex ) { + + } finally { + if( !runner.pool.isTerminated() ) { + runner.pool.shutdownNow(); + } + } + } + } + + ScheduledExecutorService pool; + + private TaskRunner() { + //pool = new ScheduledThreadPoolExecutor(4); + pool = Executors.newScheduledThreadPool(POOL_SIZE, new ThreadFactory() { + private final ThreadFactory threadFactory = Executors.defaultThreadFactory(); + + @Override + public Thread newThread( Runnable r ) { + Thread t = threadFactory.newThread(r); + t.setDaemon(true); + return t; + } + }); + } + + private static final Logger LOGGER = Logger.getLogger(TaskRunner.class.getName()); + +} diff --git a/src/schule/ngb/zm/util/FontLoader.java b/src/schule/ngb/zm/util/FontLoader.java new file mode 100644 index 0000000..a71dbe0 --- /dev/null +++ b/src/schule/ngb/zm/util/FontLoader.java @@ -0,0 +1,59 @@ +package schule.ngb.zm.util; + +import java.awt.Font; +import java.awt.FontFormatException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public class FontLoader { + + private static final Map fontCache = new ConcurrentHashMap<>(); + + public static Font loadFont( String source ) { + Objects.requireNonNull(source, "Font source may not be null"); + if( source.length() == 0 ) { + throw new IllegalArgumentException("Font source may not be empty."); + } + + if( fontCache.containsKey(source) ) { + LOG.trace("Retrieved font <%s> from font cache.", source); + return fontCache.get(source); + } + + // Look for System fonts + Font font = Font.decode(source); + if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) { + fontCache.put(source, font); + LOG.debug("Loaded system font for <%s>.", source); + return font; + } else { + font = null; + } + + // Load userfonts + try( InputStream in = ResourceStreamProvider.getResourceStream(source) ) { + font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN); + + if( font != null ) { + fontCache.put(source, font); + //ge.registerFont(font); + } + LOG.debug("Loaded custom font from <%s>.", source); + } catch( IOException ioex ) { + LOG.error(ioex, "Error loading custom font file from source <%s>.", source); + } catch( FontFormatException ffex ) { + LOG.error(ffex, "Error creating custom font from source <%s>.", source); + } + + return font; + } + + private FontLoader() { + } + + private static final Log LOG = Log.getLogger(FontLoader.class); + +} diff --git a/src/schule/ngb/zm/util/ImageLoader.java b/src/schule/ngb/zm/util/ImageLoader.java index ef512ed..3bddbfb 100644 --- a/src/schule/ngb/zm/util/ImageLoader.java +++ b/src/schule/ngb/zm/util/ImageLoader.java @@ -1,169 +1,354 @@ package schule.ngb.zm.util; import javax.imageio.ImageIO; -import java.awt.*; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.GraphicsEnvironment; +import java.awt.Image; import java.awt.image.BufferedImage; -import java.awt.image.RenderedImage; +import java.awt.image.ColorModel; +import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; -import java.net.URL; -import java.util.HashMap; +import java.io.InputStream; +import java.lang.ref.SoftReference; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; public class ImageLoader { - public static boolean cacheing = true; + public static boolean caching = true; - private static HashMap imageCache = new HashMap<>(); + private static Map> imageCache = new ConcurrentHashMap<>(); + private static SoftReference NOCACHE = new SoftReference<>(null); + + /** + * Lädt ein Bild von der angegebenen Quelle {@code source}. + *

+ * Die Bilddatei wird nach den Regeln von + * {@link ResourceStreamProvider#getResourceStream(String)} gesucht und + * geöffnet. Tritt dabei ein Fehler auf oder konnte keine passende Datei + * gefunden werden, wird {@code null} zurückgegeben. + *

+ * Ist der {@link #activateCache() Cache aktiviert} und ein Bild mit der + * angegebenen Quelle schon vorhanden, wird das gespeicherte Bild + * zurückgegeben. + * + * @param source + * @return + * @see #loadImage(String, boolean) + */ public static BufferedImage loadImage( String source ) { - return loadImage(source, cacheing); + return loadImage(source, caching); } /** - * Läadt ein Bild von der angegebenen Quelle source und gibt das + * Lädt ein Bild von der angegebenen Quelle source und gibt das * Bild zurück oder null, wenn das Bild nicht geladen werden * konnte. Ist ein Bild mit der angegebenen Quelle im Cache, wird das - * gechachete Bild zurückgegeben. Dies kann mit cacheing = false + * gespeicherte Bild zurückgegeben. Dies kann mit {@code caching = false} * verhindert werden. *

- * Wurde chacheing global deaktiviert, kann mit cacheing = true - * das Bild trotzdem aus dem Cache geladen werden, wenn es vorhanden ist. + * Wurde das Chaching global deaktiviert, kann mit caching = + * true das Bild trotzdem aus dem Cache geladen werden, wenn es + * vorhanden ist. * - * @param source - * @param cacheing + * @param source Die Quelle des Bildes. + * @param caching Ob das Bild (falls vorhanden) aus dem Zwischenspeicher + * geladen werden soll. * @return */ - public static BufferedImage loadImage( String source, boolean cacheing ) { - if( source == null || source.length() == 0 ) - throw new IllegalArgumentException("Image source may not be null or empty."); + public static BufferedImage loadImage( String source, boolean caching ) { + Validator.requireNotNull(source, "Image source may not be null"); + Validator.requireNotEmpty(source, "Image source may not be empty."); - if( cacheing && imageCache.containsKey(source) ) { - BufferedImage cachedImage = imageCache.get(source); - if( cachedImage != null ) { - return cachedImage; - } + if( caching && isCached(source) ) { + return getCache(source); } - try { - BufferedImage img; + BufferedImage img = null; + try( InputStream in = ResourceStreamProvider.getResourceStream(source) ) { + //URL url = ResourceStreamProvider.getResourceURL(source); + //BufferedImage img = ImageIO.read(url); - // Load image from working dir - File file = new File(source); - if( file.isFile() ) { - img = ImageIO.read(file); - } else { - // load ressource relative to .class-file - URL url = ImageLoader.class.getResource(source); + img = ImageIO.read(in); - // relative to ClassLoader - if( url == null ) { - url = ImageLoader.class.getClassLoader().getResource(source); - } - - // load form web or jar-file - if( url == null ) { - url = new URL(source); - } - - img = ImageIO.read(url); + if( caching && img != null ) { + putCache(source, img); } - - if( cacheing && img != null ) { - imageCache.put(source, img); - } - - return img; - } catch( IOException ioe ) { - return null; + } catch( IOException ioex ) { + LOG.error(ioex, "Error loading image file from source <%s>.", source); } + return img; } /** - * Loads an image into the cache with a user specified name that may differ - * from the image source string. + * Lädt ein Bild aus der angegebenen Quelle unter dem angegebenen Namen in + * den Cache. + *

+ * Der {@code name} kann beliebig gewählt werden. Existiert unter dem Namen + * schon ein Bild im Zwischenspeicher, wird dieses überschrieben. + *

+ * Wenn der Cache aktiviert ist, werden zukünftige Aufrufe von + * {@link #loadImage(String)} mit {@code name} als Quelle das gespeicherte + * Bild zurückgeben. * - * @param name - * @param source - * @return + * @param name Name des Bildes im Zwischenspeicher. + * @param source Quelle, aus dem das Bild geladen werden soll. + * @return {@code true}, wenn das Bild erfolgreich geladen wurde, + * {@code false} sonst. + * @see #loadImage(String) */ public static boolean preloadImage( String name, String source ) { BufferedImage img = loadImage(source, true); - if( cacheing && img != null ) { - imageCache.put(name, img); + if( img != null ) { + putCache(name, img); return true; } return false; } /** - * Checks if an image with the given name is currently cached. + * Speiechert das angegebene Bild unter dem angegebenen Namen im Cache. + *

+ * Exisitert zu {@code name} schon ein Bild im Zwischenspeicher + * ({@code ImageLoader.isCached(name) == true}), dann wird dieses + * überschrieben. + *

+ * Wenn der Cache aktiviert ist, werden zukünftige Aufrufe von + * {@link #loadImage(String)} mit {@code name} als Quelle das gespeicherte + * Bild zurückgeben. * - * @param name - * @return + * @param name Name des Bildes im Zwischenspeicher. + * @param img ZU speicherndes Bild. */ - public static boolean isCached( String name ) { - if( imageCache.containsKey(name) ) { - return imageCache.get(name) != null; - } - return false; + public static void preloadImage( String name, BufferedImage img ) { + putCache(name, img); } /** - * Remove the specified key from the cache. + * Prüft, ob zum angegebenen Namen ein Bild im Cache gespeichert ist. * - * @param name + * @param name Name des Bildes im Cache. + * @return {@code true}, wenn es ein Bild zum Namen gibt, sonst + * {@code false}. */ - public static void invalidateCache( String name ) { + public static boolean isCached( String name ) { + SoftReference imgRef = imageCache.get(name); + return imgRef != null && imgRef != NOCACHE && imgRef.get() != null; + } + + /** + * Entfernt das Bild zum angegebenen Namen aus dem Cache. Gibt es zum Namen + * kein Bild im Zwischenspeicher, dann passiert nichts. + * + * @param name Name des Bildes im Cache. + */ + public static void invalidateCache( final String name ) { if( imageCache.containsKey(name) ) { imageCache.remove(name); } } /** - * Prevents caching for the specified source. + * Speichert ein Bild als {@link SoftReference} im Cache. * - * @param source + * @param name Name des Bildes im Zwischenspeicher. + * @param img Das zu speichernde Bild. */ - public static void noCache( String source ) { - imageCache.put(source, null); + private static void putCache( final String name, final BufferedImage img ) { + imageCache.put(name, new SoftReference<>(img)); } + /** + * Holt ein Bild aus dem Cache. + *

+ * Prüft nicht, ob ein Bild vorhanden ist. Dies sollte vom Aufrufenden + * übernommen werden, da sonst eine {@link NullPointerException} erzeugt + * werden kann. + * + * @param name + * @return + */ + private static BufferedImage getCache( final String name ) { + return imageCache.get(name).get(); + } + + /** + * Deaktiviert den Cache für die angegebene Quelle. + *

+ * Selbst wenn der {@link #activateCache() Cache aktiviert} ist, wird das + * Bild zur angegebenen Quelle niemals zwischengespeichert und immer neu + * geladen. + * + * @param name Die Quelle des Bildes. + */ + public static void preventCache( final String name ) { + imageCache.put(name, NOCACHE); + } + + /** + * Leer den Cache und löschte alle bisher gespeicherten Bilder. + *

+ * Auch vorher mit {@link #preventCache(String)} verhinderte Caches werden + * gelöscht und müssen neu gesetzt werden. + */ public static void clearCache() { imageCache.clear(); } - public static void enableCaching() { - cacheing = true; + /** + * Aktiviert den Cache. + *

+ * Der Cache ist ein Zwischenspeicher für geladene Bilder. Wenn er aktiviert + * ist (Standard), dann werden mit dem {@code ImageLoader} geladene Bilder + * im Zwischenspeicher abgelegt. Bei jedem folgenden laden desselben Bildes + * (bzw. eines Bildes mit derselben {@code source}), wird das gespeicherte + * Bild zurückgegeben und nicht komplett neu geladen. + *

+ * Wichtig: Bildreferenzen, die aus dem Cache geladen werden, + * verweisen alle auf dasselbe Objekt. Änderungen schalgen sich daher in + * allen anderen Versionen des Bildes nieder (inklusive dem Bild im + * Zwischenspeicher). Für Änderungen sollte daher immer + * {@link #copyImage(BufferedImage) eine Kopie} des Bildes erstellt werden: + *

+	 * BufferedImage originalImage = ImageLoader.loadImage("assets/image.gif");
+	 * BufferedImage copiedImage = ImageLoader.copyImage(originalImage);
+	 * 
+ *

+ * Alternativ kann der Cache umgangen werden, indem + * {@link #loadImage(String, boolean)} verwendet wird. + */ + public static void activateCache() { + caching = true; } - public static void disableCaching() { - cacheing = false; + /** + * Deaktiviert den Cache. + * + * @see #activateCache() + */ + public static void deactivateCache() { + caching = false; } + /** + * Erstellt eine exakte Kopie des angegebenen Bildes als neues Objekt. + *

+ * Die Methode ist hilfreich, wenn ein Bild aus dem + * {@link #activateCache() Cache} geladen wurde und dann verändert werden + * soll, ohne den Cache (oder andere Referenzen auf das Bild) zu verändern. + * + * @param image Das Originalbild. + * @return Eine exakte Kopie des Originals. + */ + public static BufferedImage copyImage( BufferedImage image ) { + ColorModel cm = image.getColorModel(); + boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); + WritableRaster raster = image.copyData(null); + return new BufferedImage(cm, raster, isAlphaPremultiplied, null); + } + + /** + * Erstellt ein {@code BufferedImage} mit demselben Inhalt wie das + * angegebene {@code Image}. + * + * @param image Das Originalbild. + * @return Eine exakte Kopie des Originals. + */ + public static BufferedImage copyImage( Image image ) { + if( image instanceof BufferedImage ) { + return copyImage((BufferedImage) image); + } else { + //BufferedImage outimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB); + BufferedImage outimage = createImage(image.getWidth(null), image.getHeight(null)); + + Graphics2D g = outimage.createGraphics(); + g.setColor(Color.WHITE); + g.fillRect(0, 0, outimage.getWidth(), outimage.getHeight()); + g.drawImage(image, 0, 0, null); + g.dispose(); + + return outimage; + } + } + + /** + * Speichert das angegebene Bild in der angegebenen Datei auf der + * Festplatte. + * + * @param image Das zu speichernde Bild. Falls die Datei schon existiert, + * wird sie überschrieben. + * @param file Die Zieldatei. + * @throws NullPointerException Falls {@code image} oder {@code file} + * {@code null} ist. + * @throws IOException Falls es einen Fehler beim Speichern gab. + */ public static void saveImage( Image image, File file ) throws IOException { - saveImage(image, file, false); + saveImage(copyImage(image), file, false); } + /** + * Speichert das angegebene Bild in der angegebenen Datei auf der + * Festplatte. + * + * @param image Das zu speichernde Bild. Falls die Datei schon existiert, + * wird sie überschrieben. + * @param file Die Zieldatei. + * @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei + * überschrieben, bei {@code false} wird eine {@link IOException} geworfen, + * wenn die Datei schon exisitiert. + * @throws NullPointerException Falls {@code image} oder {@code file} + * {@code null} ist. + * @throws IOException Falls es einen Fehler beim Speichern gab. + */ 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); - BufferedImage outimage = createImage(image.getWidth(null), image.getHeight(null)); - - 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); + saveImage(copyImage(image), file, overwriteIfExists); } + /** + * Speichert das angegebene Bild in der angegebenen Datei auf der + * Festplatte. + * + * @param image Das zu speichernde Bild. Falls die Datei schon existiert, + * wird sie überschrieben. + * @param file Die Zieldatei. + * @throws NullPointerException Falls {@code image} oder {@code file} + * {@code null} ist. + * @throws IOException Falls es einen Fehler beim Speichern gab. + */ public static void saveImage( BufferedImage image, File file ) throws IOException { saveImage(image, file, false); } + /** + * Speichert das angegebene Bild in der angegebenen Datei auf der + * Festplatte. + * + * @param image Das zu speichernde Bild. Falls die Datei schon existiert, + * wird sie überschrieben. + * @param file Die Zieldatei. + * @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei + * überschrieben, bei {@code false} wird eine {@link IOException} geworfen, + * wenn die Datei schon exisitiert. + * @throws NullPointerException Falls {@code image} oder {@code file} + * {@code null} ist. + * @throws IOException Falls es einen Fehler beim Speichern gab. + */ public static void saveImage( BufferedImage image, File file, boolean overwriteIfExists ) throws IOException { + if( image == null ) { + throw new NullPointerException("Image may not be ."); + } + if( file == null ) { + throw new NullPointerException("File may not be ."); + } + if( file.isFile() ) { + // Datei existiert schon if( !overwriteIfExists ) { throw new IOException("File already exists. Delete target file before saving image."); } else if( !file.canWrite() ) { @@ -171,6 +356,7 @@ public class ImageLoader { } } + // Dateiformat anhand der Dateiendung ermitteln oder PNG nutzen String filename = file.getName(); String formatName = "png"; if( filename.lastIndexOf('.') >= 0 ) { @@ -179,13 +365,31 @@ public class ImageLoader { file = new File(file.getAbsolutePath() + ".png"); } + // Datei schreiben ImageIO.write(image, formatName, file); } + /** + * Erstellt ein neues, leeres {@code BufferedImage} passend für dieses + * Anzeigegerät. + * + * @param width Breite des leeren Bildes. + * @param height Höhe des leeren Bildes. + * @return Ein neues, leeres Bild. + */ public static BufferedImage createImage( int width, int height ) { return createImage(width, height, BufferedImage.TYPE_INT_RGB); } + /** + * Erstellt ein neues, leeres {@code BufferedImage} passend für dieses + * Anzeigegerät. + * + * @param width Breite des neuen Bildes. + * @param height Höhe des neuen Bildes. + * @param type {@link BufferedImage#getType() Typ} des neuen Bildes. + * @return Ein neues, leeres Bild. + */ public static BufferedImage createImage( int width, int height, int type ) { return GraphicsEnvironment .getLocalGraphicsEnvironment() @@ -194,4 +398,9 @@ public class ImageLoader { .createCompatibleImage(width, height, type); } + private ImageLoader() { + } + + private static final Log LOG = Log.getLogger(ImageLoader.class); + } diff --git a/src/schule/ngb/zm/util/Log.java b/src/schule/ngb/zm/util/Log.java new file mode 100644 index 0000000..dd22f72 --- /dev/null +++ b/src/schule/ngb/zm/util/Log.java @@ -0,0 +1,318 @@ +package schule.ngb.zm.util; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.function.Supplier; +import java.util.logging.*; + +import static java.util.logging.Level.*; + +/** + * Einfache Logging-API, die auf {@link java.util.logging} aufsetzt. + *

+ * Klassen, die Informations- oder Fehlernachrichten loggen wollen, erstellen + * ein internes {@code LOG} Objekt dieser Klasse. Die Zeichenmaschine erstellt + * ihren Logger beispielsweise so: + *

+ *     private static final Log LOG = new Log(Zeichenmaschine.class);
+ * 
+ *

+ * Jedes {@code Log} nutzt intern einen {@link Logger}, der über + * {@link Logger#getLogger(String)} abgerufen wird. Die {@code Log}-Objekte + * selbst werden nicht weiter zwischengespeichert, aber in der Regel wird pro + * Klasse nur genau ein {@code Log}-Objekt erstellt. Mehrere {@code Log}s nutzen + * dann aber denselben {@code Logger}. + *

+ * Die API orientiert sich lose an Log4j und vereinfacht die + * Nutzung der Java logging API für die häufigsten Anwendungsfälle. + */ +public final class Log { + + private static final String ROOT_LOGGER = "schule.ngb.zm"; + + private static boolean LOGGING_INIT = false; + + /** + * Aktiviert das Logging in der Zeichenmaschine global. + *

+ * Die Methode sollte einmalig möglichst früh im Programm aufgerufen werden, + * um für alle bisher und danach erstellten {@link Logger} das minimale + * Logging-Level auf {@link Level#FINE} zu setzen. Dies entspricht allen + * Nachrichten die mit den Methoden (außer {@code trace}) eines {@link Log} + * erzeugt werden. + * + * @see #enableGlobalLevel(Level) + */ + public static final void enableGlobalDebugging() { + enableGlobalLevel(ALL); + } + + /** + * Setzt das Logging-Level aller bisher und danach erzeugten {@link Logger} + * auf den angegebenen {@code Level}. + *

+ * Das Level für bestehende {@code Logger} wird nur abgesenkt, so dass + * Nachrrichten bis {@code level} auf jeden Fall ausgegeben werden. Besitzt + * der {@code Logger} schon ein niedrigeres Level, wird dieses nicht + * verändert. Gleiches gilt für alle {@link ConsoleHandler}, die den + * bestehenden {@code Logger}n hinzugefügt wurden. + *

+ * Hinweis: Das Setzen des Logging-Level während der + * Programmausführung gilt als bad practice, also schlechter + * Programmierstil. Im Kontext der Zeichenmaschine macht dies Sinn, um + * Programmieranfängern eine einfache Möglichkeit zu geben, in ihren + * Programmen auf Fehlersuche zu gehen. Für andere Einsatzszenarien sollte + * auf die übliche Konfiguration des {@link java.util.logging} Pakets über + * eine Konfigurationsdatei zurückgegriffen werden. + * + * @param level Das Level, auf das alle {@code Logger} und {@code Handler} + * mindestens herabgesenkt werden sollen. + */ + public static final void xenableGlobalLevel( Level level ) { + int lvl = Validator.requireNotNull(level).intValue(); + + Logger rootLogger = Logger.getLogger(ROOT_LOGGER); + rootLogger.setLevel(level); + + for( Handler handler : rootLogger.getHandlers() ) { + if( handler instanceof ConsoleHandler ) { + Level handlerLevel = handler.getLevel(); + if( handlerLevel == null || handler.getLevel().intValue() > lvl ) { + handler.setLevel(level); + } + } + } + } + + public static final void enableGlobalLevel( Level level ) { + int lvl = Validator.requireNotNull(level).intValue(); + + // Decrease level of root level ConsoleHandlers for outpu + Logger rootLogger = Logger.getLogger(""); + for( Handler handler : rootLogger.getHandlers() ) { + if( handler instanceof ConsoleHandler ) { + Level handlerLevel = handler.getLevel(); + if( handlerLevel == null || handler.getLevel().intValue() > lvl ) { + handler.setLevel(level); + } + } + } + + // Decrease level of all existing ZM Loggers + Iterator loggerNames = LogManager.getLogManager().getLoggerNames().asIterator(); + while( loggerNames.hasNext() ) { + String loggerName = loggerNames.next(); + + if( loggerName.startsWith(ROOT_LOGGER) ) { + Logger logger = Logger.getLogger(loggerName); + logger.setLevel(level); + + for( Handler handler : logger.getHandlers() ) { + if( handler instanceof ConsoleHandler ) { + Level handlerLevel = handler.getLevel(); + if( handlerLevel == null || handler.getLevel().intValue() > lvl ) { + handler.setLevel(level); + } + } + } + } + } + } + + public static final Log getLogger( Class clazz ) { + if( !LOGGING_INIT ) { + Logger.getLogger(ROOT_LOGGER); + LOGGING_INIT = true; + } + return new Log(clazz); + } + + private final Logger LOGGER; + + private final Class sourceClass; + + private Log( final Class clazz ) { + sourceClass = clazz; + if( !clazz.getName().startsWith(ROOT_LOGGER) ) { + LOGGER = Logger.getLogger(ROOT_LOGGER + "." + clazz.getSimpleName()); + } else { + LOGGER = Logger.getLogger(clazz.getName()); + } + } + + /*public Log( final String name ) { + LOGGER = Logger.getLogger(name); + }*/ + + public void log( Level level, final CharSequence msg ) { + //LOGGER.log(level, msg::toString); + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), msg::toString); + doLog(level, null, msg::toString); + } + } + + public void log( Level level, final CharSequence msg, Object... params ) { + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), () -> String.format(msg.toString(), params)); + doLog(level, null, () -> String.format(msg.toString(), params)); + } + } + + public void log( Level level, final Supplier msgSupplier ) { + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), msgSupplier); + doLog(level, null, msgSupplier); + } + } + + public void log( Level level, final Throwable throwable, final CharSequence msg, Object... params ) { + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), throwable, () -> String.format(msg.toString(), params)); + doLog(level, throwable, () -> String.format(msg.toString(), params)); + } + } + + public void log( Level level, final Throwable throwable, final Supplier msgSupplier ) { + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), throwable, msgSupplier); + doLog(level, throwable, msgSupplier); + } + } + + private String inferCallerName() { + StackTraceElement[] trace = new Throwable().getStackTrace(); + for( int i = 0; i < trace.length; i++ ) { + /// if( trace[i].getClassName().equals(sourceClass.getName()) ) { + if( !trace[i].getClassName().equals(Log.class.getName()) ) { + return trace[i].getMethodName(); + } + } + return "unknown"; + } + + private void doLog( Level level, final Throwable throwable, final Supplier msgSupplier ) { + String clazz = sourceClass.getName(); + String method = "unknown"; + + StackTraceElement[] trace = new Throwable().getStackTrace(); + for( int i = 0; i < trace.length; i++ ) { + if( !trace[i].getClassName().equals(Log.class.getName()) ) { + clazz = trace[i].getClassName(); + method = trace[i].getMethodName(); + break; + } + } + + if( throwable != null ) { + LOGGER.logp(level, clazz, method, throwable, msgSupplier); + } else { + LOGGER.logp(level, clazz, method, msgSupplier); + } + } + + public boolean isLoggable( Level level ) { + return LOGGER.isLoggable(level); + } + + public void info( final CharSequence msg ) { + this.log(INFO, msg); + } + + public void info( final CharSequence msg, Object... params ) { + this.log(INFO, () -> String.format(msg.toString(), params)); + } + + public void info( final Supplier msgSupplier ) { + this.log(INFO, msgSupplier); + } + + public void info( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(INFO, ex, () -> String.format(msg.toString(), params)); + } + + public void info( final Throwable throwable, final Supplier msgSupplier ) { + this.log(INFO, throwable, msgSupplier); + } + + public void warn( final CharSequence msg ) { + this.log(WARNING, msg); + } + + public void warn( final CharSequence msg, Object... params ) { + this.log(WARNING, () -> String.format(msg.toString(), params)); + } + + public void warn( final Supplier msgSupplier ) { + this.log(WARNING, msgSupplier); + } + + public void warn( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(WARNING, ex, () -> String.format(msg.toString(), params)); + } + + public void warn( final Throwable throwable, final Supplier msgSupplier ) { + this.log(WARNING, throwable, msgSupplier); + } + + public void error( final CharSequence msg ) { + this.log(SEVERE, msg); + } + + public void error( final CharSequence msg, Object... params ) { + this.log(SEVERE, () -> String.format(msg.toString(), params)); + } + + public void error( final Supplier msgSupplier ) { + this.log(SEVERE, msgSupplier); + } + + public void error( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(SEVERE, ex, () -> String.format(msg.toString(), params)); + } + + public void error( final Throwable throwable, final Supplier msgSupplier ) { + this.log(SEVERE, throwable, msgSupplier); + } + + public void debug( final CharSequence msg ) { + this.log(FINE, msg); + } + + public void debug( final CharSequence msg, Object... params ) { + this.log(FINE, () -> String.format(msg.toString(), params)); + } + + public void debug( final Supplier msgSupplier ) { + this.log(FINE, msgSupplier); + } + + public void debug( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(FINE, ex, () -> String.format(msg.toString(), params)); + } + + public void debug( final Throwable throwable, final Supplier msgSupplier ) { + this.log(FINE, throwable, msgSupplier); + } + + public void trace( final CharSequence msg ) { + this.log(FINER, msg); + } + + public void trace( final CharSequence msg, Object... params ) { + this.log(FINER, () -> String.format(msg.toString(), params)); + } + + public void trace( final Supplier msgSupplier ) { + this.log(FINER, msgSupplier); + } + + public void trace( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(FINER, ex, () -> String.format(msg.toString(), params)); + } + + public void trace( final Throwable throwable, final Supplier msgSupplier ) { + this.log(FINER, throwable, msgSupplier); + } + +} diff --git a/src/schule/ngb/zm/util/ResourceStreamProvider.java b/src/schule/ngb/zm/util/ResourceStreamProvider.java new file mode 100644 index 0000000..cf231f9 --- /dev/null +++ b/src/schule/ngb/zm/util/ResourceStreamProvider.java @@ -0,0 +1,171 @@ +package schule.ngb.zm.util; + +import schule.ngb.zm.Zeichenmaschine; + +import java.io.*; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * Helferklasse, um {@link InputStream}s für Resourcen zu erhalten. + */ +public class ResourceStreamProvider { + + /** + * Sucht eine zur angegebenen Quelle passende Resource und öffnet einen + * passenden {@link InputStream}. Die konkrete Art des Streams hängt davon + * ab, welche Art an Resource gefunden wird: + *

    + *
  1. Ist {@code source} eine existierende Datei + * {@code new File(source}.isFile() == true}, dann wird ein + * {@link FileInputStream} erstellt.
  2. + *
  3. Ist {@code source} ein relativer Pfad im Projekt, wird ein passender + * Stream mit {@link Class#getResourceAsStream(String)} geöffnet.
  4. + *
  5. Ist {@code source} im Classpath enthalten, wird ein passender Stream + * mit {@link ClassLoader#getResourceAsStream(String)} geöffnet.
  6. + *
  7. Ist {@code source} eine gültige {@link URL}, dann wird ein + * {@link URL#openStream() Netwerkstream} geöffnet.
  8. + *
+ *

+ * Die Möglichen Resourcen werden in der gelisteten Reihenfolge durchprobiert + * und der erste gefundene Stream zurückgegeben. Auftretende Exceptions + * werden als {@link IOException} geworfen. Das bedeutet, falls für {@code source} + * keine gültige Resource gefunden werden kann, wird am Ende die + * von {@link URL#openStream()} erzeugte {@code IOException} geworfen. + *

+ * Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt. + * Eine {@link SecurityException} beim Öffnen des {@code FileInputStream} + * (zum Beispiel, weil auf eine existierende Datei keine Leserechte bestehen) + * verhindert daher, dass noch im Classpath gesucht wird. + * + * @param source Eine Quelle für die Resource (Absoluter Dateipfad, + * Dateipfad im Classpath oder Netzwerkresource) + * @return Ein {@code InputStream} für die Resource + * @throws NullPointerException Falls {@code source} {@code null} ist. + * @throws IllegalArgumentException Falls {@code source} ein leerer String + * ist. + * @throws IOException Geworfen beim öffnen des Streams zu + * einer bestehenden Resource oder falls + * keine passende Resource gefunden wurde. + */ + public static InputStream getResourceStream( String source ) throws NullPointerException, IllegalArgumentException, IOException { + if( source == null ) { + throw new NullPointerException("Resource source may not be null"); + } + if( source.length() == 0 ) { + throw new IllegalArgumentException("Resource source may not be empty."); + } + + InputStream in; + + // See if source is a readable file + File file = new File(source); + try { + if( file.isFile() ) { + in = new FileInputStream(file); + } + } catch( FileNotFoundException fnfex ) { + // Somehow an exception occurred, but we still try other sources + in = null; + } + // File does not exist, try other means + // load ressource relative to .class-file + in = Zeichenmaschine.class.getResourceAsStream(source); + + // relative to ClassLoader + if( in == null ) { + in = Zeichenmaschine.class.getClassLoader().getResourceAsStream(source); + } + + // load form web or jar-file + if( in == null ) { + in = new URL(source).openStream(); + } + + // One of the above got a valid Stream, + // otherwise an Exception was thrown + return in; + } + + /** + * Ermittelt zur angegebenen Quelle einen passenden {@link URL} (Unified + * Resource Locator). Eine passende Datei-Resource wird wie folgt + * ermittelt: + *

    + *
  1. Ist {@code source} eine existierende Datei + * ({@code new File(source}.isFile() == true})?
  2. + *
  3. Ist {@code source} ein relativer Pfad im Projekt ({@code getResource(source) != null})?.
  4. + *
  5. Ist {@code source} im Classpath enthalten ({@code getClassLoader().getResource(source) != null})?
  6. + *
  7. Ansonten erstellt ein {@link URL}-Objekt.
  8. + *
+ *

+ * Ein {@code URL} für die erste gefundene Resource wird zurückgegeben. + * Auftretende Exceptions + * werden als {@link IOException} geworfen. + *

+ * Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt. + * Eine {@link java.net.MalformedURLException} beim Konstruieren des {@code URL} + * zu einer Datei verhindert daher, dass noch im Classpath gesucht wird. + * + * @param source Eine Quelle für die Resource (Absoluter Dateipfad, + * Dateipfad im Classpath oder Netzwerkresource) + * @return Ein {@code InputStream} für die Resource + * @throws NullPointerException Falls {@code source} {@code null} ist. + * @throws IllegalArgumentException Falls {@code source} ein leerer String + * ist. + * @throws IOException Geworfen beim erzeugen eines URL zu + * einer bestehenden Resource. + */ + public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException { + if( source == null ) { + throw new NullPointerException("Resource source may not be null"); + } + if( source.length() == 0 ) { + throw new IllegalArgumentException("Resource source may not be empty."); + } + + File file = new File(source); + if( file.isFile() ) { + return file.toURI().toURL(); + } + + URL url; + + url = Zeichenmaschine.class.getResource(source); + if( url != null ) { + return url; + } + + url = Zeichenmaschine.class.getClassLoader().getResource(source); + if( url != null ) { + return url; + } + + return new URL(source); + } + + public static InputStream getResourceStream( File file ) throws FileNotFoundException, SecurityException { + if( file == null ) { + throw new NullPointerException("Provided file can't be null."); + } + + return new FileInputStream(file); + } + + public static InputStream getResourceStream( URL url ) throws IOException { + if( url == null ) { + throw new NullPointerException("Provided URL can't be null."); + } + + return url.openStream(); + } + + private ResourceStreamProvider() { + } + + + private static final Log LOG = Log.getLogger(ResourceStreamProvider.class); + +} diff --git a/src/schule/ngb/zm/util/Validator.java b/src/schule/ngb/zm/util/Validator.java new file mode 100644 index 0000000..50067b9 --- /dev/null +++ b/src/schule/ngb/zm/util/Validator.java @@ -0,0 +1,152 @@ +package schule.ngb.zm.util; + +import java.util.Objects; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public class Validator { + + public static final T requireNotNull( T obj ) { + return Objects.requireNonNull(obj); + } + + public static final T requireNotNull( T obj, CharSequence msg ) { + return Objects.requireNonNull(obj, msg::toString); + } + + public static final T requireNotNull( T obj, Supplier msg ) { + return Objects.requireNonNull(obj, msg); + } + + public static final String requireNotEmpty( String str ) { + return requireNotEmpty(str, (Supplier)null); + } + + public static final String requireNotEmpty( String str, CharSequence msg ) { + return requireNotEmpty(str, msg::toString); + } + + public static final String requireNotEmpty( String str, Supplier msg ) { + if( str.isEmpty() ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get()); + return str; + } + + public static final int requirePositive( int i ) { + return requirePositive(i, (Supplier)null); + } + + public static final int requirePositive( int i, CharSequence msg ) { + return requirePositive(i, msg::toString); + } + + public static final int requirePositive( int i, Supplier msg ) { + if( i <= 0 ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter must be positive (<%d> provided)", i) : msg.get()); + return i; + } + + public static final int requireNotNegative( int i ) { + return requireNotNegative(i, (Supplier)null); + } + + public static final int requireNotNegative( int i, CharSequence msg ) { + return requireNotNegative(i, msg::toString); + } + + public static final int requireNotNegative( int i, Supplier msg ) { + if( i < 0 ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be negative (<%d> provided)", i) : msg.get()); + return i; + } + + public static final int requireInRange( int i, int min, int max ) { + return requireInRange(i, min, max, (Supplier)null); + } + + public static final int requireInRange( int i, int min, int max, CharSequence msg ) { + return requireInRange(i, min, max, msg::toString); + } + + public static final int requireInRange( int i, int min, int max, Supplier msg ) { + if( i < min || i > max ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter out of range <%d> to <%d> (<%d> provided)", min, max, i) : msg.get()); + return i; + } + + public static final double requirePositive( double i ) { + return requirePositive(i, (Supplier)null); + } + + public static final double requirePositive( double i, CharSequence msg ) { + return requirePositive(i, msg::toString); + } + + public static final double requirePositive( double i, Supplier msg ) { + if( i <= 0 ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter must be positive (<%f> provided)", i) : msg.get()); + return i; + } + + public static final double requireNotNegative( double i ) { + return requireNotNegative(i, (Supplier)null); + } + + public static final double requireNotNegative( double i, CharSequence msg ) { + return requireNotNegative(i, msg::toString); + } + + public static final double requireNotNegative( double i, Supplier msg ) { + if( i < 0 ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be negative (<%f> provided)", i) : msg.get()); + return i; + } + + public static final double requireInRange( double i, double min, double max ) { + return requireInRange(i, min, max, (Supplier)null); + } + + public static final double requireInRange( double i, double min, double max, CharSequence msg ) { + return requireInRange(i, min, max, msg::toString); + } + + public static final double requireInRange( double i, double min, double max, Supplier msg ) { + if( i < min || i > max ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter out of range <%f> to <%f> (<%f> provided)", min, max, i) : msg.get()); + return i; + } + + /* + public static final N requirePositive( N num, Supplier msg ) { + if( num.doubleValue() <= 0 ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter must be positive (<%s> provided)", num.toString()) : msg.get()); + return num; + } + + public static final N requireNotNegative( N num, Supplier msg ) { + if( num.doubleValue() < 0 ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be negative (<%s> provided)", num.toString()) : msg.get()); + return num; + } + + public static final N requireInRange( N num, N min, N max, Supplier msg ) { + if( num.doubleValue() < min.doubleValue() || num.doubleValue() > max.doubleValue() ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter out of range <%s> to <%s> (<%s> provided)", min.toString(), max.toString(), num.toString()) : msg.get()); + return num; + } + */ + + public static final T[] requireNotEmpty( T[] arr, Supplier msg ) { + return requireSize(arr, 0, ()->"Parameter array may not be empty"); + } + + public static final T[] requireSize( T[] arr, int size, Supplier msg ) { + if( arr.length != size ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter array must have <%d> elements (<%d> provided)", size) : msg.get()); + return arr; + } + + private Validator() { + } + +} diff --git a/test/src/schule/ngb/zm/OptionsTest.java b/test/src/schule/ngb/zm/OptionsTest.java index f1c88d5..d7dae3e 100644 --- a/test/src/schule/ngb/zm/OptionsTest.java +++ b/test/src/schule/ngb/zm/OptionsTest.java @@ -45,4 +45,14 @@ class OptionsTest { assertFalse(SOUTHWEST.contains(NORTHWEST)); } + @Test + public void testOppositeDirection() { + assertEquals(SOUTH, NORTH.inverse()); + assertEquals(NORTH, SOUTH.inverse()); + assertEquals(WEST, EAST.inverse()); + assertEquals(EAST, WEST.inverse()); + + assertEquals(SOUTHEAST, NORTHWEST.inverse()); + } + } diff --git a/test/src/schule/ngb/zm/TestShapes.java b/test/src/schule/ngb/zm/TestShapes.java index e947f9b..0a73166 100644 --- a/test/src/schule/ngb/zm/TestShapes.java +++ b/test/src/schule/ngb/zm/TestShapes.java @@ -22,8 +22,10 @@ public class TestShapes extends Zeichenmaschine { //createShapes(); //add(new Triangle(200, 150, 150, 250, 250, 250)); - add(new Rectangle(200, 200, 200, 100)); - shapes.getShape(0).setAnchor(CENTER); + //add(new Rectangle(200, 200, 200, 100)); + //shapes.getShape(0).setAnchor(CENTER); + + shapePositions(); } public void createShapes() { @@ -61,10 +63,29 @@ public class TestShapes extends Zeichenmaschine { add(new Text(200, 40, "Shapes 😊")); } + public void shapePositions() { + int pad = 24; + Rectangle bounds = new Rectangle(pad, pad, width-pad, height-pad); + + Rectangle[] rects = new Rectangle[5]; + for( int i = 0; i < rects.length; i++ ) { + rects[i] = new Rectangle(40 + i*15, 40 + i*15, 10, 10+i*4); + } + shapes.add(rects); + + for( Rectangle r: rects ) { + r.alignTo(bounds, LEFT); + } + + Text t = new TextBox(width/2, height/2, 200, 200, "Hello,\nWorld!"); + + shapes.add(t); + } + @Override public void update( double delta ) { //shapes.getShape(Triangle.class).rotate(2); - shapes.getShape(0).rotate(200, 250,2); + //shapes.getShape(0).rotate(200, 250,2); } @Override diff --git a/test/src/schule/ngb/zm/shapes/ShapeTest.java b/test/src/schule/ngb/zm/shapes/ShapeTest.java new file mode 100644 index 0000000..a258dcc --- /dev/null +++ b/test/src/schule/ngb/zm/shapes/ShapeTest.java @@ -0,0 +1,202 @@ +package schule.ngb.zm.shapes; + +import org.junit.jupiter.api.Test; +import schule.ngb.zm.Options; + +import static schule.ngb.zm.Options.Direction.*; + +import static org.junit.jupiter.api.Assertions.*; + + + +class ShapeTest { + + class TestShape extends Shape { + + public TestShape() { + super(42, 131); + } + + @Override + public double getWidth() { + return 182; + } + + @Override + public double getHeight() { + return 235; + } + + @Override + public Shape copy() { + return new TestShape(); + } + + @Override + public java.awt.Shape getShape() { + return null; + } + + } + + @Test + void testShape() { + Shape s = new TestShape(); + + assertEquals(42, s.getX()); + assertEquals(131, s.getY()); + assertEquals(182, s.getWidth()); + assertEquals(235, s.getHeight()); + + assertEquals(Options.Direction.CENTER, s.getAnchor()); + } + + @Test + void getAnchorPoint() { + Shape s = new TestShape(); + + assertEquals(0.0, s.getAnchorPoint(CENTER).getX(), 0.00001); + assertEquals(0.0, s.getAnchorPoint(CENTER).getY(), 0.00001); + + assertEquals(0.0, s.getAnchorPoint(UP).getX(), 0.00001); + assertEquals(-117.5, s.getAnchorPoint(UP).getY(), 0.00001); + + assertEquals(-91.0, s.getAnchorPoint(LEFT).getX(), 0.00001); + assertEquals(0.0, s.getAnchorPoint(LEFT).getY(), 0.00001); + + assertEquals(91.0, s.getAnchorPoint(SOUTHEAST).getX(), 0.00001); + assertEquals(117.5, s.getAnchorPoint(SOUTHEAST).getY(), 0.00001); + + s.setAnchor(DOWN); + + assertEquals(0.0, s.getAnchorPoint(DOWN).getX(), 0.00001); + assertEquals(0.0, s.getAnchorPoint(DOWN).getY(), 0.00001); + + assertEquals(0.0, s.getAnchorPoint(CENTER).getX(), 0.00001); + assertEquals(-117.5, s.getAnchorPoint(CENTER).getY(), 0.00001); + + assertEquals(0.0, s.getAnchorPoint(UP).getX(), 0.00001); + assertEquals(-235.0, s.getAnchorPoint(UP).getY(), 0.00001); + + assertEquals(-91.0, s.getAnchorPoint(LEFT).getX(), 0.00001); + assertEquals(-117.5, s.getAnchorPoint(LEFT).getY(), 0.00001); + + assertEquals(91.0, s.getAnchorPoint(SOUTHEAST).getX(), 0.00001); + assertEquals(0-0, s.getAnchorPoint(SOUTHEAST).getY(), 0.00001); + } + + @Test + void getAbsAnchorPoint() { + Shape s = new TestShape(); + + assertEquals(42.0, s.getAbsAnchorPoint(CENTER).getX(), 0.00001); + assertEquals(131.0, s.getAbsAnchorPoint(CENTER).getY(), 0.00001); + + assertEquals(42.0, s.getAbsAnchorPoint(UP).getX(), 0.00001); + assertEquals(13.5, s.getAbsAnchorPoint(UP).getY(), 0.00001); + + assertEquals(-49.0, s.getAbsAnchorPoint(LEFT).getX(), 0.00001); + assertEquals(131.0, s.getAbsAnchorPoint(LEFT).getY(), 0.00001); + + assertEquals(133.0, s.getAbsAnchorPoint(SOUTHEAST).getX(), 0.00001); + assertEquals(248.5, s.getAbsAnchorPoint(SOUTHEAST).getY(), 0.00001); + + s.setAnchor(DOWN); + + assertEquals(42.0, s.getAbsAnchorPoint(CENTER).getX(), 0.00001); + assertEquals(13.5, s.getAbsAnchorPoint(CENTER).getY(), 0.00001); + + assertEquals(42.0, s.getAbsAnchorPoint(UP).getX(), 0.00001); + assertEquals(-104.0, s.getAbsAnchorPoint(UP).getY(), 0.00001); + + assertEquals(-49.0, s.getAbsAnchorPoint(LEFT).getX(), 0.00001); + assertEquals(13.5, s.getAbsAnchorPoint(LEFT).getY(), 0.00001); + + assertEquals(133.0, s.getAbsAnchorPoint(SOUTHEAST).getX(), 0.00001); + assertEquals(131.0, s.getAbsAnchorPoint(SOUTHEAST).getY(), 0.00001); + } + + @Test + void testMoveTo() { + Shape s1 = new TestShape(); + Shape s2 = s1.copy(); + + s1.moveTo( 100, 200); + assertEquals(100.0, s1.x, 0.00001); + assertEquals(200.0, s1.y, 0.00001); + + s2.moveTo(s1); + assertEquals(100.0, s2.x, 0.00001); + assertEquals(200.0, s2.y, 0.00001); + + s2.moveTo(s1, RIGHT); + assertEquals(191.0, s2.x, 0.00001); + assertEquals(200.0, s2.y, 0.00001); + assertEquals(s2.getAbsAnchorPoint(CENTER), s1.getAbsAnchorPoint(RIGHT)); + assertEquals(s1.getAbsAnchorPoint(CENTER), s2.getAbsAnchorPoint(LEFT)); + + s2.moveTo(s1, RIGHT, 10.0); + assertEquals(201.0, s2.x, 0.00001); + assertEquals(200.0, s2.y, 0.00001); + + s2.moveTo(s1, LEFT); + assertEquals(9.0, s2.x, 0.00001); + assertEquals(200.0, s2.y, 0.00001); + + s2.moveTo(s1, LEFT, 10.0); + assertEquals(-1.0, s2.x, 0.00001); + assertEquals(200.0, s2.y, 0.00001); + } + + @Test + void testAlignTo() { + Shape s1 = new TestShape(); + Shape s2 = new TestShape(); + + s1.moveTo(100, 200); + + s2.alignTo(s1, LEFT, 0.0); + assertEquals(100.0, s2.getX()); + assertEquals(131.0, s2.getY()); + assertEquals(9.0, s2.getAbsAnchorPoint(LEFT).getX(), 0.00001); + + s2.moveTo(300, 400); + s2.alignTo(s1, LEFT, 10.0); + assertEquals(90.0, s2.getX()); + assertEquals(400.0, s2.getY()); + + s2.alignTo(s1, SOUTHEAST, 0.0); + assertEquals(100.0, s2.getX()); + assertEquals(200.0, s2.getY()); + + s2.moveTo(0, 0); + s2.alignTo(s1, DOWN, 24.0); + assertEquals(0.0, s2.getX()); + assertEquals(224.0, s2.getY()); + + s2.setAnchor(NORTHWEST); + s2.moveTo(0, 0); + s2.alignTo(s1, DOWN, 24.0); + assertEquals(0.0, s2.getX()); + assertEquals(106.5, s2.getY()); + } + + @Test + void testNextTo() { + Shape s1 = new TestShape(); + Shape s2 = new TestShape(); + + s2.nextTo(s1, RIGHT, 10.0); + assertEquals(234.0, s2.getX()); + assertEquals(131.0, s2.getY()); + + s2.nextTo(s1, LEFT, 10.0); + assertEquals(-150.0, s2.getX()); + assertEquals(131.0, s2.getY()); + + s2.nextTo(s1, SOUTHEAST, 10.0); + assertEquals(234.0, s2.getX()); + assertEquals(376.0, s2.getY()); + } + +} diff --git a/test/src/schule/ngb/zm/util/ValidatorTest.java b/test/src/schule/ngb/zm/util/ValidatorTest.java new file mode 100644 index 0000000..413399b --- /dev/null +++ b/test/src/schule/ngb/zm/util/ValidatorTest.java @@ -0,0 +1,127 @@ +package schule.ngb.zm.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ValidatorTest { + + @Test + void requireNotNull() { + StringBuilder sb = new StringBuilder("Message"); + + Object o1 = new Object(); + assertEquals(o1, Validator.requireNotNull(o1)); + assertEquals(o1, Validator.requireNotNull(o1, "Message")); + assertEquals(o1, Validator.requireNotNull(o1, sb)); + assertEquals(o1, Validator.requireNotNull(o1, ()->"Message")); + + String o2 = null; + assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2)); + assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "Message")); + assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, ()->"Message")); + } + + @Test + void requireNotEmpty() { + StringBuilder sb = new StringBuilder("Message"); + + String s1 = "Content"; + assertEquals(s1, Validator.requireNotEmpty(s1)); + assertEquals(s1, Validator.requireNotEmpty(s1, "Message")); + assertEquals(s1, Validator.requireNotEmpty(s1, sb)); + assertEquals(s1, Validator.requireNotEmpty(s1, ()->"Message")); + + String s2 = ""; + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "Message")); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, ()->"Message")); + + } + + @Test + void requirePositive() { + int i = 1; + assertEquals(i, Validator.requirePositive(i)); + assertEquals(i, Validator.requirePositive(i, "Message")); + assertEquals(i, Validator.requirePositive(i, ()->"Message")); + + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requirePositive(i-1)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requirePositive(i-1, "Message")); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requirePositive(i-1, ()->"Message")); + + double k = 1.0; + assertEquals(k, Validator.requirePositive(i)); + assertEquals(k, Validator.requirePositive(i, "Message")); + assertEquals(k, Validator.requirePositive(i, ()->"Message")); + + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requirePositive(k-1.0)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requirePositive(k-1.0, "Message")); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requirePositive(k-1.0, ()->"Message")); + } + + @Test + void requireNotNegative() { + int i = 0; + assertEquals(i, Validator.requireNotNegative(i)); + assertEquals(i, Validator.requireNotNegative(i, "Message")); + assertEquals(i, Validator.requireNotNegative(i, ()->"Message")); + + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotNegative(i-1)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotNegative(i-1, "Message")); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotNegative(i-1, ()->"Message")); + + double k = 0.0; + assertEquals(k, Validator.requireNotNegative(i)); + assertEquals(k, Validator.requireNotNegative(i, "Message")); + assertEquals(k, Validator.requireNotNegative(i, ()->"Message")); + + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotNegative(k-1.0)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotNegative(k-1.0, "Message")); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotNegative(k-1.0, ()->"Message")); + } + + @Test + void requireInRange() { + int min = 50, max = 100; + for( int ii = min; ii < max; ii++ ) { + final int i = ii; + assertEquals(i, Validator.requireInRange(i, min, max)); + assertEquals(i, Validator.requireInRange(i, min, max, "Message")); + assertEquals(i, Validator.requireInRange(i, min, max, () -> "Message")); + } + for( int ii = 0; ii < min; ii++ ) { + final int i = ii; + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(i, min, max)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(i, min, max)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(i, min, max)); + } + for( int ii = max+1; ii < 2*max; ii++ ) { + final int i = ii; + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(i, min, max)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(i, min, max)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(i, min, max)); + } + + double dmin = 50, dmax = 60; + for( double dd = dmin; dd < dmax; dd+=0.1 ) { + final double d = dd; + assertEquals(d, Validator.requireInRange(d, dmin, dmax)); + assertEquals(d, Validator.requireInRange(d, dmin, dmax, "Message")); + assertEquals(d, Validator.requireInRange(d, dmin, dmax, () -> "Message")); + } + for( double dd = dmin-10.0; dd < dmin; dd+=0.1 ) { + final double d = dd; + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(d, dmin, dmax)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(d, dmin, dmax)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(d, dmin, dmax)); + } + for( double dd = dmax+0.1; dd < dmax+10.0; dd+=0.1 ) { + final double d = dd; + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(d, dmin, dmax)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(d, dmin, dmax)); + assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireInRange(d, dmin, dmax)); + } + } + +}